openauditB85out of 100: openaudit's audit of itself

MCP and agents

Some audits need judgment rather than patterns. openaudit is built so agents can drive it end to end:

  • MCP server. claude mcp add openaudit -- bunx openaudit mcp (or the equivalent mcp.json in Cursor or VS Code) gives an agent run_audit, get_finding, compare_reports and more, plus the playbook tools list_playbooks, get_playbook and submit_findings. Your agent does the review with its own tools, and its findings merge into the same scored report. No extra API key is needed.
  • Headless agentic auditors. openaudit . --agentic runs the playbooks through the Claude Agent SDK. The agent gets read-only tools, confined to the project and the target origin. It uses ANTHROPIC_API_KEY or an existing Claude Code login.
  • AI triage. --ai turns the findings into a prioritised fix plan. Only finding metadata is sent, never file contents.
  • Agent mode CLI. --json prints a single JSON report to stdout, and openaudit schema gives its JSON Schema.
  • Skill. skills/openaudit/SKILL.md teaches an agent the loop: baseline, triage, playbooks, fix, then verify with compare_reports.

Details are in apps/cli/README.md (CLI, CI, MCP setup) and packages/agent/README.md (playbooks, data handling).

MCP server

The MCP server lets an agent run audits, read findings with the surrounding source code, compare reports to confirm fixes, and work through playbooks. Playbooks are judgment-heavy checks, such as authorization, business logic and real-world accessibility, that the agent does with its own code and browser tools. It then reports the results back with submit_findings. Playbooks need no API key.

Claude Code

claude mcp add openaudit -- bunx openaudit mcp

Cursor (.cursor/mcp.json) or VS Code (.vscode/mcp.json, which uses "servers" instead of "mcpServers"):

{
  "mcpServers": {
    "openaudit": { "command": "bunx", "args": ["openaudit", "mcp"] }
  }
}

Codex (~/.codex/config.toml):

[mcp_servers.openaudit]
command = "bunx"
args = ["openaudit", "mcp"]

Tools: detect_target, list_auditors, run_audit, get_report (json, markdown, sarif or html), get_finding (with ±5 lines of source), compare_reports, list_playbooks, get_playbook and submit_findings. Every tool returns a short markdown summary and also structured content. Reports are kept in memory (the last 20) and, for directory targets, written to .openaudit/reports/<id>.json, which is git-ignored automatically. The audit_project prompt walks an agent through the whole loop: audit, triage, run the playbooks, submit findings, fix, then compare reports.

MCP over HTTP

For remote or hosted agents, serve the same tools over MCP Streamable HTTP. Each client gets its own session (Mcp-Session-Id), so progress notifications and the report store are per session.

# Local: loopback only, one allowed project directory. A token is generated,
# printed once and saved to ~/.config/openaudit/mcp-token (mode 0600).
openaudit mcp --http --root ~/code/my-app

# Remote: needs a token (at least 16 characters)
OPENAUDIT_MCP_TOKEN=$(openssl rand -hex 24) openaudit mcp --http --host 0.0.0.0 --port 7337 --root /srv/repos

Endpoints: POST/GET/DELETE /mcp and an unauthenticated GET /health. Flags (all but --http apply only with it):

FlagDetails
--host <addr>Bind address (default 127.0.0.1)
--port <n>Port (default 7337; 0 picks a free one)
--token <t>Bearer token; falls back to OPENAUDIT_MCP_TOKEN. On loopback one is generated if omitted; a non-loopback host refuses to start without one (16+ characters)
--root <dir>Directory that file and directory targets must resolve inside (repeatable)
--allow-origin <origin>Browser Origin allowed to connect (repeatable)
--allow-host <host[:port]>Extra Host header value accepted (repeatable)
--allow-cloudAccept cloud:<provider> targets, which use the server's own credentials
--persist-reportsSave reports under <root>/.openaudit/reports as stdio does. Off over HTTP, where get_report html returns the document inline
--allow-imagesAccept image:<ref> targets on a non-loopback bind (their registry host is still checked)
--allow-agenticLet clients run agentic auditors, which spend the server's ANTHROPIC_API_KEY (refused by default)
--allow-private-targetsAllow URL targets on private, loopback, link-local or metadata addresses on a non-loopback bind

Claude Code

claude mcp add --transport http openaudit http://127.0.0.1:7337/mcp \
  --header "Authorization: Bearer $(cat ~/.config/openaudit/mcp-token)"

Generic mcp.json (clients with remote-server support; the exact key names vary a little by client):

{
  "mcpServers": {
    "openaudit": {
      "type": "http",
      "url": "http://127.0.0.1:7337/mcp",
      "headers": { "Authorization": "Bearer <token>" }
    }
  }
}

