Skip to content

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

airsec

State-of-the-art security review, driven by the coding CLI you already have.

Claude Code · Codex · OpenCode · Command Code

ci license: MIT node

No new API key. No cloud upload. No third-party scanner. airsec drives the agent already installed on your machine through a structured, multi-pass security audit, then turns the result into a report you can act on.


Quick start

npx airsec@latest init

That is the whole thing. init walks you through it: pick the folder, pick your coding CLI, pick the model, choose review-or-fix, and it runs.

Other ways to install
npm  install -g airsec      # global
pnpm add    -g airsec
bun  add    -g airsec
yarn global add airsec

airsec init                 # then run it anywhere

No curl | sh installer, deliberately: a tool that audits your code for supply-chain risk should not ask you to pipe a URL into a shell.

Getting ETARGET · No matching version found?

Your npm is pointed at a mirror that has not synced the release yet. Check with npm config get registry — if it is anything other than https://registry.npmjs.org, either wait for the mirror to catch up or bypass it:

npx --registry=https://registry.npmjs.org airsec@latest init   # one-off
npm config set registry https://registry.npmjs.org             # permanently

Requirements

  • Node ≥ 20 (the published bundle has zero runtime dependencies)
  • At least one supported coding CLI on your PATH — check with npx airsec harness

60 seconds, end to end

npx airsec harness            # 1. what coding CLIs can airsec see?
npx airsec@latest init        # 2. guided setup — writes .airsec/config.json, then runs
npx airsec report --format html > report.html   # 3. share the result
npx airsec fix --severity high                  # 4. remediate the worst of it

Why this instead of a scanner

Pattern scanners find what patterns catch. An agent that reads your code can follow a tainted value through three helpers and a middleware layer and tell you whether the sink is actually reachable. airsec supplies the missing part: a rigorous process around that agent.

  • Reconnaissance first. The attack surface is mapped — entrypoints, trust boundaries, sensitive assets — before any vulnerability hunting starts, and that map is fed into every later pass.
  • Fourteen focused audit modules instead of one "find bugs" prompt, so injection analysis is not competing for attention with IaC review.
  • Multiple search strategies per module. Forward taint analysis, reverse sink analysis, goal-oriented attack, and invariant enforcement each find things the others miss.
  • Adversarial verification. Every finding is handed to independent reviewers whose job is to refute it. Findings that lose the vote are dropped, and you see how many were.
  • Deterministic scoring and merge. Deduplication, severity weighting, and the aggregate risk score are plain code, not another model call.

Supported coding CLIs

Run airsec harness to see which of these are installed and what models each offers.

Harness id Install
Claude Code claude npm i -g @anthropic-ai/claude-code
Codex codex npm i -g @openai/codex
OpenCode opencode npm i -g opencode-ai
Command Code commandcode npm i -g command-code
Gemini CLI gemini npm i -g @google/gemini-cli — experimental adapter
Cursor Agent cursor curl https://cursor.com/install -fsS | bash — experimental adapter
Crush crush npm i -g @charmland/crush — experimental adapter

The first four are built against headless flags verified against the real CLIs, and each is tested end to end in both directions — audit (read-only) and fix (write). The ones marked experimental are best-effort: they are overridable from config without touching the source.

Models are discovered, not hardcoded

Model ids change every few months. airsec never ships a versioned id that can rot into a wrong default, so airsec harness --models <id> asks your machine instead:

Harness Where the list comes from
OpenCode opencode models — the live list, hundreds of entries
Command Code cmd --list-models — the live list with descriptions
Claude Code your ~/.claude/settings.json model, plus the opus/sonnet/haiku aliases, which resolve to the current model by design
Codex the model in your ~/.codex/config.toml, including profiles

A probe command is only ever one verified to list models. Guessing is unsafe: several of these CLIs treat an unrecognised subcommand as a prompt, so a wrong guess would bill you for a model call instead of printing a list. Where no list exists, the wizard offers "harness default" plus free-text entry — always correct, never stale.

Usage

Guided

npx airsec init          # asks: which folder, which CLI, which model, review or fix
npx airsec init ../api   # start on a specific path

