Security Watch CLI scans local project files for security signals on Linux workstations.
It reports:
- local project dependency vulnerabilities from Trivy or OSV Scanner
- direct vs transitive (indirect) classification for each vulnerability
- the direct dependency chain that pulls in each transitive vulnerable package
- optional registry lookups (PyPI, npm) to check if a newer version of that direct dep resolves the issue
- optional public watch-feed matches from CISA KEV and NVD Recent
sec-watch is the core scanner. It scans your projects directory, fetches
public vulnerability feeds, caches results with a TTL, and prints a status line.
It is designed to run continuously — for example, triggered by a shell prompt or
status bar widget.
sec-watch-local is a one-shot wrapper around sec-watch. Use it when you
want to scan on demand, point at a specific directory, or scan a local Git branch
without touching your working tree. It:
- accepts a
PATHargument (defaults to the same projects dir assec-watch) - with
--branch <name>, clones a local mirror of the repo and checks the branch out into a temporary worktree before scanning - runs
sec-watchin an isolated per-run cache directory so it never touches the persistent cache - supports
--jsonoutput for scripting
Install Trivy:
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b ~/.local/binDownload the latest binaries from GitHub Releases:
TAG=$(curl -fsSL https://api.github.com/repos/virge-io/sec-watch/releases/latest | grep '"tag_name"' | cut -d'"' -f4)
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
for b in sec-watch sec-watch-local; do
curl -fLo ~/.local/bin/$b \
"https://github.com/virge-io/sec-watch/releases/download/${TAG}/${b}-${OS}-${ARCH}"
done
chmod +x ~/.local/bin/sec-watch ~/.local/bin/sec-watch-localVerify the MD5 checksums (optional):
SUMS=$(curl -fsSL "https://github.com/virge-io/sec-watch/releases/download/${TAG}/md5sums.txt")
for b in sec-watch sec-watch-local; do
expected=$(printf '%s\n' "$SUMS" | grep "${b}-${OS}-${ARCH}" | awk '{print $1}')
actual=$(md5sum ~/.local/bin/$b | awk '{print $1}')
[ "$expected" = "$actual" ] && echo "$b: OK" || echo "$b: MISMATCH"
doneMake sure ~/.local/bin is on your PATH. Verify:
sec-watch defaults-envOptionally install a keyword watch config:
mkdir -p ~/.config/sec-watch
cp config/watch.example.json ~/.config/sec-watch/watch.json
# edit ~/.config/sec-watch/watch.json to add your keywordsRun a scan from the default projects directory (~/Projects):
sec-watch-localScan a specific local directory:
sec-watch-local /path/to/projectScan a local Git branch without changing your working tree:
sec-watch-local /path/to/repo --branch mainScan a remote GitHub repo (clone first):
git clone --depth=1 https://github.com/org/repo /tmp/repo-scan
sec-watch-local /tmp/repo-scanMachine-readable JSON output:
sec-watch-local --jsonDebug mode — prints effective scanner settings and internal scan milestones:
sec-watch-local --debug --progress-interval 1Check defaults and current configuration:
sec-watch defaults-envFor each vulnerability, sec-watch classifies the affected package as direct or indirect (transitive). Indirect findings include:
- which direct dependency or dependencies pull in the vulnerable package (the "via" chain, resolved by BFS through the lockfile dependency graph)
- a registry lookup against PyPI or npm for each direct parent to find whether its latest release constrains the transitive dep to a fixed version
Example text report output:
1. CRITICAL CVE-2026-42208 (CVSS 9.8)
Package: litellm 1.83.0 [indirect]
Fix: needs litellm >= 1.83.7
Via: orchestrator-core 5.0.1 — latest 5.0.2 still allows unfixed litellm (>=1.80.0)
Target: uv.lock
...
If the latest release of the direct dep does require a fixed version, the advice will say so and name the version to upgrade to. If the package is private and not on the public registry, that is noted too.
Registry lookups add a few seconds to each scan. Set
SEC_WATCH_REGISTRY_LOOKUP=0 to disable them for offline or faster runs.
All defaults can be overridden via environment variables:
| Variable | Default | Description |
|---|---|---|
SEC_WATCH_PROJECTS_DIR |
~/Projects |
Root directory to scan |
SEC_WATCH_PROJECTS |
(all) | Comma-separated project name filter |
SEC_WATCH_ECOSYSTEMS |
npm,yarn,pnpm,pip,poetry,uv,python-pkg |
Dependency ecosystems to include |
SEC_WATCH_PUBLIC_FEEDS |
cisa-kev,nvd-recent |
Public CVE feeds to query |
SEC_WATCH_RECENT_DAYS |
7 |
Lookback window for "recent" changes |
SEC_WATCH_TTL |
1800 |
Cache TTL in seconds |
SEC_WATCH_CONFIG |
~/.config/sec-watch/watch.json |
Watch config path |
SEC_WATCH_REGISTRY_LOOKUP |
1 |
Set to 0 to skip PyPI/npm lookups |
SEC_WATCH_FORCE |
0 |
Set to 1 to bypass the cache |
SEC_WATCH_DEBUG |
0 |
Set to 1 for debug output |
The sec-watch-local wrapper also accepts flags and SEC_WATCH_LOCAL_* overrides:
# Scan a different projects root
SEC_WATCH_PROJECTS_DIR=~/Code sec-watch-local
# Filter to specific ecosystems
sec-watch-local --ecosystems npm,pip
# Disable public feeds
sec-watch-local --public-feeds ''
# Override recent-days lookback
sec-watch-local --recent-days 14Public-feed keyword matching is configured in ~/.config/sec-watch/watch.json:
{
"lookback_days": 14,
"keywords": [
"openssh",
"openssl",
"linux kernel",
"node.js"
]
}The keywords list is matched case-insensitively against CVE IDs, vendor
names, product names, and descriptions in both CISA KEV and NVD. A starter
template is at config/watch.example.json.
Each scan writes reports to ~/.cache/sec-watch/:
| File | Description |
|---|---|
dependency-report.txt |
Terminal-formatted findings with CVSS details and via chain |
dependency-report.html |
Sortable HTML table with indirect badge and fix/via column |
dependency-report.json |
Raw Trivy output |
Both the text and HTML reports mark transitive packages with an [indirect]
label. The HTML report's Fix / Via column shows the fix version and the
direct dep chain inline. Tables can be sorted by clicking any column header.
sec-watch-local run artefacts are kept under ~/.cache/sec-watch-local/jobs/
for post-scan inspection.
Build and verify:
go build ./...
go vet ./...To test without touching the real cache:
XDG_CACHE_HOME=$(mktemp -d) SEC_WATCH_FORCE=1 sec-watch status