SecAudit
6 surfacesone enginev1.0.0

One engine.Every way in.

The plugin, the CLI, the MCP server, the Action, the image and the hooks all run the same deterministic tier — the one carrying the external number. None is a reduced version of another; the appropriate surface is the one already present in the workflow. Every command on this page is read at build time out of the file that defines it, so a renamed flag breaks this build rather than a user's terminal.

None of these paths requires an API key. The deterministic tier needs no network and no account; the optional model tier remains off until it is enabled.

Choose one

Which surface, in one table.

Same engine, same findings, same report. What differs is who runs it, when it runs, and what it is allowed to reach.

SituationSurfaceWhat it costs
An existing Claude Code workflowClaude Code pluginTwo lines; the live-target track comes with it
CI, gating pull requestsGitHub ActionTen lines of workflow, no install step
Codex, Cursor, OpenCode or another MCP clientMCP serverOne config entry
Local, scriptable and offline useStandalone CLIOne package, zero dependencies
A fixed toolchain and a sandboxed scannerDockerOne build, non-root
Before the commit rather than after itpre-commitStaged files only, under a second

The live target is the one real asymmetry. Probing a running system is a human decision, so it exists only in the plugin and only behind the authorization gate — nothing on any other row reaches a host. Everything else is the same code: the detectors, the taint analysis, the dependency reachability pass and the evidence pack.

1 / 6Claude Code

The plugin: the whole methodology, live target included.

Two lines in the Claude Code prompt. The marketplace id and the plugin id come out of .claude-plugin/marketplace.json — the file Claude Code itself reads — so this page cannot end up naming a marketplace that was renamed a release ago. This is the only surface with the live-target track and the only one that runs P1–P10 end to end; the others are the deterministic engine underneath it.

Active testing remains blocked until ownership is declared in scope.yaml. That is a PreToolUse hook rather than an instruction to a model: the harness refuses the call, so no amount of persuasion in the conversation reaches an unclaimed host.

/plugin marketplace add mtvrkan/secaudit
/plugin install secaudit@secaudit-kit
/secaudit .

Then type /secaudit. The command should appear with its argument hint — if it does not, the marketplace was added but the plugin was not installed.

plugins/secaudit/commands/4
  • 1

    /secaudit<url | path> [--lang tr|en] [--passive] [--active] [--code] [--deps]

  • 2

    /secaudit-code[path] [--lang tr|en]

  • 3

    /secaudit-deps[path] [--lang tr|en]

  • 4

    /secaudit-passive<url> [--lang tr|en]

2 / 6no key, no network

The CLI: the same engine with nothing around it.

secaudit-kit installs the secaudit command and nothing else: no account, no key, no plan, and no runtime dependency to audit before it can be trusted. It runs on a host with no network connection, which is the state a great deal of code worth auditing lives in. Python 3.9 or newer.

Until the first release is published, pip install secaudit-kit is a command that can succeed with somebody else's package: a pending trusted publisher does not reserve the name. The git+… line above needs no release and is exact. And the zero-dependency claim is a build gate rather than a sentence: assert_no_runtime_deps.py fails the build the moment one is added, and the list rendered beside this paragraph is read out of kit/pyproject.toml.

# the released package
pip install secaudit-kit

# or the same package from any commit, no release needed
pip install "git+https://github.com/mtvrkan/secaudit#subdirectory=kit"

secaudit .                             # audit this tree
secaudit . --min high --format sarif   # for GitHub code scanning
secaudit . --since main                # only what this branch introduced
kit/pyproject.toml
  • packagesecaudit-kit
  • version1.0.0
  • python>=3.9
  • commandssecaudit, secaudit-mcp
  • runtime depsnone

3 / 6stdio

The MCP server: Codex, Cursor, OpenCode, and whatever is next.

One process over stdio, 6 tools, the same engine the plugin and the CLI run. A second implementation per harness is how two clients end up disagreeing about whether a file is safe, so there is not one — the published number describes this server too.

No tool here accepts a URL, a host or an endpoint, and the test suite asserts that no schema grows one. A tools/call carries no evidence that anybody authorized a probe, and a tool that scans whatever it is handed is a tool that scans whatever a prompt injection puts in front of it.

