Inspiration
Every team I've been on has a dependency graveyard. Packages that haven't been touched in two years, sitting quietly in package.json, accumulating CVEs. Dependabot fires off 47 alerts and nobody knows which three actually matter. So the alerts get snoozed and the cycle repeats.
The real problem isn't that teams don't care. It's that the alerts give you no context. "axios is outdated" means nothing without knowing whether axios is in your auth middleware or a weekend script in /tools. Those are very different conversations.
I wanted something that would do the triage automatically and show up once a week with a short, actionable list instead of a wall of noise.
What it does
Dependency Aging Advisor is a GitHub Action. You add one workflow file to your repo and it runs every Monday morning. It opens a GitHub issue with a prioritized advisory.
Every run:
- Detects your package ecosystems (npm, PyPI, RubyGems, Go, Rust, Maven, PHP, NuGet)
- Parses manifests and lockfiles to find pinned versions
- Queries OSV.dev for known vulnerabilities
- Fetches latest versions from each registry
- Scans your source files with ripgrep to find where each dependency is actually imported
- Classifies usage depth:
CRITICAL/CORE/FEATURE/PERIPHERAL - Computes a risk score (0-10) from four weighted factors
- Drops everything into one of four action buckets
The buckets:
| Bucket | Score | Meaning |
|---|---|---|
| 🔴 Act This Week | >= 8.0 | High-severity vuln in heavily-used code |
| 🟠 Act This Month | >= 5.0 | Moderate risk, plan for upcoming sprint |
| 🟡 Track Monthly | >= 3.0 | Low risk, keep on radar |
| ⚪ Ignore | < 3.0 | No material risk signal |
The risk score formula:
score = 0.55 x OSV_severity + 0.25 x usage_depth + 0.15 x version_lag + 0.05 x maintenance_health
All four weights are configurable in the workflow file without touching any code.
If OSV.dev is down, the advisory still gets created with an outage notice and retry guidance. The issue never silently skips.
How we built it
The whole tool is a single Python script with zero pip dependencies. Pure stdlib. No supply chain risk from the tool that's supposed to protect you from supply chain risk.
The usage depth classifier is the interesting piece. It uses ripgrep with ecosystem-specific regex patterns to scan your source for import statements, then classifies each dependency by what kind of file is importing it. A dependency imported in auth/, jwt/, middleware/, or crypto/ paths is CRITICAL. The same package imported only in test files is PERIPHERAL. Dev-scope dependencies are always capped at PERIPHERAL because they don't run in production.
The terminal output is colour-coded with a segmented score bar per dependency. Each segment shows the contribution of each factor so you can see exactly what's driving a high score:
* axios 6.1 ##################..........
0.21.1 -> 1.14.0 PERIPHERAL 4 vuln(s)
* lodash 5.7 ##################.......
4.17.15 -> 4.18.1 PERIPHERAL 6 vuln(s)
Every run also produces advisory.html, a self-contained dark-theme interactive risk map. Each dependency is plotted as a bubble: X axis is version lag, Y axis is OSV severity, bubble size is usage depth. Hovering shows the exact upgrade command. Zero external dependencies, works offline, opens in any browser.
There's also an optional force-directed import graph that shows which source files pull in which dependencies. Enable it with import-graph: true in the workflow.
The action runs fully inside GitHub Actions with GITHUB_TOKEN only. No external secrets to configure.
Challenges we ran into
Getting usage depth classification right took the most iteration. Ripgrep finds where a package is imported, but the same package might be imported in 12 different files across 4 different path categories. The classification has to take the highest-risk path hit, not average them, so a package that's used in both src/auth/ and tests/ correctly shows up as CRITICAL, not somewhere in the middle.
Manifest parsing across 8 ecosystems is a long tail problem. npm's lockfile has three different formats across v1/v2/v3. Poetry locks look nothing like requirements.txt. Maven's pom.xml uses ${property} interpolation that you have to resolve before the version string is usable. Each ecosystem needed its own parsing path.
OSV.dev's batch API has rate and payload limits. Querying all dependencies naively for a large repo would hit those. The collector batches requests and handles partial responses gracefully so a single failed batch doesn't kill the whole advisory.
Accomplishments that we're proud of
The zero-dependency constraint held. The entire tool runs on Python 3.11 stdlib. The only external binary is ripgrep, which is pre-installed on GitHub-hosted runners.
The weight system ended up being cleaner than expected. Security-focused teams can push risk-weight-osv to 0.70 and de-weight version lag. Teams doing planned migrations can flip it the other way. The formula stays the same and the issue output changes accordingly.
The fixture test suite runs fully offline with synthetic dependency and OSV data. You can validate the entire bucketing and scoring pipeline without a network connection or a real project.
The interactive HTML risk map is self-contained in a single file. No CDN, no framework, no external calls. You can email it to someone and it works.
What we learned
The usage depth signal is more useful than the OSV score for prioritizing work. A low-severity vuln in your auth middleware is more urgent than a high-severity vuln in a dev-only linter. Most existing tools don't model this at all. They sort by CVE score and call it done.
Writing the script against pure stdlib forces you to think carefully about what you actually need. There's no requests, no packaging, no toml on 3.10. You end up with simpler, more auditable code, which is exactly what you want in a security tool.
Flattening the output into a GitHub issue was the right call. Slack alerts get muted. Dashboard links don't get clicked. An issue in the repo shows up in the team's weekly review and has a clear owner.
What's next for Dependency Aging Advisor
- PR mode: instead of opening an issue, open a draft PR with the lockfile updates already applied for the top 3 Act This Week items
- SBOM integration: accept a CycloneDX or SPDX file as input so the action works on repos that generate SBOMs separately rather than parsing manifests directly
- Slack / Teams output: post the advisory summary as a message alongside the issue for teams that do async triage in chat
- Historical trending: track score changes week over week and flag dependencies that are getting worse, not just bad ones
- Policy file: define per-repo rules like "never allow AGPL in CORE paths" and fail the workflow if a violation is detected
Log in or sign up for Devpost to join the conversation.