Security model:

  • Every request needs a bearer token, even on loopback (other local users can reach 127.0.0.1). It is compared in constant time and never logged; a generated one is printed once at startup and saved to a user-only file. The server binds 127.0.0.1 by default, and any other host refuses to start without an explicit token. Put a TLS-terminating proxy in front of a remote server, since the token travels in a header.
  • Host must match the bind address and port (DNS-rebinding protection), and a request with an Origin header is rejected unless it was allow-listed with --allow-origin. Requests without Origin (non-browser clients) pass.
  • Local targets are refused unless they resolve (via realpath, so .. and symlinks cannot escape) inside a --root. With no --root, only http(s) URLs and image:<registry-ref> targets are accepted (an OCI reference such as nginx:1.27 or ghcr.io/org/repo@sha256:…; transport forms like docker-archive:, oci-dir:, dir:, file: or podman:, and a bare registry: or docker: prefix, are refused, and Trivy is forced to --image-src remote); cloud: needs --allow-cloud. Stdio is unchanged and trusts its local client.
  • Reports live in memory, scoped to the session that created them (other sessions get "Unknown reportId"), and are not written into served directories unless --persist-reports is set. Source context for findings is read only from regular files that resolve inside the report's root.
  • Requests are capped at 4 MiB, at 32 concurrent sessions, and sessions idle for 30 minutes expire. DELETE /mcp, SIGINT and SIGTERM close sessions cleanly.
  • Agentic auditors (agentic: true) are refused unless --allow-agentic is set, so remote clients cannot spend the server owner's API key. Stdio is unchanged.
  • On a non-loopback bind, URL targets must resolve to public addresses (SSRF guard shared with the hosted API): private, loopback, link-local, metadata (169.254.169.254) and unique-local IPv6 addresses are refused, as are ports other than 80, 443, 8080 and 8443. Every request and redirect hop is DNS-validated and pinned to the checked IP, and web.tls dials that IP too. To make pinning possible, guarded URL audits run only the built-in web.* auditors: no browser, Lighthouse or external tools. Loopback binds and --allow-private-targets skip the guard and run everything. image: targets are refused on such binds unless --allow-images is set, and then the registry host (Docker Hub when the reference has none) is validated like a URL, including the port rules. Residual risk: Trivy and Dockle resolve the registry themselves, so that check cannot be pinned and a DNS-rebinding registry could still reach a private address.

Playbooks

idtargetsneedsfocus
agent.authzcodebasecoderoute inventory, missing auth, IDOR, tenant isolation, privilege escalation, sessions/JWT, password reset, rate limiting
agent.injectioncodebasecodesource → sink review: SQL/NoSQL, command, SSRF, XSS, path traversal, SSTI, deserialization
agent.secrets-configcodebase, container, iaccodesecrets in logs/bundles/CI/images, debug modes, env handling, broad IAM
agent.performancecodebasecodeN+1, missing indexes, unbounded queries, blocking work, caching, bundles, leaks, waterfalls
agent.privacycodebase, urlcode, networkPII inventory and flows, logging, third parties, consent, retention/deletion, data-subject rights
agent.a11y-journeyswebapp, urlbrowserkeyboard and screen-reader journeys, focus, dialogs, errors, live regions, reflow, zoom, motion
agent.webapp-securitywebapp, urlbrowsersessions, logout, CSRF, cookies in practice, cross-account access with test accounts, error leakage, login throttling
agent.ux-contenturl, webappnetworkcontent quality and search intent, Nielsen heuristics, broken journeys, copy, mobile layout
agent.mobilemobilecode (shell for host agents)OWASP MASVS: storage, network/pinning, deep links, WebViews and JS bridges, logging
agent.iac-architectureiac, containercodeblast radius, least privilege, segmentation, backup/DR, secrets management, image/action supply chain

Every playbook has a scope, a method, an evidence standard, a severity rubric, a "what NOT to report" list, reporting fields and a checklist.

Two ways to run agentic audits

1. Headless, inside openaudit (openaudit --agentic)

Agentic auditors are off by default. --agentic (or options.agentic: true) enables all of the ones that apply to the target, and selecting one by id (for example agent.authz in the auditor selection) runs just that one.

