Watches the dependency manifests in your git repositories, checks the pinned versions against published security advisories, and sends a Telegram alert when something needs an urgent bump.
Sibling to cve-watcher (watching running services) — same alert philosophy: not every new version, only CVEs bad enough to act on, with the score, the version you have, and the minimum version to upgrade to.
- Inventory — discovers repositories on your Gitea instance via a
read-only token and reads their dependency files. Parsers land for
the stacks most self-hosted apps actually use:
- npm —
package.json,package-lock.json(v1/v2/v3),yarn.lock(classic + berry),pnpm-lock.yaml - Go —
go.mod - Python —
requirements.txt,Pipfile.lock,poetry.lock,pyproject.toml Cargo.toml/Gemfile/composer.jsonare detected and listed in the weekly digest as "ecosystem not watched yet" — parsers follow (see PLAN.md).
- npm —
- Match — resolves each pinned dependency version against GitHub Security Advisories (primary — package+ecosystem native), NVD, and CISA KEV. Within a run each package is fetched once (batched GHSA queries into memory); results are not reused across runs, so a new advisory is seen on the next cycle (default every 6 h). KEV is one daily bulk file. Fifty projects depending on the same library still cost one query per cycle, not fifty.
- Alert — one Telegram message per affected repository+dependency, grouped with the max CVSS and the minimum fixed version to upgrade to. Deduplicated so you hear about each problem once; KEV (known-exploited) findings flagged as urgent. Weekly digest covers open findings and dependencies pinned to ranges that can't be checked.
A message looks like this:
⚠️ Security updates needed
gitea:main · myorg/webapp · npm/lodash
Manifest: package-lock.json (locked)
Current: 4.17.15
Findings: 2 (max CVSS 9.8)
Update to at least: 4.17.21
• CVE-2021-23337 — CVSS 7.2
• CVE-2020-28500 — CVSS 7.5
Command injection in template.
https://nvd.nist.gov/vuln/detail/CVE-2021-23337
git clone https://github.com/CaffeinatedTech/dep-cve-watcher
cd dep-cve-watcher
cp config.example.yaml config.yaml # edit: chat id, gitea url, discover
cp env.example .env # fill tokens (see docs/DEPLOY.md)
go run . --once --print-inventory # merged inventory, no alert traffic
go run . --once # one real cycle, then exit
--print-inventory prints every discovered repo with its resolved
dependencies and manifest notes — the fast way to sanity-check
discover: and ignore: rules before trusting alerts. Full
credentials setup (Telegram bot, Gitea machine-user PAT, GitHub/NVD
keys) is docs/DEPLOY.md.
One YAML file (config.example.yaml, fully commented). Credentials never live here — they come from env vars (env.example):
| Env var | Purpose |
|---|---|
DEPCVEWATCHER_TELEGRAM_BOT_TOKEN |
required for alerts (dry-run works without) |
DEPCVEWATCHER_GITEA_<NAME>_TOKEN |
one read-only PAT per gitea: instance |
DEPCVEWATCHER_GITHUB_TOKEN |
optional, raises GHSA limit 60 → 5000/h |
DEPCVEWATCHER_NVD_API_KEY |
optional, only if sources.nvd: true |
The knobs:
interval: 6h # check cadence (in-process ticker)
severity_threshold: 7.0 # alert on CVSS >= this, or any KEV entry
state_file: state.json # local default; use /data/... in containers
cache_dir: cache # KEV/NVD disk cache only (GHSA is in-memory)
digest_day: Mon # weekly digest; "" disables
telegram:
chat_id: "000000000"
gitea: # zero or more instances
- name: main # token env var: DEPCVEWATCHER_GITEA_MAIN_TOKEN
url: https://gitea.example.com
discover:
orgs: [example-org] # all repos in these orgs
# users: [someone] # and/or repos owned by these users
# repositories: # and/or an explicit owner/repo allowlist
# - otherorg/legacy-app
ignore: # applied at inventory collection
repos: [] # owner/repo to skip entirely
packages: [] # package name, any ecosystem
ecosystems: [] # e.g. [maven] — also hides its digest notes
paths: [] # repo path prefixes, e.g. "legacy/"
sources:
ghsa: true # primary: package+ecosystem native
nvd: false # secondary; needs a CPE mapping to be useful
kev: true # flag actively-exploited CVEs as urgentRules worth knowing:
- Ranges are never guessed.
^1.2.0without a lockfile goes to the digest's "cannot check" list — silence still means all-clear. - Ecosystem travels with every dependency —
requests(pip) is not any otherrequests; in-memory keys and advisory queries are scoped. - Failed Telegram sends queue for the next cycle; state is saved atomically so restarts never re-spam or drop findings.
Three supported shapes — one binary + one config + a writable data dir in every case:
| Target | Artifact |
|---|---|
| k3s / Kubernetes | deploy/ manifests + NetworkPolicy |
| Docker Compose | docker-compose.yml |
| systemd / Coolify | unit + steps in docs/DEPLOY.md |
Images are built by hand with dated tags
(ghcr.io/caffeinatedtech/dep-cve-watcher:YYYY-MM-DD) — no CI; the
build/push procedure is docs/DEPLOY.md §4.
- Dependabot — not self-hostable outside GitHub.
- Renovate — opens fix PRs; doesn't send you an ops-style alert with score + current + minimum fix. Useful as a companion, not this.
- Trivy / Grype — general-purpose scanners; you'd still wrap them in discovery, caching, dedup, and Telegram yourself. That wrapper is what this is.
- Gitea Enterprise dependency scanning — paid, dashboard-first, no Telegram push.
If one of those fits better, use it. dep-cve-watcher exists for the specific gap: pinned version in your repos → published advisory → urgent Telegram alert, centrally, without installing anything in every repository.
One central service with read-only access to your forge — not a
workflow file in every repo. New repositories are covered the moment
they're created; alerts are deduplicated and digested in one place.
Repo access is Gitea's tree + contents API (no git binary — the image
stays FROM scratch). See PLAN.md for the full design and
REQUIREMENTS.md for the spec.
Implementation Phases 1–5.5 are complete (domain model, Gitea
discovery, manifest/lockfile parsers, advisory matching, alerting +
digest, reliability fixes); Phase 6 (this pass) finalizes deployment
files and docs. Open items — more ecosystems, ignore-rule extensions,
v0.1.0 tag — live as checkboxes in PLAN.md.