The wizard writes .airsec/config.json, so every later run is a bare airsec scan.

Non-interactive

airsec scan                                      # uses .airsec/config.json, or sensible defaults
airsec scan ../api --depth paranoid
airsec scan --harness codex --model gpt-5.1-codex-max
airsec scan --modules injection,authn-authz,secrets --min-severity medium
airsec scan --quiet --reports sarif --fail-on high --require-complete   # CI shape

Exit codes only matter when something automated is watching — a CI pipeline decides pass or fail from this number. Running airsec yourself, you can ignore them; the wizard does not ask about them for that reason.

Exit Meaning
0 No finding at or above --fail-on
1 airsec itself failed — bad config, harness missing, unreadable target
2 A finding at or above --fail-on (default high) survived verification
3 An audit pass did not finish, and --require-complete was set

--fail-on sets the bar: --fail-on critical fails a build only on critical findings, --fail-on none never fails it. It changes nothing about the scan or the report.

Coverage can be partial, and that is not the same as clean. If a harness invocation times out or errors, airsec records it, finishes the remaining passes, and reports the run — but the areas that pass covered were never reviewed. Both the terminal output and report.md lead with a warning when this happens, summary.incompletePasses carries it in JSON, and --require-complete turns it into a build failure. Never read a partial run as a clean bill of health.

Parallel sessions of the same coding CLI contend for one rate limit, and a stalled session burns the full --timeout (default 900s) before giving up. Concurrency 5 against Claude Code lost 5 of 14 passes to timeouts in testing; the default of 3 is deliberately conservative. If you see timeouts, lower --concurrency before raising --timeout.

Fixing

airsec fix                        # pick findings from the last review interactively
airsec fix --all --severity high  # remediate everything high and above

Fixes are applied to your working tree, one finding at a time — sequentially on purpose, because parallel agents editing overlapping files produce conflicting writes. Review the diff before you commit; airsec never commits for you.

Each fix is scoped to one finding, so a change that alters a function's contract can leave a caller in another file out of step. The remediation prompt tells the agent to follow the change across file boundaries, but weaker models do not always manage it — run your tests after fix, always.

Reports

Every run is stored under .airsec/runs/<timestamp>-<id>/ with the formats you configured.

airsec report                        # re-print the last review
airsec report --format html > out.html
airsec report --format sarif > out.sarif

SARIF uploads straight to GitHub code scanning.

Depth

Depth Lenses per module Verifiers per finding File budget
quick 1 0 600
standard 1 1 2,000
deep (default) 2 2 6,000
paranoid 3 3 20,000

Cost scales roughly with modules × lenses, plus one verification call per finding per verifier. Start at quick on a new codebase to calibrate before committing to paranoid.

Audit modules

airsec modules prints these with their CWE and OWASP mappings.

Core (default): injection · authn-authz · secrets · crypto · input-validation · ssrf-network · xss-frontend · path-file · supply-chain · infra-config

Extended (opt in): api-surface · logic-race · llm-agentic · privacy-compliance

Configuration

.airsec/config.json:

{
  "version": 1,
  "target": "/abs/path/to/repo",
  "harness": "claude",
  "model": "opus",
  "depth": "deep",
  "modules": ["injection", "authn-authz", "secrets"],
  "concurrency": 3,
  "timeoutMs": 900000,
  "retries": 1,
  "verify": true,
  "minSeverity": "low",
  "exclude": ["fixtures/", "generated/"],
  "reports": ["markdown", "json", "sarif"],
  "failOn": "high",
  "harnesses": {}
}

exclude entries are case-insensitive substring matches against each path relative to the target — not globs. "generated/" drops the directory; ".test." drops test files anywhere.

Overriding a harness

When a CLI changes its headless flags, fix it in config rather than waiting for a release. Tokens {{prompt}}, {{system}}, {{model}} and {{cwd}} are substituted per invocation; an argument whose token has no value is dropped along with its flag.

