Supply-chain security scanner for 16 package ecosystems and GitHub Actions workflows. No account, no API key, no backend.
- CVE / GHSA lookup — batch query against OSV.dev, 16 ecosystems in one shot; every finding carries
FixedIn(the smallest version that resolves it) - EPSS + KEV enrichment — every CVE gets the FIRST.org Exploit Prediction Scoring System probability;
[KEV]badge when the CVE is in CISA's Known Exploited Vulnerabilities catalog (actively exploited right now) - AST capability scan — tree-sitter walks every package source; surfaces
shell-spawn,net-egress,dynamic-eval,fs-write-outside-rootand more, even on packages with no advisory yet - Taint analysis — constant folding evaluates
String.fromCharCode([104,116,...])arrays to detect obfuscated C2 hostnames; variable taint tracking catchesatob(x) → eval(x)patterns that bypass simple pattern matching - Hardcoded secrets in dep source — detects AWS AKIA/ASIA keys, GitHub tokens, npm tokens, PEM private keys, Stripe / SendGrid / Twilio keys, Slack bot tokens, Bearer tokens ≥ 40 chars embedded in package source — no legitimate dep ships real credentials
- Behavior heuristics — postinstall hooks doing
curl|sh, obfuscated payloads, typosquat names (Levenshtein distance 2), maintainer hijack patterns, patch-version capability drift, git-SHA optional deps (worm propagation vector), unlisted large files (smuggled payloads), yanked-version detection - Symbol-level reachability — when OSV publishes
affected[].ecosystem_specific.functionsfor an advisory, aegis cross-references against the user code'sUsedSymbolsand suppresses advisories where the vulnerable function is provably never called - OpenVEX suppression —
aegis ci --vex project.vexloads a VEX document and clearsnot_affectedadvisories from verdict scoring (still shown greyed-out in output, omitted from JSON) - License policy gate —
aegis ci --deny-licenses GPL-3.0,AGPL-3.0or--allow-licenses MIT,Apache-2.0enforce SPDX policy; unknown license blocks under allow-list mode - Package health —
deprecatedflag fetched from deps.dev for npm / PyPI / Cargo / Go / Maven / NuGet;aegis ci --fail-on-deprecatedopt-in gate - Guided remediation —
aegis fixpicks the highestFixedInper dep (the smallest single upgrade that clears every resolvable CVE) and emits ecosystem-appropriate upgrade commands;--scriptpipes straight tosh - npm provenance — fetches SLSA attestations from the npm registry during
snapshot enrich; extracts source repo + git commit from SLSA v1 predicates; flags packages with no attestation as an informational risk signal - Transitive deps included — lockfile-based; every resolved package is scanned, not just direct deps
- Polyglot monorepo — finds all lockfiles, merges into a single
aegis.lock - CycloneDX + SPDX SBOMs —
aegis sbomemits CycloneDX 1.5/1.6 or SPDX 2.3 JSON;--include-vulnsattaches OSV advisories; package licenses populated from registries;--attestproduces in-toto attestations via cosign - GitHub Actions scanner —
aegis actions scanwalks.github/workflows/*.yml(or fetches from a remote repo with--repo owner/repo); flags unpinned action refs,pull_request_target+ checkout escalation, OIDC + npm publish worm vector,actions/cachepoisoning, script injection,curl|shinrun:blocks, andpermissions: write-all; outputs SARIF 2.1.0 with--sariffor GitHub Code Scanning - Offline capable —
AEGIS_NO_VULN_LOOKUP=1for air-gapped use; self-hosted OSV mirror viaAEGIS_OSV_URL
| Ecosystem | Lockfiles | OSV | AST scanner |
|---|---|---|---|
| npm | package-lock.json, pnpm-lock.yaml, yarn.lock, bun.lock |
✅ | ✅ js |
| PyPI | poetry.lock, uv.lock, Pipfile.lock, requirements.txt |
✅ | ✅ py |
| RubyGems | Gemfile.lock |
✅ | ✅ ruby |
| crates.io | Cargo.lock |
✅ | ✅ rust |
| Go | go.sum / go.mod |
✅ | ✅ golang |
| Maven | pom.xml, gradle.lockfile |
✅ | ✅ java |
| Packagist | composer.lock |
✅ | ✅ php |
| NuGet | packages.lock.json |
✅ | ✅ csharp |
| Hex | manifest.toml (Gleam), mix.lock (Elixir) |
✅ | ✅ gleam |
| Pub | pubspec.lock |
✅ | ✅ |
| SwiftURL | Package.resolved |
✅ | ✅ |
| CRAN | renv.lock |
✅ | ✅ |
| Hackage | cabal.project.freeze, stack.yaml.lock |
✅ | ✅ |
| CPAN | cpanfile.snapshot |
✅ | ✅ |
| CocoaPods | Podfile.lock |
✅ | ✅ |
go install github.com/qwexvf/aegis-cli/cmd/aegis@latestOne all-in-one binary — every PM wrapper and the AST scanner ship together. Requires Go 1.26+.
Pre-built linux/amd64 binary on Releases, cosign-signed with SLSA build provenance:
cosign verify-blob \
--certificate-identity-regexp 'https://github.com/qwexvf/aegis-cli/.github/workflows/release.yml.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
--certificate checksums.txt.pem \
--signature checksums.txt.sig \
checksums.txt
sha256sum -c checksums.txt# snapshot the lockfile and scan
aegis snapshot save # parse lockfile → aegis.lock
aegis snapshot enrich # AST scan + CVE lookup
aegis snapshot show # direct deps
aegis snapshot show --all # + transitive
aegis snapshot diff baseline.lock # drift between two snapshots
aegis snapshot rescan # re-query OSV for new CVEs on saved deps
# CI gate — exits 1 on findings ≥ threshold
aegis ci --fail-on=block
aegis ci --fail-on=prompt --json # machine-readable
aegis ci --deny-licenses=GPL-3.0,AGPL-3.0 # license policy
aegis ci --vex project.vex # suppress not_affected CVEs
aegis ci --fail-on-deprecated # treat deprecated deps as findings
# guided remediation — emit upgrade commands that clear every fixable CVE
aegis fix # human-readable plan
aegis fix --json # tooling integration
aegis fix --script | sh # apply directly
# analyze a package ad hoc (fetches from registry)
aegis analyze [email protected]
aegis analyze --evidence [email protected]
# analyze a local source tree (no registry fetch)
aegis analyze rubygems/[email protected] \
--local examples/incidents/rubygems/rest-client-1.6.13/
# SBOM export — CycloneDX or SPDX, with optional in-toto attestation
aegis sbom > sbom.json
aegis sbom --format=spdx -o sbom.spdx.json
aegis sbom --include-vulns --pretty -o sbom.json
aegis sbom --format=cyclonedx -o sbom.json --attest # in-toto via cosign
# allowlist
aegis allowlist add lodash \
--capability=dynamic-eval \
--version='^4' \
--reason='_.template uses Function() to compile templates'
aegis allowlist list
aegis allowlist test npm/[email protected]
aegis allowlist verify
# GitHub Actions workflow scanner
aegis actions scan # scan .github/workflows/ in cwd
aegis actions scan --fail-on=high # exit 1 on high/critical findings
aegis actions scan --json # machine-readable output
# shell completion
aegis completion bash > /etc/bash_completion.d/aegis
aegis completion zsh > "${fpath[1]}/_aegis"
aegis completion fish > ~/.config/fish/completions/aegis.fish- Parse — lockfile → every resolved
(name, version), direct and transitive - Fetch — tarballs from the registry; cached under
~/.aegis/cache/sources/ - AST scan — tree-sitter walks each file; emits
capability:file:line:snippetevidence - Heuristics — behavior-based detectors over the tarball and registry metadata (no network beyond step 2):
- install hooks doing
curl|sh,bun run <file> && exit 1, and similar download-execute patterns optionalDependenciespointing at git SHA commits (worm-propagation injection vector)- VCS dependencies (
git+https://,git = "...",:git =>) across PyPI, Cargo, RubyGems, Go, Composer, Gleam — bypasses registry immutability - unlisted large code files (≥512 KB not in
filesfield — smuggled payload shape) - confirmed-malware IOC filenames (
router_init.js,router_runtime.js,tanstack_runner.js) - yanked versions: lockfile pinning a version removed from the registry flags users who installed during an incident window
- maintainer hijack: publisher change between consecutive releases, fresh publish on abandoned package
- tarball drift: files in the published tarball absent from the upstream git tag (requires GitHub access)
- typosquat: name within Levenshtein distance 2 of a top-1000 package
- install hooks doing
- CVE lookup — batch POST to OSV.dev; severity cached under
~/.aegis/cache/advisories/ - Allowlist — builtin →
~/.aegis/allowlist.yaml→.aegis-allowlist.yaml; specific beats wildcard - Verdict —
max(ast, advisory)vs--fail-on; Critical/High →block, Medium →prompt, Low →review
# .aegis-allowlist.yaml — commit this for team-shared suppressions
version: 1
rules:
- ecosystem: npm
name: lodash
version: "^4"
capability: dynamic-eval
reason: "_.template uses Function() to compile templates"Three layers, in match order: builtin (~20 curated rules) → user (~/.aegis/allowlist.yaml) → project (.aegis-allowlist.yaml).
# Scan local workflows
aegis actions scan
# Scan a remote repository (uses $GITHUB_TOKEN)
aegis actions scan --repo owner/repo
# Emit SARIF for GitHub Code Scanning
aegis actions scan --sarif > results.sarifSuppress known-safe findings with .aegis-actions-allowlist.yaml:
version: 1
rules:
- kind: unpinned_ref
file: .github/workflows/release.yml
reason: "pinned via dependabot"
- kind: write_all_permissions
reason: "covered by network policy"Upload to GitHub Security tab:
- run: aegis actions scan --sarif > aegis.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: aegis.sarifDrop-in templates in examples/ci/ for GitHub Actions, GitLab CI, and generic shell.
| Exit code | Meaning |
|---|---|
0 |
clean — no findings ≥ --fail-on |
1 |
findings ≥ --fail-on |
2 |
verdict failed (config / network error) |
One-step full audit (packages + GitHub Actions workflows):
- name: aegis full audit
run: aegis ci --scan-actions --sarif > aegis.sarif
- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: aegis.sarif
if: always()| Flag | Description |
|---|---|
--scan-actions |
Also scan .github/workflows/ (same checks as aegis actions scan) |
--sarif |
Emit SARIF 2.1.0 for GitHub Code Scanning |
--fail-on |
Threshold: safe|review|prompt|block (default: block) |
--baseline |
Drift mode — only fail on newly-introduced findings |
aegis ci --json output is stable for tooling — see examples/ci/README.md.
Full docs: qwexvf.github.io/aegis-cli
See CONTRIBUTING.md. Open an issue before a non-trivial PR. Vulnerability reports: GitHub Private Vulnerability Reporting — not public issues. Maintainers cutting a release: RELEASING.md.
