@elephantseed/auto-depender
v0.1.31
Published
Local offline polyglot dependency vulnerability scanner and auto-fixer (Java, npm, Rust, Python)
Downloads
2,438
Maintainers
Readme
Auto-Depender
Local-first polyglot dependency scanner & auto-fixer. Scans npm, Rust, Python, and Java projects for vulnerabilities using the OSV (Open Source Vulnerabilities) database — no LLM, no API keys, no cloud after initial download. Runs offline once cached.
At a Glance
| Feature | Detail |
|---------|--------|
| What it does | Finds vulnerable dependencies → proposes minimal version bumps → edits your manifest files |
| How it knows | Downloads GitHub's OSV advisory database (~few MB per ecosystem, gzipped) and keeps it cached locally |
| Where data lives | .auto-depender/db/osv/ in your current working directory — never leaves your machine |
| Network needed? | First scan downloads OSV cache (~7 day TTL). Each scan also queries the live OSV API for fresh advisories unless you pass --offline |
| LLM involved? | No. Fixed versions come directly from OSV advisory metadata |
How It Works
┌─────────────────────────────────────────────────────────────────────┐
│ SCAN FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Lockfile scanner │
│ │ │
│ ▼ │
│ Parse packages (pure TS) --> PackageRef[] │
│ │ │
│ ▼ │
│ Detect ecosystems (npm / pypi / maven / gradle) │
│ │ │
│ ▼ │
│ .auto-depender/db/osv/ │
│ cache miss --> download OSV zip from GCS (gzip) │
│ cache hit --> read local advisory JSON │
│ stale --> update-db │
│ │ │
│ ▼ │
│ AdvisoryEntry[] │
│ │ │
│ ▼ │
│ MATCH ENGINE (matcher.ts) │
│ Maven: compareMavenVersion + matchesMavenRange │
│ PyPI: comparePEP440 + matchesPEP440Range │
│ npm/Rust: semver ranges │
│ │ │
│ ▼ │
│ Finding[] sorted by severity │
│ │ │
│ ▼ │
│ REPORTER (reporter.ts) │
│ --format markdown | json │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ FIX FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Finding[] │
│ │ │
│ ▼ │
│ buildFixPlan() (fixer.ts) │
│ group by package, pick minimal fix version │
│ detectMavenRenames() — OSV + known group migrations │
│ │ │
│ ▼ │
│ FixPlan: fixable | unfixable | renames │
│ expandRenameBundle() — sibling coords in same migration cluster │
│ health warnings — mixed groups, risky managed constraints │
│ │ │
│ ▼ │
│ Gates: major (--allow-major) | rename (--allow-renames) │
│ --yolo: loop apply until clean (combine with gates as needed) │
│ dry-run shows plan; TTY prompts; CI skips risky │
│ │ │
│ ▼ │
│ MANIFEST EDITORS │
│ gradle-editor / pom-editor / npm-editor │
│ cargo-editor / python-editor │
│ backup .auto-depender-bak before edit │
└─────────────────────────────────────────────────────────────────────┘Install & Quick Start
| | |
|---|---|
| Package | @elephantseed/auto-depender |
| Commands | scan, fix, update-db |
Bins ship inside the package — not separate npm packages. Use bunx or npx with --package. Both require Bun installed — bins are #!/usr/bin/env bun TypeScript, not precompiled Node binaries.
bunx --package @elephantseed/auto-depender scan -- ./my-project(npx --package @elephantseed/auto-depender … works the same if Bun is on PATH.)
More examples (each line is a full command):
bunx --package @elephantseed/auto-depender scan -- ./my-project --ecosystem=npm
bunx --package @elephantseed/auto-depender scan -- ./my-project --format=json
bunx --package @elephantseed/auto-depender fix -- ./my-project --dry-run
bunx --package @elephantseed/auto-depender fix -- ./my-project --apply
bunx --package @elephantseed/auto-depender fix -- ./my-project --yolo --allow-renames
bunx --package @elephantseed/auto-depender fix -- ./my-project --apply --allow-major --allow-renames --allow-source-migrations
bunx --package @elephantseed/auto-depender update-dbWindows:
bunx --package @elephantseed/auto-depender scan -- C:\dev\code\my-app
bunx --package @elephantseed/auto-depender fix -- C:\dev\code\my-app --dry-run
bunx --package @elephantseed/auto-depender fix -- C:\dev\code\my-app --yolo --allow-renamesLocal install (optional):
bun add -d @elephantseed/auto-depender{
"scripts": {
"security:scan": "scan",
"security:fix": "fix",
"security:update-db": "update-db"
}
}bun run security:scan -- .
bun run security:fix -- . --dry-runfix --dry-run shows planned bumps/renames. fix --apply backs up manifests (.auto-depender-bak), edits files, re-scans; restores backup on failure. Fix re-scans use runtimeClasspath only (not test deps) to keep yolo loops faster — use scan for full runtime+test coverage.
fix --yolo implies --apply and loops re-scan/apply for same-package bumps until clean or stalled (default cap: 10 passes). Majors, coordinate renames, and source migrations still need --allow-major, --allow-renames, and --allow-source-migrations when dry-run shows them. On pass 2+, YOLO does not re-prompt — flags only. Exits non-zero if vulnerabilities remain.
npm / Rust: re-scans overlay direct deps from package.json / Cargo.toml onto the lockfile tree so bump loops progress without npm install. Transitive-only fixes still need a lock refresh after.
After large coordinate migrations, later passes may pin transitive deps via managed constraints. Results are deterministic for a fixed OSV cache and resolved dependency tree; live OSV API refreshes or Gradle re-resolve can surface new findings mid-run.
YOLO is still wet behind the ears — mixed classpaths, coordinate migrations, and Gradle managed blocks can still stall. Stall messages now say if --allow-major or --allow-renames blocked progress. Prefer --dry-run first; run your build/test suite after.
Coordinate migrations
Some CVE fixes move to new Maven coordinates (new group:artifact), not just a higher version. When OSV lists both a same-major patch (e.g. Jackson 2.21.4 → 2.21.5) and a coordinate migration (Jackson 3), fix prefers the patch unless you pass --allow-renames.
| Ecosystem | Manifest rename | Source rewrite |
|-----------|-----------------|----------------|
| Maven / Gradle | Yes (--allow-renames) | Java import prefixes (--allow-source-migrations) |
| npm | Yes when OSV lists rename | JS/TS import/require strings |
| pypi | Yes when OSV lists rename | Python import/from lines |
| crates.io | Yes when OSV lists rename | Rust use statements |
- Scan warns when old and new groups from the same migration both appear on the classpath.
- Fix can rename coords (
--allow-renames), bundles siblings in the same migration cluster, and prints health warnings (mixed groups, managed-block excludes, platform pins that should be plugin/BOM bumps). - Jackson 3 (
com.fasterxml.jackson.core→tools.jackson.core): fix auto-adds global excludes for legacycom.fasterxml.jackson.coreartifacts and pinstools.jacksonconstraints for transitives. - Source migrations (opt-in):
--allow-renamesupdates dependency declarations only.--allow-source-migrationsapplies known source rewrite rules from migration clusters (Java import prefixes for Jackson today). Dry-run shows Planned source migrations. Does not migrate full API breaking changes — only known import paths. - Jackson annotations: Jackson 3 still uses
com.fasterxml.jackson.core:jackson-annotations2.x on the classpath for@Json*— the tool does not exclude or rename that artifact. - Scan clean ≠ ship ready — after coordinate migrations, health warnings may still mention mixed groups or managed excludes. Always run
gradlew build/mvn verifyand your test suite before merge; scan only reflects the resolved tree at scan time.
--allow-java-imports is deprecated; use --allow-source-migrations.
Example dry-run output (Jackson is one real migration; others work the same way):
# Auto-Depender Gradle Fix Report
## Planned renames (1)
| From | To | Version | Advisories |
|------|----|---------|------------|
| com.fasterxml.jackson.core:jackson-databind | tools.jackson.core:jackson-databind | 3.1.4 | GHSA-5jmj-h7xm-6q6v |
## Health warnings (2)
- Mixed Maven coordinates during migration: com.fasterxml.jackson.core and tools.jackson.core both on classpath — align related artifacts.
- org.springframework.boot:spring-boot bump to 3.5.14 will likely become a managed constraint — bump platform plugin/BOM instead when possible.CLI Reference
Prefix: bunx --package @elephantseed/auto-depender (or npx --package …). Examples below show <command> only.
scan
Scan a project for dependency vulnerabilities.
scan [path] [options]| Option | Default | Description |
|--------|---------|-------------|
| [path] | . | Directory containing lockfiles |
| --format=markdown\|json | markdown | Output format |
| --ecosystem=<name> | (all) | Filter: npm, crates.io, pypi, maven, gradle (Gradle = deps from build.gradle via gradlew) |
Gradle scan scope: runtimeClasspath + testRuntimeClasspath via gradlew dependencies (285 pkgs typical Spring app). Not scanned: Gradle plugin classpath, custom configs (e.g. jaxb in applied scripts), annotationProcessor deps. fix --yolo uses runtimeClasspath only (215 pkgs) — use plain scan for full coverage.
| --offline | false | Skip live OSV API; use cached advisory DB only |
Exit codes:
0— Clean (no vulnerabilities found)1— Vulnerabilities detected2— Error (invalid path, network failure)
Example outputs:
Markdown (default):
Scanning: /home/user/my-project
Ecosystems: NPM, MAVEN
Total packages: 147
Matching against 8,432 advisory entries...
🚨 5 vulnerabilities found
CRITICAL (2)
┌───────────────────────────┬───────────┬─────────┬──────────────────────────┐
│ Package │ Version │ Severity│ Summary │
├───────────────────────────┼───────────┼─────────┼──────────────────────────┤
│ com.google.guava:guava │ 31.1.0 │ Critical│ Multiple CVEs in Guava │
│ │ │ │ Fixed: 32.0, 33.0 │
│ [email protected] │ 2.28.0 │ Critical│ CVE-202X-XXXXX │
│ │ │ │ Fixed: 2.31.0, 2.32.0 │
└───────────────────────────┴───────────┴─────────┴──────────────────────────┘
HIGH (2)
┌───────────────────────────┬───────────┬─────────┬──────────────────────────┐
│ Package │ Version │ Severity│ Summary │
├───────────────────────────┼───────────┼─────────┼──────────────────────────┤
│ org.apache.commons:commons│ 3.14.0 │ High │ Integer overflow │
│ -lang │ │ │ Fixed: 3.15.0 │
│ [email protected] │ 1.34.0 │ High │ Race condition in timer │
│ │ │ │ Fixed: 1.35.0 │
└───────────────────────────┴───────────┴─────────┴──────────────────────────┘JSON (--format=json):
[
{
"id": "GHSA-xxxx",
"packageName": "com.google.guava:guava",
"installedVersion": "31.1.0",
"fixedVersions": ["32.0.0", "33.0.0"],
"severity": "CRITICAL",
"summary": "Multiple CVEs in Guava",
"aliases": ["CVE-2024-1234"]
}
]fix
Propose and apply version bumps to fix vulnerabilities.
fix [path] [options]| Option | Default | Description |
|--------|---------|-------------|
| [path] | . | Project directory |
| --dry-run | false | Show plan without modifying files |
| --apply | false | Write changes to manifest files |
| --yolo | false | Implies --apply; bump loop until clean or stalled (--max-iterations default 10). Exits 1 if not clean. Experimental |
| --allow-major | false | Skip interactive prompts for major bumps |
| --allow-renames | false | Skip prompts for Maven coordinate renames (group:artifact migrations from OSV / known group moves) |
| --allow-source-migrations | false | Apply known source rewrites for applied rename clusters (Java, JS/TS, Python, Rust) |
| --ecosystem=<name> | (all) | Limit to one ecosystem |
| --max-iterations=N | 5 (10 with --yolo) | Safety cap on apply re-scan loops |
| --exclude=group:pkg | (none) | Packages to skip (repeatable) |
| --offline | false | Skip live OSV API during re-scans |
| --format=markdown\|json | markdown | Output format |
Safety guarantees:
- Backup created before any edit (
.auto-depender-bak; skipped if backup already exists for that file) - Restored automatically on apply failure or uncaught error
- Backup discarded after successful full resolution
- Dry-run mode does not write manifests
CI note: non-TTY environments skip interactive prompts. Use fix --apply (or --yolo) explicitly — fix alone in CI only prints the plan and exits.
update-db
Download or refresh the OSV advisory databases.
update-db [--ecosystem=<name>]| Option | Default | Description |
|--------|---------|-------------|
| --ecosystem=<name> | (all) | Refresh only one ecosystem's DB |
What happens:
- Checks local
.auto-depender/db/osv/for existing caches - For each missing/expired cache: downloads from GitHub's advisory-database
- Stores as gzip-compressed JSON (~few MB per ecosystem)
- Falls back to cached data if download fails (never breaks your workflow)
Supported Ecosystems
| Ecosystem | Scanned Lockfile(s) | Auto-Fix Manifest(s) | Transitive Resolution |
|-----------|---------------------|----------------------|----------------------|
| npm | package-lock.json (v1/v2/v3) | package.json | Built-in (lockfile has full tree) |
| Rust | Cargo.lock | Cargo.toml | Built-in (lockfile has full tree) |
| Python | requirements.txt, poetry.lock | requirements.txt, pyproject.toml | Built-in (lockfile has full tree) |
| Java (Maven) | pom.xml | pom.xml | Via mvn dependency:tree (requires mvn/mvnw on PATH) |
| Java (Gradle) | build.gradle | build.gradle | Via gradlew dependencies (requires gradlew present) |
Not supported yet: build.gradle.kts, libs.versions.toml, Gradle version catalogs. Use build.gradle or pom.xml workflows.
Deduplication rules
When multiple manifests exist in the same directory:
poetry.lock+requirements.txt→ poetry.lock wins (authoritative)pom.xml+build.gradle→ pom.xml wins (more structured)- Different ecosystems → all scanned independently
Programmatic API
Use auto-depender as a library in your own tools:
import {
runAutoDependerScan,
buildFixPlan,
reportMarkdown,
} from "@elephantseed/auto-depender";
// Scan
const result = await runAutoDependerScan("/path/to/project");
console.log(`Found ${result.findings.length} vulnerabilities`);
console.log(reportMarkdown(result.findings));
// Build fix plan
const plan = buildFixPlan(result.findings);
plan.fixable.forEach(c => {
console.log(
`${c.packageName}: ${c.installedVersion} → ${c.targetVersion}`
);
});
// Filter by ecosystem
const npmOnly = await runAutoDependerScan("/path/to/project", {
ecosystemFilter: "npm",
});Available exports
| Module | Exports |
|--------|---------|
| scan | runAutoDependerScan(), loadAdvisoriesForEcosystem(), readCacheFile() |
| fixer | buildFixPlan(findings[]) → { fixable[], unfixable[] } |
| matcher | matchVulnerabilities(packages[], advisories[]) → Finding[] |
| reporter | reportMarkdown(), reportJson(), reportSummary() |
| editors | applyNpmBumps(), applyCargoBumps(), applyMavenBumps(), etc. |
| types | Finding, PackageRef, FixCandidate, FixPlan |
Cache Architecture
.auto-depender/
└── db/
└── osv/
├── NPM.json.gz ← ~2MB compressed
├── CRATES.IO.json.gz ← ~1.5MB compressed
├── PyPI.json.gz ← ~1MB compressed
└── Maven.json.gz ← ~3MB compressed- Location: Always under current working directory
- Format: Gzip-compressed JSON arrays of OSV advisory entries
- TTL: 7 days (configurable via
CACHE_TTL_MSconstant) - Fallback strategy: Stale cache > no cache. If download fails, uses whatever is cached
- Disk usage: ~8MB total across all four ecosystems
CI Integration
The tool returns non-zero exit codes on findings, making it ideal for CI gates:
# GitHub Actions — scan gate (needs Bun)
- uses: oven-sh/setup-bun@v2
- name: Check dependencies
run: bunx --package @elephantseed/auto-depender scan -- . --format=json
# Apply fixes in CI (non-TTY — must pass --apply)
- name: Apply security fixes
run: bunx --package @elephantseed/auto-depender fix -- . --apply --allow-majorOr pipe JSON into custom logic:
bunx --package @elephantseed/auto-depender scan -- . --format=json | \
jq '[.[] | select(.severity == "CRITICAL")] | length'Security Model
- No telemetry — nothing is sent from your machine
- No LLM — fixed versions come directly from OSV metadata
- No credentials — reads from the public GitHub Advisory Database
- Backups first — every edit creates a
.auto-depender-bak(legacy.depender-bakstill restored) - Deterministic — given the same lockfile + advisory DB, output is identical
FAQ
Q: bunx scan returns 404
A: The published package is @elephantseed/auto-depender. Use bunx --package @elephantseed/auto-depender scan -- <path> (see Install & Quick Start).
Q: Why Bun?
A: Bun's native TypeScript execution means zero compilation step. The package ships .ts source directly (no bundling). It also gives us access to Bun's fast fetch and fs APIs out of the box.
Q: Can I use this with Node.js?
A: Install Bun. Bins are Bun TypeScript scripts — npx/bunx invoke them via Bun, not plain Node.
Q: How does this differ from Dependabot / Renovate?
A: Those are PR-driven bots that live in your repo. This is a local CLI/library you run on demand — useful for pre-commit checks, CI gates, or ad-hoc auditing. No account setup needed.
Q: What about transitive dependencies?
A: npm's package-lock.json already contains the full resolved tree. Rust's Cargo.lock too. Java needs mvn/gradlew installed to resolve transitively; otherwise falls back to direct deps only.
Q: My project uses both Maven and Gradle — which wins?
A: pom.xml takes priority since it's more structured. Use --ecosystem=gradle to force Gradle-only scanning.
Development
git clone https://github.com/elephantseed/auto-depender.git
cd auto-depender
bun install
# Typecheck
bun run typecheck
# Run tests
bun test
# Manual CLI testing
bun run scan -- /path/to/project
bun run fix -- /path/to/project --dry-runLicense
MIT © Elephant Seed