{
  "harnesses": {
    "gemini": {
      "bin": "gemini",
      "promptVia": "stdin",
      "args": {
        "read": ["--model", "{{model}}", "--output-format", "text"],
        "write": ["--model", "{{model}}", "--yolo"]
      }
    }
  }
}

How a run works

inventory   walk the tree, detect stack, rank security-relevant paths   (deterministic)
    ↓
recon       one pass: entrypoints, trust boundaries, sensitive assets   (1 call)
    ↓
audit       modules × lenses, run concurrently                          (m × l calls)
    ↓
merge       fingerprint by location, corroborate by title overlap       (deterministic)
    ↓
verify      independent reviewers try to refute each finding            (f × v calls)
    ↓
score       severity × confidence × verdict × corroboration             (deterministic)
    ↓
report      terminal · markdown · json · sarif · html

Development

bun install
bun run dev -- scan ./fixture --depth quick   # run from source
bun run check                                 # lint + typecheck + test, what CI runs
bun run format                                # apply Biome fixes
bun run build                                 # bundle to dist/cli.js, node-compatible

Bun is the toolchain; the published bundle targets Node ≥ 20 so npx airsec works anywhere. Biome enforces formatting and lint, tsc --strict enforces types, and CI runs all three plus the packaged binary on every push.

Extending it. A new harness is one file in src/harness/adapters/ plus a line in registry.ts — or zero code, via a config override. A new audit module is one entry in src/audit/catalog.ts. A new report format is one entry in src/report/index.ts.

src/
  audit/      what to look for — module catalog, search lenses, prompts, output schema
  core/       the pipeline — inventory, recon, fan-out, merge, verify, score, trust boundary
  harness/    how to talk to each coding CLI — adapters, process exec, JSON extraction
  report/     terminal · markdown · json · sarif · html
  ui/         wizard prompts, progress, banner
  util/       fs, json recovery, hashing, redaction, logging

Security model

airsec is pointed at code you may not trust, so it treats the target directory as hostile input.

Config from the target is not trusted to decide what runs. .airsec/config.json lives inside the directory under review. Its harnesses block names an executable and its argv, so honouring it from a repository you just cloned would be remote code execution. That block is ignored by default, with a warning, and applied only when you pass --trust-config on the command line — somewhere the repository cannot reach. Everything else in the file only decides what gets read, and is applied normally.

Audit passes cannot execute commands or reach the network. The repository under review is attacker-controlled input to a model, so injected instructions in a README or a code comment must not be able to reach a shell (remote code execution) or the network (exfiltration of the source being reviewed). Reconnaissance, auditing and verification run with only the tools an audit needs — open a file, search, list.

For Claude Code that means Bash, WebFetch, WebSearch and Task are denied alongside the editing tools; Task because a subagent could otherwise be spawned with the tools just removed. Codex runs under --sandbox read-only, OpenCode under its plan agent, Command Code under --permission-mode plan. Write access is granted only by airsec fix, and even then network tools stay denied.

This is verified rather than assumed: a fixture repository carrying prompt-injection text in its README and source is scanned as part of testing, and the injected command does not run.

Discovered credentials are masked in stored output. A secrets finding would otherwise copy a live credential into report.md, report.json and report.sarif — files that get committed and pasted into tickets. High-entropy tokens are masked to sk_l…redacted…4c across every text field of a finding, not just the code snippet: models routinely spell the same credential out again in the exploit narrative as a URL parameter. The credential stays identifiable, and the file, line, and exploit path are untouched. Add .airsec/ to your .gitignore regardless.

These properties are covered by test/security.test.ts.

Caveats

  • airsec runs your coding CLI, so it spends that CLI's quota and inherits its permissions. Audit passes are launched read-only; fix is the only mode that grants write access.
  • Findings are produced by a language model. Verification removes a lot of noise, but treat the output as a strong prioritised lead list, not a proof.
  • A clean report is not a security guarantee. It is one pass by one reviewer.
  • Findings are stored under .airsec/runs/. Keep that directory out of version control.

License

MIT

About

Find Security Issues in your code by using coding CLIs like Claude, Codex & CommandCode

Resources

Stars

128 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages