A polyglot dependency-health platform (NuGet · npm · PyPI · Cargo · Go), built in .NET — it resolves a project's full transitive graph, scores every package for security, license, license-shift and maintenance risk, and helps you remediate, gate and monitor it.
Quick start · Features · Usage · Architecture · ▶ Demo video
Teams discover license changes (MediatR, AutoMapper, MassTransit and FluentAssertions all went commercial in 2025), security advisories, abandoned packages and breaking changes far too late — usually at the next audit or incident.
DepRadar resolves the full transitive dependency graph of a NuGet, npm, PyPI, Cargo or Go package or project, scores every node with an explainable risk model, and answers the question every tech lead actually has: "is this upgrade worth it — and how risky is it?" It then lets you fix the risky ones (in place or via a PR), gate them in CI, and monitor them over time with alerts.
How is that different from Dependabot, Renovate or Snyk?
| Dependabot / Renovate | Snyk & co. | DepRadar | |
|---|---|---|---|
| Core job | open update PRs | commercial vulnerability platform | explainable risk scoring + one CI gate |
| Prioritization | none (a PR per bump) | proprietary | EPSS + CISA KEV — "is it actually exploited?" |
| Ecosystems, one tool | per-repo config | yes (paid tiers) | NuGet · npm · PyPI · Cargo · Go, keyless |
| Runs | GitHub-hosted | SaaS | local CLI / your CI — no account, no key |
| License & maintenance risk | ✗ | partial | license shift, deprecated/archived/stale signals |
DepRadar complements a PR bot: the bot bumps versions, DepRadar tells you which risk actually matters and blocks the build when it crosses your policy.
▶ Watch the 15-second screencast · landing → graph → risk & remediation → upgrade diff → drift
![]() |
![]() |
![]() |
![]() |
Risk ranking with the vulnerability path + fix · drift since last scan · upgrade-impact diff · npm live in the dashboard
Gate a build in 30 seconds — the CLI runs the whole analysis in-process (no server, no database):
dotnet tool install --global DepRadar.Tool # with the .NET SDK…
depradar scan WindowsAzure.Storage --fail-on high # exit 1 fails CI on a policy breach
depradar npm express # npm too
depradar pypi requests # …PyPI (Python)
depradar cargo serde # …Cargo (Rust)
depradar go golang.org/x/text # …and Go
depradar fix ./MyApp.csproj --open-pr --repo owner/name # auto-fix → opens a pull requestNo .NET SDK? Every release ships self-contained single-file binaries (Linux x64/arm64, macOS x64/arm64, Windows x64 — checksums + provenance attested):
curl -fsSL https://github.com/AdrianDeutsch/DepRadar/releases/latest/download/depradar-osx-arm64.tar.gz | tar xz
./depradar npm ./package.json --fail-on highOne gate, five ecosystems — exploit intelligence (EPSS + CISA KEV) and typosquat warnings included.
Run the full platform (dashboard, drift monitoring, REST API) with one command:
docker compose up --build # then open http://localhost:8080Note
Compose starts PostgreSQL + pgvector, the API (migrates on startup) and the Worker
(gated on the API being healthy). The pgvector extension is created up front, so there
is no first-run ordering issue. Tear down with docker compose down -v.
- Security scan — known CVE/GHSA advisories per package version (OSV.dev).
- Exploit intelligence — advisories are escalated with real-world evidence: CISA-KEV-listed CVEs become Critical, high EPSS probabilities raise severity (ADR 0022).
- License & license-shift — flags SPDX changes and the OSS → commercial pivot (the "MediatR case").
- Maintenance signals — deprecated, archived or stale (last-commit) source repositories.
- Transitive graph + health score — an explainable score per package and per project.
- Vulnerability paths — for every vulnerable package, the chain that pulled it in (
root → A → B). - Typosquat warning — direct targets that look like a typo of a top package (
lodahs→lodash) are flagged (ADR 0025).
- Minimal safe upgrade — the smallest version that clears every advisory.
depradar fix— applies it: patch the manifest in place, or open a pull request — for.csproj/props,package.json,requirements.txt,Cargo.tomlandgo.mod(ADR 0021).- Transitive parent-bump — bumps a direct dependency to the smallest version whose whole graph is clean.
- Drift over time — every scan is snapshotted; surfaces what rotted since you last looked.
- Autonomous watchlist — re-scans tracked packages on a schedule (opt-in).
- Multi-channel alerts — Slack and/or GitHub issues, de-duplicated and auto-closing on recovery.
- Drift digest + health/drift badges + a
depradar.drift.openOpenTelemetry gauge.
- CLI + policy-as-code — one
dotnet tool; a committeddepradar.jsongates every ecosystem verb. - GitHub Action — a shift-left dependency gate that uploads SBOM + SARIF.
- CycloneDX 1.5 SBOM & SARIF 2.1.0 — standards-based export; findings land in the Security tab.
- Live dashboard (SignalR) with a live multi-ecosystem mode — npm/PyPI/Cargo/Go graphs render in-process via
/api/live/{ecosystem}/{package}, no persistence needed (ADR 0029). - Edge hardening — opt-in
X-API-Keygate on/api/*+ per-client rate limiting, both off by default (ADR 0018).
- Multi-ecosystem — scan npm, PyPI, Cargo and Go packages, whole manifests (
package.json,requirements.txt,Cargo.toml,go.mod) or lockfiles (package-lock.json,poetry.lock,uv.lock,Cargo.lock,go.sum) through the same Domain model (ADR 0020, ADR 0023, ADR 0024, ADR 0026). - One gate everywhere — a committed
depradar.jsonpolicy,--sbom/--sarif, exploit intelligence and typosquat warnings apply identically to all five ecosystems.
- LLM upgrade advisor — RAG over changelogs (pgvector) + a deterministic "ask the graph" chatbot. Prose comes from Claude when
ANTHROPIC_API_KEYis set, else a templated fallback. - Prompt-injection defense — changelogs are untrusted input;
PromptShieldfences them (ADR 0006).
Note
Retrieval ships with a keyless, deterministic hashing embedder so RAG runs out of the box. It approximates lexical overlap, not meaning — register a hosted embedding model behind IEmbeddingGenerator for production-grade semantic search.
# Scan a package or a whole project; write SBOM + SARIF for CI
depradar scan WindowsAzure.Storage --fail-on high --no-deprecated
depradar scan ./MyApp.csproj --forbid copyleft --sbom sbom.json --sarif results.sarif
# Compare two versions — added/removed deps + CVEs introduced or cleared
depradar diff Newtonsoft.Json 12.0.3 13.0.3
# Multi-ecosystem — npm and PyPI through the same risk model
depradar npm express "^4" # exact versions, ranges, or latest
depradar npm ./package.json --sarif results.sarif # scan a whole manifest
depradar npm ./package-lock.json # …or the lockfile: exactly what's installed
depradar pypi requests 2.19.1 --fail-on high
depradar pypi ./requirements.txt --sbom sbom.json # PEP 440 specifiers respected
depradar pypi ./poetry.lock # poetry.lock / uv.lock work too
depradar cargo regex "=1.5.4" # Cargo (Rust): crates, Cargo.toml, Cargo.lock
depradar go golang.org/x/text v0.3.7 # Go: modules, go.mod, go.sum
# Auto-fix vulnerable dependencies (incl. transitive, via parent-bump)
depradar fix ./MyApp.csproj --dry-run # preview the bumps
depradar fix ./MyApp.csproj # patch in place
depradar fix ./MyApp.csproj --open-pr --repo owner/name # open a PR (needs GITHUB_TOKEN)
depradar fix ./package.json --dry-run # npm: keeps your ^/~ operator
depradar fix ./Cargo.toml # Cargo: =/^/~ preserved · go.mod: exact vX.Y.Z
depradar fix ./requirements.txt # PyPI: rewrites == pinsExit codes: 0 policy passed · 1 policy violated · 2 usage error.
Keep the gate in the repo, not in CI flags. Every verb (scan, npm, pypi, cargo,
go) auto-detects depradar.json in the working directory (or pass --policy <path>);
it takes precedence over the flags:
The CLI ships as a composite GitHub Action. DepRadar dogfoods it —
dependency-health gates its own dependencies on every
push, uploads a CycloneDX SBOM artifact, and publishes a SARIF report to the Security tab.
- uses: AdrianDeutsch/DepRadar@v1
with:
target: src/MyApp/MyApp.csproj # a package id, manifest, or lockfile
ecosystem: nuget # nuget (default) | npm | pypi
fail-on: high # none | low | medium | high | critical
no-deprecated: true # nuget only
forbid: copyleft # comma-separated license categories (nuget only)
sbom: sbom.json # optional CycloneDX export
sarif: results.sarif # optional SARIF for code scanning
# The same gate for a JS or Python repo:
- uses: AdrianDeutsch/DepRadar@v1
with:
target: package-lock.json # or package.json / requirements.txt / poetry.lock / uv.lock
ecosystem: npm
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: results.sarifSet a few config values and DepRadar watches your dependencies for you:
// appsettings / environment / Aspire parameters
"Watch": { "IntervalHours": 24 }, // re-scan tracked packages daily
"Digest": { "IntervalHours": 24 }, // deliver the drift digest to Slack daily
"Retention": { "IntervalHours": 6, "MaxSnapshotsPerRoot": 50 }, // bound drift history (defaults shown)
"Alerts": {
"SlackWebhookUrl": "https://hooks.slack.com/services/…", // optional channel
"GitHubRepo": "owner/name" // optional channel (uses GitHub:Token)
}When a re-scan introduces a new high-severity issue, the alert fans out to every configured channel. GitHub alerts de-duplicate and auto-resolve: one stable issue per package — a repeat comments on the open issue, and a later clean scan closes it.

REST API reference (the dashboard at / drives all of these)
# Queue a transitive scan (202 + scan id), then poll status
curl -X POST http://localhost:8080/api/packages/Serilog.Sinks.Console/scan
curl http://localhost:8080/api/scans/<scan-id>
# Graph, per-package risk, and the project-level rollup (worst first)
curl http://localhost:8080/api/packages/Serilog.Sinks.Console/graph
curl http://localhost:8080/api/packages/Serilog.Sinks.Console/risk
curl http://localhost:8080/api/packages/Serilog.Sinks.Console/graph/risk
# Why is a transitive package vulnerable, and what's the safe upgrade?
curl http://localhost:8080/api/packages/WindowsAzure.Storage/vulnerability-paths
curl http://localhost:8080/api/packages/WindowsAzure.Storage/remediation
# "Is this upgrade worth it?" — RAG over changelogs + risk
curl "http://localhost:8080/api/packages/Serilog.Sinks.Console/upgrade?from=5.0.0&to=6.0.0"
# Whole-project scan, audit-ready report, SBOM, SARIF, chat
curl -X POST http://localhost:8080/api/projects/scan -H "Content-Type: text/plain" --data-binary @MyApp.csproj
curl http://localhost:8080/api/packages/Serilog.Sinks.Console/report
curl http://localhost:8080/api/packages/Serilog.Sinks.Console/sbom
curl http://localhost:8080/api/packages/WindowsAzure.Storage/sarif
curl -X POST http://localhost:8080/api/packages/Serilog.Sinks.Console/chat \
-H "Content-Type: application/json" -d '{"question":"which packages are unmaintained?"}'
# Version diff, drift, badges, and the cross-package digest
curl "http://localhost:8080/api/packages/Newtonsoft.Json/diff?from=12.0.3&to=13.0.3"
curl http://localhost:8080/api/packages/WindowsAzure.Storage/drift
curl http://localhost:8080/api/packages/Serilog.Sinks.Console/badge.svg
curl http://localhost:8080/api/drift/digestThe interactive API reference is served at /scalar/v1.
Clean Architecture with a strictly inward dependency direction, enforced in CI by
NetArchTest. Decisions are recorded as ADRs in
docs/adr.
The CLI host resolves only the network-backed ports (NuGet/OSV/GitHub) — never EF — so it runs the whole analysis with no database. Every feature is a thin composition over the same Domain model; the arrows only ever point inward.
Flow diagrams — scan pipeline · drift monitoring · auto-fix
Async, durable scan pipeline
sequenceDiagram
actor User
participant API as Web API
participant DB as PostgreSQL
participant W as Worker (Channels)
participant NG as NuGet API
User->>API: POST /api/packages/{id}/scan
API->>DB: INSERT scan (Queued)
API-->>User: 202 Accepted (Location /api/scans/{id})
loop poll every 2s
W->>DB: claim Queued scans into Channel
end
W->>NG: resolve transitive graph (ranges to versions, retry + breaker)
NG-->>W: registration + dependencies
W->>DB: upsert packages / versions / edges (idempotent), mark Completed
W->>DB: record risk snapshot (prune to newest N)
W->>W: diff vs previous snapshot — on new high-severity drift, alert Slack / GitHub
Autonomous drift monitoring (open on regression, close on recovery)
sequenceDiagram
participant W as Worker · watchlist
participant DB as PostgreSQL
participant EXT as OSV · NuGet · GitHub
participant CH as Slack · GitHub issue
loop every Watch:IntervalHours
W->>DB: re-queue every tracked package
end
W->>EXT: resolve + score the graph
W->>DB: record snapshot, load the two latest
alt new high-severity drift
W->>CH: alert — open or comment (de-duplicated)
else drift cleared
W->>CH: resolve — close the issue
end
Auto-fix (minimal safe upgrade, incl. transitive parent-bump)
flowchart LR
A["vulnerable direct dep<br/>(itself or a transitive)"] --> B{"smallest newer version<br/>with a clean graph?"}
B -- yes --> C["rewrite Version=<br/>(targeted text edit)"]
B -- no --> D["report: consider replacing<br/>e.g. a deprecated package"]
C --> E{"--open-pr ?"}
E -- yes --> F["branch + commit + PR<br/>via GitHub REST"]
E -- no --> G["patch the file in place"]
How it works — the scan lifecycle, step by step
POST …/scancreates aScanrow (Queued) and returns 202 — Postgres is the durable queue.- In the Worker,
System.Threading.Channelsdecouples a DB poller from a consumer that runs each scan through the hand-rolled mediator. - The resolver walks NuGet registration metadata breadth-first, resolving each version range to the concrete version NuGet would install, bounded by a node cap.
- Packages, versions and dependency edges are upserted idempotently — re-running a scan never duplicates rows.
- Each node is scored: OSV.dev for advisories, license + deprecation from the NuGet catalog, and the root's GitHub repo health. A pure
PackageRiskScorerturns these into explainable findings and an additive health score. - The graph (
recursive CTE), risk rollup, vulnerability paths and remediation are read back over the API. - The upgrade advisor embeds the query, retrieves changelog chunks from pgvector, builds a prompt-injection-shielded prompt, and returns a deterministic recommendation + an LLM (or templated) narrative.
- On completion the assessed graph is captured as an append-only
jsonbsnapshot. Comparing the two latest snapshots is drift — served at…/drift, digested at/api/drift/digest, and alerted to Slack/GitHub on new high-severity issues. An opt-in watchlist re-scans on a schedule. - The same Application core runs standalone in the CLI (no DB) to gate CI, and renders SBOM/SARIF/badges on demand.
Prompt-injection defense. Changelogs are attacker-controllable input. PromptShield
fences untrusted text in unique delimiters (stripping any occurrence of them), declares the
block to be data, never instructions, constrains the output, and delegates no authority
— the recommendation is computed deterministically; the LLM only writes the narrative. The
advisor works keyless (local embedder + templated narrative); set Anthropic:ApiKey
to enable the live Claude narrative (ADR 0006).
| Area | Technology | Purpose |
|---|---|---|
| Runtime | .NET 10 (LTS) / C# 14 | Long-term support; modern language features. |
| Web | ASP.NET Core Minimal API + SignalR | Thin HTTP surface; live scan progress to the dashboard. |
| Pipeline | Worker Service + System.Threading.Channels |
Ingestion, watchlist and scheduled digest, decoupled from the API. |
| Persistence | PostgreSQL + EF Core 10 | Flat tables + recursive CTEs; pgvector for RAG; jsonb snapshots. |
| CQRS | Hand-rolled mediator (MIT) | No commercially-licensed MediatR in the core (ADR 0002). |
| AI / RAG | pgvector + ILanguageModel seam (Claude) |
Keyless local embedder + RAG; Claude narrative behind a key (ADR 0006). |
| CLI | dotnet global tool (PackAsTool) |
Stateless scan + policy gate for CI; no server, no database (ADR 0009). |
| Caching | HybridCache (in-memory L1 + Redis L2) |
Keeps idempotent re-scans off the upstream API quota. |
| Interop | CycloneDX 1.5 · SARIF 2.1.0 · SVG badges | Standards-based export; findings in GitHub code scanning. |
| Alerts | Slack webhook · GitHub issues (composite) | Pluggable, multi-channel drift notifications (ADR 0012). |
| Orchestration | .NET Aspire 13 | Wires API + Worker + Postgres + Redis + telemetry. |
| Resilience | Microsoft.Extensions.Http.Resilience |
Retry, circuit breaker, timeout, rate limiter on every call. |
| Observability | OpenTelemetry (via Aspire) | Traces, metrics, logs — incl. a depradar.drift.open gauge. |
| Kind | Tooling | What it proves |
|---|---|---|
| Unit | xUnit v3 + Shouldly | SemVer/npm-range/PEP 440 precedence, risk scoring, drift, prompt-injection shield. |
| Architecture | NetArchTest | Layer boundaries hold; MediatR & NuGet.Versioning stay out of the core. |
| Integration | Testcontainers + real PostgreSQL/pgvector | Idempotent graph upserts, recursive-CTE closure, risk rollup, RAG, drift. |
| Resolver | Canned registry + OSV HTTP fixtures | npm/PyPI transitive BFS, range/PEP 440 matching, de-dup, CVE mapping — no network. |
CI collects coverage (floor-gated) and publishes keyless SLSA build provenance for the .nupkg (ADR 0019); verify a download with gh attestation verify <file>.nupkg -R AdrianDeutsch/DepRadar.
dotnet test # unit + architecture + integration (needs Docker)Quality gates: nullable reference types, TreatWarningsAsErrors,
AnalysisLevel=latest-recommended (with a few documented waivers), Central Package
Management, and a CI pipeline running build, format check and
all tests. Production hardening (ADR 0008): resilience on every external call, HybridCache
(+ Redis), EF Core migrations validated against pgvector on every test run, a stale-scan
reaper, custom OpenTelemetry, and a DB health check.
Note
Docker 29: the Testcontainers Ryuk reaper is incompatible with Docker 29; the test fixture disables it programmatically, so integration tests stay green.
DepRadar was built in six vertical slices, then extended well beyond them.
Development milestones (all shipped)
- Slice 1 — Skeleton: package → deps.dev → Postgres → API, with Aspire + tests.
- Slice 2 — Transitive graph: async durable scans, NuGet range resolution, Channels worker, recursive-CTE graph API.
- Slice 3 — Risk analysis: OSV security scan, license + license-shift, maintenance signals, explainable scoring.
- Slice 4 — LLM layer: changelog RAG over pgvector, upgrade advisor, an
ILanguageModelseam, prompt-injection defense. - Slice 5 — Presentation: dashboard, SignalR live progress, Markdown audit report.
- Slice 6 — Hardening: HybridCache, EF migrations, stale-scan reaper, OpenTelemetry, health check.
- Beyond: whole-project scan, CycloneDX SBOM, graph chatbot, upgrade-impact diff, the CLI + policy gate (ADR 0009), and drift history (ADR 0010).
- Autonomous monitoring: retention, watchlist, Slack alerts, health badge (ADR 0011).
- Multi-channel alerts & digest: GitHub-issue channel + cross-package digest (ADR 0012).
- Full drift lifecycle: auto-closing issues, retention job, drift badge, OpenTelemetry gauge.
- Explainable & exportable findings: vulnerability paths + SARIF 2.1.0 (ADR 0013).
- Remediation & auto-fix: minimal safe upgrade +
depradar fix/ PR (ADR 0014). - Multi-ecosystem: npm (ADR 0016) and PyPI (ADR 0017) support — the same Domain, a new adapter per registry.
- Production hardening: opt-in API-key gate + rate limiting (ADR 0018), CI coverage floor-gate + keyless build provenance (ADR 0019), HTTP-fixture resolver tests, and graph-truncation surfacing.
- Manifest scanning:
package.json/requirements.txtas first-class scan targets, range-aware root resolution, OSV fixed-version hints for every ecosystem (ADR 0020). - Multi-ecosystem auto-fix:
depradar fixbumps vulnerable npm ranges and PyPI==pins to the minimal clean version (ADR 0021). - Exploit intelligence: EPSS probabilities + CISA KEV escalate "has a CVE" into "is being exploited" — on every ecosystem and path (ADR 0022).
- Lockfile scanning:
package-lock.json/poetry.lock/uv.lockscanned as the exact installed set — the most precise target (ADR 0023). - Fourth ecosystem — Cargo (Rust): crates.io + RUSTSEC via OSV, yanked = deprecated,
Cargo.toml/Cargo.locktargets (ADR 0024). - Typosquat warning: Damerau-Levenshtein lookalike check of direct targets against curated top-package lists (ADR 0025).
- Fifth ecosystem — Go: module proxy + Go vulnerability DB via OSV; exact requires, no range grammar (ADR 0026).
Next up
- Self-contained CLI binaries (no .NET SDK needed — the npm/PyPI/Cargo/Go audience doesn't have one) (ADR 0028).
- Multi-ecosystem dashboard — live mode for npm/PyPI/Cargo/Go (ADR 0029).
- Multi-ecosystem drift monitoring (the persistence pipeline is NuGet-only today).
- deps.dev license enrichment for Go/Cargo · Maven as ecosystem #6.
Licensed under the MIT License.
Data sources: NuGet V3 API · npm registry · PyPI JSON API · crates.io API · Go module proxy · deps.dev · OSV.dev · GitHub Advisory Database · FIRST.org EPSS · CISA KEV · SPDX License List.







{ "failOn": "high", // none | low | medium | high | critical "allowDeprecated": false, "forbiddenLicenses": ["copyleft", "unknown"], "ignore": ["Some.Accepted.Package"] // accepted risk (VEX-style): shown, but not failing the gate }