Command reference
Every aegis subcommand, flag, exit code, and example. π marks commands that require the Aegis backend.
Every subcommand aegis --help lists, with flags, examples, exit codes, and output format. Authoritative as of the current main branch.
Legend: π marks commands that require a reachable Aegis API server (set via
AEGIS_API_URL). The hosted Aegis Cloud is not yet available and the platform repo is currently private β these commands are documented and shipped, but wonβt function for most local users today. Everything else works locally with no backend.
Global flags (apply to every subcommand):
| Flag | Description |
|---|---|
-v, --verbose | Enable debug-level structured logging to stderr (slog DEBUG). Same effect as AEGIS_VERBOSE=1. |
-h, --help | Show help for the current command. |
Common output flags (where applicable):
| Flag | Description |
|---|---|
--json | Emit a machine-readable JSON object to stdout. Suppresses human-readable output entirely. Stable schema β safe to parse. |
--quiet | Print only the summary line (no per-finding detail). Mutually compatible with --json. |
Exit codes (uniform across the binary):
| Code | Meaning |
|---|---|
0 | Success / no findings β₯ threshold |
1 | Failure / findings β₯ threshold (block, prompt, etc. depending on --fail-on) |
2 | Couldnβt reach a verdict β config error, network error, malformed input |
π aegis npm / aegis bun / aegis yarn / aegis pnpm#
Requires Aegis API. Drop-in wrappers around the underlying package manager. Install commands are intercepted, parsed, checked against the Aegis API, and either allowed (passed through to the real PM), prompted (interactive y/N), or blocked (non-zero exit, no PM call). All non-install commands pass straight through.
Without AEGIS_API_URL pointing at a reachable backend, install commands will return a connection error. Non-install passthrough (aegis npm test, aegis bun run dev) works regardless.
aegis npm install lodash@4.17.21 # checked
aegis npm test # passthrough
aegis bun add lodash@^4.17.0 # range β resolved via npm registry β checked
aegis yarn global add create-react-app
aegis pnpm add lodash
Install verbs recognized per PM:
| PM | Recognized | Notes |
|---|---|---|
npm | install, i, add, plus typo aliases | npm i is the same as npm install |
bun | install, i, add, a | Bunβs bun add foo is the canonical install |
yarn | add, install, global add | Yarn classic + berry; yarn global add is treated as install |
pnpm | add, install, i | Workspace-aware via the underlying pnpm |
Non-registry installs are detected and passed through without an API check (we have no version to check against):
./vendor/foo(local path)git+https://β¦(git URL)link:../sibling,workspace:*(workspace protocols)portal:./pkg,patch:foo,exec:node,npm:alias@1.0.0(yarn-berry protocols)
Interactive prompts use /dev/tty (so they work even when stdin is piped). In CI (CI=true, GITHUB_ACTIONS, etc.), prompts auto-block β never wait for input.
Override: pass AEGIS_OVERRIDE=allow and AEGIS_OVERRIDE_REASON='<text>' to bypass a block; both are written to the audit log. An empty reason is refused.
Exit codes: 0 if the install proceeds (allow / approved prompt / passthrough), 1 if anything was blocked.
aegis snapshot save#
Scan the projectβs lockfile(s) and write aegis.lock at the project root. No network calls β pure lockfile parse + serialise. Fast, deterministic, safe to commit.
aegis snapshot save # auto-detect lockfile(s)
Recognised lockfiles (every match in the project root is parsed; deps are merged in one snapshot keyed by ecosystem):
| Ecosystem | Lockfiles (priority order within the ecosystem) |
|---|---|
| npm | pnpm-lock.yaml β yarn.lock β bun.lock β package-lock.json |
| PyPI | poetry.lock β uv.lock β Pipfile.lock β requirements.txt |
| crates.io | Cargo.lock |
| Go | go.sum |
| RubyGems | Gemfile.lock |
Within an ecosystem, only the first match is parsed (a project typically commits to one tool). Across ecosystems, every match is parsed β polyglot monorepos with both package-lock.json and Cargo.lock produce a single aegis.lock containing both.
Output: writes ./aegis.lock (zstd-compressed JSON). Idempotent β repeated saves with no lockfile change produce a byte-identical file.
Exit codes: 0 on success, 2 if no lockfile is found.
aegis snapshot show#
Print the saved snapshot. By default only direct deps are shown (transitive deps are in the file but hidden from the table by default).
aegis snapshot show # direct only
aegis snapshot show --all # include transitive
aegis snapshot show --all --used-only # hide deps not referenced by project source
aegis snapshot show --json # full JSON output including reach + symbols
| Flag | Default | Description |
|---|---|---|
--all | off | Include transitive dependencies in the rendered table. |
--used-only | off | Hide deps whose reachability is unused β in the lockfile but not imported by project source. Requires a prior snapshot enrich run. Filtered count shown in footer. |
--json | off | Emit the full snapshot as a JSON array. Includes all fields including reach and symbols. |
Output columns:
| Column | Meaning |
|---|---|
ECO | Ecosystem (npm, pypi, crates, go, rubygems) |
NAME | Package name |
VERSION | Resolved version |
DIRECT | β when listed in the project manifest (vs transitive) |
CAPS | AST + heuristic capability count. Empty if not enriched yet; β if enriched with no findings. Appends [unused] when reachability scan found no import of this dep in project source. |
ADVISORIES | OSV.dev vulnerability count + max severity, color-coded. Empty if not looked up; β if looked up with no matches |
aegis snapshot diff [a.lock] [b.lock]#
Diff two snapshots. With no arguments, diffs the saved aegis.lock against the live lockfile. With one argument, diffs aegis.lock (saved) against the given path. With two, diffs the two files.
aegis snapshot diff # saved vs live
aegis snapshot diff baseline.lock # baseline vs current saved
aegis snapshot diff main.lock pr-branch.lock # explicit
Reports added, removed, upgraded, downgraded β and drift (a version-changed dep that grew new capabilities). Drift is the high-signal entry: lodash 4.17.20 β 4.17.21 is normal, but if 4.17.21 newly contains child-process itβs worth a look.
aegis snapshot enrich#
Run AST analysis + vulnerability lookup over every dep in the saved snapshot.
Two phases per run:
- AST scan (parallel, 8-worker pool): fetch the tarball from the registry (cached under
~/.aegis/cache/sources/), gunzip and untar in memory, walk the tree-sitter AST, and write capability fingerprints back intoaegis.lock. Per-dep cost: 100msβ2s for first scan, ~5ms cache hit on subsequent runs. - Vulnerability lookup (single batch POST to OSV.dev): every dep is cross-referenced against the public OSV vulnerability database. Returned advisories are stamped onto each
Dependencyand persisted inaegis.lock. Advisory bodies are cached under~/.aegis/cache/advisories/. No Aegis API required, no auth needed.
aegis snapshot enrich
Disable the vulnerability lookup with AEGIS_NO_VULN_LOOKUP=1 (AST scanning still runs). Point at a self-hosted OSV mirror with AEGIS_OSV_URL=β¦. Respects AEGIS_CACHE_DIR.
Live progress UI: when stderr is a TTY and AEGIS_NO_LIVE is unset, shows an 8-slot live status panel. Disabled in CI and when piped.
π aegis snapshot submit#
Requires Aegis API. Post analyzed deps as community reports to the Aegis API. Requires AEGIS_API_KEY β keys are issued via the Aegis web UI under /admin?tab=api-keys.
AEGIS_API_KEY=β¦ aegis snapshot submit
Submits one report per (ecosystem, name, version) tuple in the saved snapshot. The API deduplicates server-side; resubmitting doesnβt create duplicate records. Submit failures are logged but donβt fail the command β best-effort.
aegis snapshot verify#
Check that aegis.lock is loadable and matches the current schema. Used by CI to catch corrupted or out-of-date snapshot files before they trip enrich.
aegis snapshot verify
Exit codes: 0 if loadable, 2 if missing / malformed / schema-incompatible.
aegis ci#
One-stop CI command. Runs the full audit pipeline: snapshot save β snapshot enrich (AST scan + OSV vulnerability lookup; skippable with --no-enrich) β score β exit.
Scoring folds two signals: AST capability findings (suspicious code patterns) and known vulnerabilities from OSV.dev (CVE / GHSA). Verdict is max(astVerdict, advisoryVerdict):
| Source | Critical / High | Medium | Low | Info |
|---|---|---|---|---|
| Advisory severity | block | prompt | review | safe |
| AST score | (capability-weighted; see Risk engine) |
aegis ci --fail-on=block # default
aegis ci --fail-on=prompt # tighter
aegis ci --fail-on=review # tightest (warn-level fails)
aegis ci --json | jq '.findings[] | .name' # machine-readable
aegis ci --baseline=baseline.lock # drift mode
aegis ci --no-enrich # score on existing fingerprints only
aegis ci --scan-actions # also scan .github/workflows/
aegis ci --sarif > packages.sarif # SARIF 2.1.0 output
| Flag | Default | Description |
|---|---|---|
--fail-on | block | Threshold to fail on: safe (any finding) β review β prompt β block (only blocks) |
--json | off | Emit a JSON object to stdout. Suppresses human output. |
--quiet | off | Print only the summary line. |
--no-enrich | off | Skip the AST scan; score on existing fingerprints. Faster, thinner. |
--baseline <path> | (none) | Drift mode: diff against this saved snapshot, only fail on newly-introduced findings. Doesnβt touch your aegis.lock. |
--scan-actions | off | Also scan .github/workflows/. Threshold controlled by --actions-fail-on. Respects .aegis-actions-allowlist.yaml. |
--actions-fail-on | high | Minimum severity for --scan-actions: low|medium|high|critical |
--sarif | off | Emit SARIF 2.1.0. When combined with --scan-actions, both package and workflow findings are emitted as two runs[] in one SARIF file. |
--suggest | off | Print per-ecosystem upgrade commands for each blocked dep (npm install pkg@latest, pip install --upgrade pkg, etc.). |
The fingerprint cache (~/.aegis/cache/fingerprints/) persists across runs β a warm CI is fast. Only newly-added or version-changed deps incur AST scan cost.
Exit codes: 0 if no findings β₯ --fail-on, 1 if any, 2 on config / network errors.
GitHub Actions β full audit in one step:
- name: Audit dependencies + workflows
run: aegis ci --scan-actions --sarif > aegis.sarif
- name: Upload to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: aegis.sarif
if: always()
π aegis recheck#
Requires Aegis API. Re-run the install gate against the current lockfile. Useful after an incident DB update β packages allowed at install time may now be flagged.
aegis recheck # direct deps only
aegis recheck --all # include transitive
aegis recheck --json
| Flag | Default | Description |
|---|---|---|
--all | off | Include transitive deps (default: only direct, matching what the user explicitly installed) |
--fail-on-prompt | off | Exit non-zero on prompt verdicts (default: only block fails) |
--json | off | Emit JSON to stdout |
--quiet | off | Summary line only |
aegis analyze <pkg-spec>#
Fetch and AST-scan a single package β fallback when the incident DB has no record. Spec is [ecosystem/]name@version. Default ecosystem is npm. Recognised ecosystem prefixes: npm, pypi, rubygems, crates, go. The registry-fetch path supports npm today; the other ecosystems work through --local.
aegis analyze lodash@4.17.21
aegis analyze @solana/web3.js@1.95.4
aegis analyze npm/event-stream@3.3.6
aegis analyze --evidence ua-parser-js@0.7.29 # show file:line snippets
aegis analyze --json lodash@4.17.21
# --local skips the registry fetcher and reads source from the
# on-disk directory at <path>. Spec is still required as a label.
aegis analyze rubygems/rest-client@1.6.13 \
--local examples/incidents/rubygems/rest-client-1.6.13/
| Flag | Description |
|---|---|
--evidence | Include file:line snippets for each detected capability |
--json | Emit JSON to stdout (suppresses human output) |
--local <path> | Skip the registry fetcher and read package source from <path>. Useful for fixture-based testing and pre-publish self-checks. The spec (<eco>/<name>@<version>) is still required as a label. |
--ecosystem <eco> | Treat the positional argument as a directory for the given ecosystem instead of a <name>@<version> spec. Currently implemented for neovim β plugins have no registry and no manifest, so Name is derived from the directory basename and Version from the git HEAD SHA. |
--baseline <prior.json> | Compare the current scanβs capabilities against a previously-saved --json output. Exits 1 when new capabilities appear. Capability shrinkage is fine; capability growth is a regression. Plugin managers use this to detect βdid this update get worse?β on plugin SHA bumps. |
The --local mode runs the same AST + heuristics pipeline snapshot enrich does, so the capability set is identical for the same input. Real-world incident fixtures ship under examples/incidents/ (rubygems/, pypi/, npm/, crates/, go/) and are validated by tests/e2e/incidents.sh on every CI run.
Neovim plugin scanning#
# Scan a local Neovim plugin checkout (no registry, no manifest)
aegis analyze --ecosystem neovim ./packer.nvim
# Plugin-manager integration: cache the JSON baseline on install,
# re-scan on update, fail if new capabilities appeared.
aegis analyze --ecosystem neovim ./plugins/foo --json \
> ~/.cache/aegis/foo-<sha>.json
aegis analyze --ecosystem neovim ./plugins/foo \
--baseline ~/.cache/aegis/foo-<old-sha>.json
Coverage maps Lua AST β existing capabilities: os.execute / vim.fn.system β shell-spawn, loadstring / vim.api.nvim_exec β dynamic-eval, require("socket.http") / vim.uv.new_tcp β net-egress, os.getenv / vim.env.* β env-read, io.open / vim.fn.writefile β fs-write-outside-root, ffi.load / package.cpath = ... β install-hook-exec. Plugin spec build = "<shell>" strings feed the same matcher that flags curl | sh in npm scripts. No OSV ecosystem for Neovim exists; --enrich returns nothing β this path is static-only. See docs/neovim-plugin-manager-safety-spec.md for the full plugin-manager integration spec.
aegis explain <pkg-spec>#
Explain why a dep was flagged. Looks up the dep in the saved aegis.lock first (no network); falls back to a fresh fetch + AST scan if not present.
aegis explain lodash@4.17.21
aegis explain --snapshot-only ua-parser-js@0.7.29 # error if not in lock
aegis explain --json lodash@4.17.21
| Flag | Description |
|---|---|
--snapshot-only | Only consult saved aegis.lock; never fetch + rescan. Errors if the dep isnβt in the snapshot. |
--json | Emit JSON to stdout |
Each capability is rendered with a one-line description; allowlist suppression reasons are surfaced; evidence (file:line) is shown when the scan was fresh.
aegis allowlist#
Manage capability suppressions for specific packages. Layered: builtin β user (~/.aegis/allowlist.yaml) β project (./.aegis-allowlist.yaml). Specific names beat wildcards; within each layer input order decides ties.
aegis allowlist list#
aegis allowlist list # all rules from all sources
aegis allowlist list --source=builtin # filter by source
| Flag | Description |
|---|---|
--source | Filter by source: builtin / user / project |
aegis allowlist add <name>#
aegis allowlist add lodash \
--capability=dynamic-eval \
--version='^4' \
--reason='_.template uses Function() to compile templates'
| Flag | Default | Description |
|---|---|---|
--ecosystem | npm | npm / pypi / crates / go / maven |
--capability | (any) | Capability code to suppress; omit for βany capabilityβ |
--version | (any) | Semver range to scope the rule to; omit for βany versionβ |
--reason | (none) | Explanation; strongly recommended. The allowlist is an audit trail β empty reasons are worse than no rule. |
--scope | user | user (~/.aegis/allowlist.yaml) or project (./.aegis-allowlist.yaml) |
aegis allowlist remove <name>#
aegis allowlist remove lodash --capability=dynamic-eval
aegis allowlist remove lodash --scope=project
| Flag | Default | Description |
|---|---|---|
--ecosystem | npm | Required to disambiguate cross-ecosystem rules |
--capability | (all) | Narrow removal to a single capability; omit to remove all rules for the name |
--scope | user | user or project |
aegis allowlist test <ecosystem>/<name>@<version>#
Show which allowlist rules would suppress capabilities for a given (ecosystem, name, version) tuple. Doesnβt fetch or scan β pure rule evaluation.
aegis allowlist test npm/lodash@4.17.21
aegis allowlist verify#
Validate user and project allowlist YAML files. Strict decoding: unknown keys, unknown capabilities, and unsupported schema versions all error out.
aegis allowlist verify
π aegis allowlist sync#
Requires Aegis API. Fetch the org-level allowlist overlay from the Aegis API and cache it locally at ~/.aegis/cache/org-allowlist.yaml. Requires AEGIS_API_KEY.
AEGIS_API_KEY=β¦ aegis allowlist sync
aegis allowlist sync --force # ignore cache freshness
| Flag | Description |
|---|---|
--force | Bypass the cache freshness check and re-fetch unconditionally |
aegis cache#
aegis cache list#
List cached decisions (the ~/.aegis/cache/decisions.json map of (eco, name, version) β verdict). Useful for βwhy was this allowed?β debugging.
aegis cache list
aegis cache clear#
Delete the local decision cache. Pass --fingerprints to also delete the AST fingerprint cache (~/.aegis/cache/fingerprints/); pass --all for both plus the package source cache (~/.aegis/cache/sources/).
aegis cache clear # decisions only
aegis cache clear --fingerprints # + fingerprints
aegis cache clear --all # everything
| Flag | Description |
|---|---|
--fingerprints | Also delete AST fingerprint cache |
--all | Delete decisions + fingerprints + tarball sources |
aegis audit tail#
Show the most recent entries from the local audit log (~/.aegis/audit.jsonl). One line per outcome (allow / block / override / β¦) with timestamp, package, decision, and reason.
aegis audit tail # last 20
aegis audit tail -n 100 # last 100
aegis audit tail -n 0 # all
| Flag | Default | Description |
|---|---|---|
-n, --n | 20 | Show the last N entries; 0 means all |
aegis hook#
aegis hook install#
Install the aegis pre-commit hook in the current git project. The hook runs aegis ci --fail-on=block before each commit.
aegis hook install
Writes .git/hooks/pre-commit. Refuses to overwrite an existing hook unless you remove it first.
aegis hook uninstall#
Remove the aegis pre-commit hook. Idempotent.
aegis hook uninstall
aegis doctor#
Sanity-check the local environment: API reachability, cache permissions, allowlist parse, free disk. Run this first when something seems off.
aegis doctor
aegis doctor --json
| Flag | Description |
|---|---|
--json | Emit machine-readable JSON |
Checks performed:
- API reachability β
HEAD AEGIS_API_URL/check; reports the HTTP status (any code = reachable) - Cache directory β
~/.aegis/cache/is writeable - Allowlist parse β user + project YAML files load without errors
- Disk space β at least 100MB free on the cache filesystem
- Build info β Go version, OS/arch, build tags
Exit codes: 0 if everything green, 1 if any check failed, 2 if doctor itself crashed.
aegis actions scan#
Scan GitHub Actions workflows for supply-chain risks β locally or from a remote repository.
# Scan current project's .github/workflows/
aegis actions scan
# Scan a remote repository (uses $GITHUB_TOKEN automatically)
aegis actions scan --repo owner/repo
# Emit SARIF 2.1.0 for GitHub Code Scanning
aegis actions scan --sarif > results.sarif
Flags
| Flag | Default | Description |
|---|---|---|
--min-severity | high | Minimum severity to fail on: low|medium|high|critical |
--fail-on | β | Deprecated alias for --min-severity; prints a warning |
--json | false | JSON output |
--sarif | false | SARIF 2.1.0 output (GitHub Code Scanning / VS Code) |
--dir | cwd | Project root; ignored when --repo is set |
--repo | β | Remote GitHub repo (owner/repo) via GitHub Contents API |
--token | β | GitHub PAT; prefer $GITHUB_TOKEN (CLI args visible in process list) |
Exit codes: 0 clean, 1 findings β₯ --min-severity, 2 I/O error.
Detections
| Finding | Severity |
|---|---|
unpinned_ref | High / Medium |
pull_request_target_checkout | Critical |
write_all_permissions | High |
script_injection | Critical |
suspicious_run | High |
oidc_npm_publish | High |
cache_poisoning | High |
Allowlist β suppress findings via .aegis-actions-allowlist.yaml:
version: 1
rules:
- kind: unpinned_ref
file: .github/workflows/release.yml
reason: "managed by dependabot"
- kind: "*"
file: .github/workflows/legacy.yml
reason: "legacy workflow not in production path"
Suppressed findings are still shown in output but donβt trigger --fail-on.
Upload SARIF to GitHub Security tab
- run: aegis actions scan --sarif > aegis-actions.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: aegis-actions.sarif
if: always()
aegis image scan <image.tar>#
Scan a local OCI / Docker image tarball for supply-chain risks. Reads docker save output (or any OCI-format archive), overlays every layer (whiteout-aware), and extracts every dependency it can find via two complementary paths:
- Lockfiles baked into the image β
package-lock.json,Gemfile.lock,Pipfile.lock,composer.lock, etc. Parsed by the same locksnap registry the project-mode scanner uses - Per-package manifests that live outside any lockfile β
node_modules/<pkg>/package.json,*.dist-info/METADATA,*.egg-info/PKG-INFO,gems/<name>-<ver>/,vendor/<v>/<p>/composer.json, and/opt/<tool>-v<ver>/package.json(top-level npm tooling like yarn). Closes the recall gap on distroless and multi-stage images where the lockfile never lands in the final image
docker save my-app:latest -o my-app.tar
aegis image scan my-app.tar
aegis image scan my-app.tar --enrich
aegis image scan my-app.tar --capabilities
aegis image scan my-app.tar --no-manifest-walk # lockfile-only mode
aegis image scan my-app.tar --json | jq '.deps[].name'
Flags
| Flag | Default | Description |
|---|---|---|
--enrich | false | Run OSV.dev vulnerability lookup against the extracted dependency set |
--capabilities | false | AST-scan every package found inside the image (tree-sitter capability + heuristic detection β finds malware Trivy canβt) |
--no-manifest-walk | false | Skip per-package manifest scanning, return lockfile-derived deps only |
--json | false | Machine-readable JSON on stdout |
Provenance β every dep in the JSON output carries a source field: "lockfile" (parsed from a known lockfile), "manifest" (synthesized from a per-package manifest), or omitted for older snapshots. Use it to audit how a particular dep was discovered.
Recall β example numbers from public base images:
| Image | Lockfile-only | Manifest walker ON |
|---|---|---|
node:20-alpine | 0 | 193 npm |
ruby:3.3-alpine | 52 gems | 130 gems |
python:3.12-alpine | 0 | 1 pypi |
Combine --enrich with manifest walk to surface CVEs that lockfile-only scans miss entirely β e.g. cross-spawn ReDoS and glob command injection on the stock node:20-alpine image.
Memory β each captured manifest is capped at 64 KB and the total per-image manifest budget is 64 MB (β 32k packages). When the cap fires the result is partial and truncated: true lands in the JSON output.
Exit codes: 0 clean scan, 2 I/O error reading the tar.
aegis admin gen-key#
Generate a fresh submit API key plus a sha256 hex digest for installing it server-side. Used when bootstrapping a new operator account against a self-hosted Aegis API.
aegis admin gen-key
Output: two lines β the key (give to the user) and the sha256 hex (insert into the submit_api_keys table). The key itself is never stored server-side.
aegis version#
Print the binary version, commit hash, and build date.
$ aegis version
aegis 0.1.0 (commit 6c5844916d8831d841edb2fec1e9dbd615519e9c, built 2026-05-03T04:46:04Z)
All three values are stamped at build time via -ldflags=-X. A binary built locally with plain go build (no ldflags) reports 0.1.0-demo (commit none, built unknown).
aegis completion {bash|zsh|fish|powershell}#
Generate a shell completion script. Pipe to your shell or write to the canonical completions directory:
# Bash (current shell only)
source <(aegis completion bash)
# Bash (persistent, Linux)
aegis completion bash > /etc/bash_completion.d/aegis
# Zsh
aegis completion zsh > "${fpath[1]}/_aegis"
# Fish
aegis completion fish > ~/.config/fish/completions/aegis.fish
# PowerShell
aegis completion powershell | Out-String | Invoke-Expression
Completes subcommand names, flag names, and (where applicable) flag values. The package-manager wrappers (aegis npm, aegis bun, etc.) use DisableFlagParsing and pass argv through unchanged, so completion of their inner args is delegated to the underlying tool.