what every client needs
  • Commandpython3 (or python on Windows)
  • Arguments-m, secaudit_mcp
  • Transportstdio
python3 -m secaudit_mcp --tools        # prints the tool manifest and exits

Per client

Claude Code — the plugin is the better path here, but the server works too

claude mcp add secaudit -- python3 -m secaudit_mcp

Codex CLI~/.codex/config.toml

[mcp_servers.secaudit]
command = "python3"
args = ["-m", "secaudit_mcp"]

Cursor.cursor/mcp.json in the project, or the global equivalent

{ "mcpServers": { "secaudit": { "command": "python3", "args": ["-m", "secaudit_mcp"] } } }

VS Code / Copilot CLI.mcp.json or .vscode/mcp.json

{ "servers": { "secaudit": { "command": "python3", "args": ["-m", "secaudit_mcp"] } } }

OpenCodeopencode.json

{ "mcp": { "secaudit": { "type": "local", "command": ["python3", "-m", "secaudit_mcp"] } } }

4 / 6pull-request gate

The Action: a gate on what the change introduced.

It runs the code in the checkout rather than installing a release, so the version audited is the commit under review and there is no window in which a compromised release of a security scanner runs against an already checked-out repository. On a pull request it compares against the base branch by default, so the gate fires on what the change introduced and stays quiet about the rest.

comment is off by default. A workflow should not acquire write access to pull requests because an example in someone's documentation had the flag set.

.github/workflows/security.yml

- uses: actions/checkout@v5
    with: { fetch-depth: 0 }      # so --since can see the base branch
- uses: mtvrkan/secaudit@v1.0.0
    with:
      fail-on: high
      sarif: audit.sarif

5 / 6non-root

The image: a fixed toolchain, built from this repository.

There is no image to pull, by design: it is built from the Dockerfile in the repository. The base is pinned by digest rather than by tag, so an image built today is the image someone else built last month, and everything that went into it is readable in one file.

It drops to an unprivileged uid, and the run above mounts the tree read-only. A scanner does not require write access to what it scans, and the one occasion on which that matters is the occasion on which the target has been tampered with.

docker build -t secaudit .
docker run --rm -v "$PWD:/src:ro" secaudit /src --min high
Dockerfile
  • basepython:3.12-slim-bookworm
  • pinned tosha256:4766d8b510c428e595d74b9cc5bbb2fae8e26316fffb4adc89908d79aacd58a2
  • runs asuid 10001
  • entrypointsecaudit

6 / 6staged files

The hooks: before the branch rather than after it.

Two hooks, both scoped to the files being committed. Budget matters more than coverage here: a hook that takes ten seconds is bypassed with --no-verify inside a week, and a bypassed hook catches nothing. So the default is the deterministic pass with the dependency and external-scanner tiers switched off, and the full audit stays in CI where it can afford to be slow.

Where the full hook is too slow to survive on a given repository, the narrower one applies. A secret is the least recoverable finding after the fact — once committed it is in the history — so catching that alone is worth considerably more than catching nothing.

.pre-commit-config.yaml

repos:
  - repo: https://github.com/mtvrkan/secaudit
    rev: v1.0.0
    hooks:
      - id: secaudit
  • 1

    secaudit

    SecAudit (staged files, deterministic pass)
    --min high --no-deps --no-scanners

  • 2

    secaudit-secrets

    SecAudit (secrets only)
    --min high --no-deps --no-scanners --no-taint --only secret

Optional, never required

External scanners, used when present on PATH.

The engine's own findings do not depend on any of them. Where one is installed its results are merged and attributed to it; where it is absent the report says so rather than quietly narrowing what it searched — a clean result has to mean the same thing on both machines.

  • semgreppipx install semgrepSAST (code)
  • gitleaksbrew/scoop install gitleakssecret detection
  • osv-scannergo install github.com/google/osv-scanner/v2/cmd/osv-scanner@latestdependency CVEs

A scan with none of these installed still runs every detector, the taint analysis and the dependency reachability pass. The live-mode toolchain — testssl.sh and the rest — belongs to the plugin rather than the engine, and docs/tooling-setup.md lists all of it.

After installation.

Getting started walks the first scan, the authorization gate and the report formats. The benchmark page has the external number and everything behind it.