Each auditor starts a Claude Agent SDK session (query()) with:

  • A system prompt made of an openaudit preamble, the playbook and its checklist, and a description of the target.
  • Read-only tools. Read, Grep and Glob for local targets, with the project root as the working directory. There is no shell: Bash is disallowed for every playbook. WebFetch, WebSearch, Write, Edit and NotebookEdit are also disallowed. The tool guard below enforces this.
  • An in-process MCP server, openaudit. Its report_finding tool validates each finding (schema, and that the file exists), prefixes the rule id with the playbook id, masks common token formats and caps the number of findings. Its finish tool closes the run. URL targets also get http_get, a same-origin GET/HEAD that returns raw headers and HTML, sends the configured options.headers and redacts cookie values.
  • A browser, only if you ask for one. With --browser, playbooks that need a browser get the Playwright MCP server (npx -y @playwright/mcp@0.0.83 --headless, pinned), with script evaluation and file upload disabled. Without --browser they fall back to http_get and report an info finding, <id>/reduced-coverage.
  • A tool guard on every call. One policy runs as a PreToolUse hook, which fires on every tool call, and as canUseTool. It assumes the audited code or site may prompt-inject the agent, so it holds even against an adversarial model:
    • Read/Grep/Glob paths (including a Glob pattern's base directory) are resolved against the project root with symlinks followed (realpath). Anything that ends up outside the root is denied, including ~, .., absolute paths and symlinks pointing out. report_finding applies the same check to finding locations.
    • Network access exists only through http_get and the browser. Both are pinned to the target's exact origin (scheme, host and port); URLs that fail to parse are denied. Local-file runs have no network tool at all.
    • Browser tools that run arbitrary code or touch local files (browser_evaluate, browser_run_code_unsafe, browser_file_upload, browser_install) are denied.
    • Any other tool, including Bash, WebFetch, WebSearch, writes and MCP servers other than openaudit/playwright, is denied.
  • Isolation. settingSources: [], strictMcpConfig and persistSession: false, so your own CLAUDE.md, hooks, plugins, MCP servers and permission rules do not apply, and no transcripts are saved.
  • Cost bounds. maxTurns (default 40) and an optional maxBudgetUsd. If the run stops early, the findings collected so far are kept and an info finding, <id>/incomplete, says so. Interrupting the audit, or hitting the engine's 20-minute agentic timeout, aborts the SDK session.

Settings go in options.settings, either under "agent" (shared by all agentic auditors) or under an auditor id:

{
  "settings": {
    "agent": { "maxTurns": 60, "maxBudgetUsd": 5 },
    "agent.webapp-security": {
      "instructions": "Test accounts: alice@test.example / bob@test.example (passwords in the staging vault). Do not touch /billing.",
      "storageState": "./auth.json"
    }
  }
}

The available keys are maxTurns, maxBudgetUsd, effort (low to max, default high), maxFindings (default 40), instructions and storageState (a Playwright storage-state file for a logged-in test account; it must live inside the project, or the working directory for URL targets). Arbitrary Playwright flags are not accepted, because several of them execute code.

2. Host agent over MCP

The openaudit MCP server serves these playbooks to the agent you already use (Claude Code, Cursor and others). The host agent calls get_playbook, does the work with its own tools and permissions, and sends results with submit_findings. The playbook text is the same in both modes, and its reporting section matches the report_finding fields.

3. Vercel deepsec (--only agent.deepsec)

agent.deepsec wraps deepsec: a regex pass finds candidate files, then deepsec's own agent reviews the riskiest ones. It is opt-in and runs only when selected explicitly; a config inside the target cannot enable it. Requirements and limits:

  • Install and auth. A local deepsec binary (npm i -g deepsec@2.3.10) and an API credential in the environment (ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, OPENAI_API_KEY, or AI_GATEWAY_API_KEY). Without a credential it is skipped. It never falls back to deepsec's use of a logged-in claude/codex CLI.
  • Cost. deepsec process has no cost flag, so openaudit reviews files in chunks of 5 and stops once deepsec's recorded spend reaches maxCostUsd (default 5) or maxDuration (default 30m). The cap can overshoot by one chunk. Set these under settings["agent.deepsec"] in a --config file, along with maxFiles, diff (true for origin/main, or a git ref: reviews files changed between that ref and HEAD, committed changes only, for CI), provider and model.
  • Trust. Unlike openaudit's agents, deepsec's reviewer gets Bash inside the Agent SDK's OS sandbox, and deepsec continues without the sandbox if it can't start. Run it only on code you trust. openaudit keeps deepsec's state and config lookup in a temp directory, never the target, and overrides .git/config keys that make git run commands.

Model, auth and cost

  • Model. The default is claude-opus-5-5. Override it with options.model (the CLI's model option) or OPENAUDIT_MODEL.
  • Auth for agentic auditors. These use ANTHROPIC_API_KEY, or an existing Claude Code login, through the Agent SDK. If the SDK cannot start or authenticate, the auditor is skipped with a hint; it does not fail the audit.
  • Auth for AI insights. These need ANTHROPIC_API_KEY (or ANTHROPIC_AUTH_TOKEN). Without one they fail with a clear error.
  • Cost. Agentic runs are real multi-turn agent sessions, and their cost grows with the size of the target and the turn limit. Use maxTurns, maxBudgetUsd, a smaller model, or select fewer auditors to bound spend. The log line for each run shows its turns and estimated cost.
  • Fallback. For models that support it, AI insights opt into server-side refusal fallback (fallbacks: "default").

What the agents send to the model, and the limits they run under, are on the Safety page.