Security Autofix
Prove which scanner findings are reachable, remediate only the eligible ones, and open a draft PR only when every safety condition holds.
Security Autofix ingests a SAST/SCA scanner's SARIF, proves reachability honestly, remediates only the findings that are genuinely fixable, and opens a draft pull request only when every safety condition holds — never on a guess. Use it when a scanner has produced hundreds of findings and you need to know which ones are real, and to auto-remediate the provable ones with an evidence-backed PR.
The command is codna secure. It runs in two tiers:
Tier-1
Classify reachability + autofix-eligibility (read-only)
Zero
No
Tier-2
Remediate eligible findings: fix, verify, re-prove closure
Yes (patch-gen)
Only a draft PR, and only past the gate
Start with Tier-1 — it is the fast, free wedge and the only step most findings ever need:
codna secure . --from-sarif results.sarifThe end-to-end loop
Security Autofix is a single closed loop. Each step feeds the next; a failure at any step stops the loop and is named in the report. The work spans three privilege domains — the read-only engine classifies and re-proves closure, the sandboxed worker runs untrusted repo code with no write token, and the writer holds a scoped token and opens the PR but runs no repo code.
Command reference
codna secure [repo] --from-sarif <results.sarif> [options]
repo (positional)
.
Local path or git URL of the repository under analysis
--from-sarif PATH
— (required)
Scanner SARIF output (CodeQL / Semgrep / Snyk / Trivy)
--ref REF
repo default
Branch, tag, or commit to snapshot
--engine {local,remote}
local
Reachability engine. local is the bounded in-process classifier and never claims exploitable; remote sends classification to the Codna engine over HTTP (the local runtime, or CODNA_ENGINE_URL)
--verification PATH
—
codna-security.yaml manifest; required for --open-pr and for remote --fix
--fix
off
Remediate eligible findings; opens no PR. The default --engine local applies the verified patch to your checkout (re-verified there); --engine remote without --open-pr is report-only
--open-pr
off
Open a fix PR per eligible finding (requires --engine remote and a resolved manifest)
--github-token TOK
$CODNA_GITHUB_TOKEN/$GITHUB_TOKEN
Write token for --open-pr
--base-branch NAME
main
PR base branch
--repo-slug owner/repo
derived from git remote
Target repo for --open-pr
Relevant environment variables:
CODNA_API_KEY
Your Codna key. Read only when CODNA_ENGINE_URL points --engine remote at a remote engine; the local runtime does not use it
OPENAI_API_KEY / ANTHROPIC_API_KEY / GEMINI_API_KEY
Provider key for model-backed remediation
CODNA_GITHUB_TOKEN / GITHUB_TOKEN
Write token. secure --open-pr accepts either; secure-open-pr reads GITHUB_TOKEN only
CODNA_ATTESTATION_KEY
HMAC key shared between worker and writer (required for the two-job handoff)
CODNA_FIX_MODEL
Patch-generation model for --engine local --fix
CODNA_MODEL_PACK_DIGEST
Model-pack digest pinned into security provenance
Tier-1: honest reachability proof
Codna ingests the scanner's SARIF, validates its provenance, and assigns every finding exactly one of four verdicts. This classification is read-only and spends zero LLM tokens — no patch generation, no build, no scanner rerun.
exploitable
An independent source-to-sink taint path is proven
Yes (policy default)
production-reachable
The vulnerable operation is callable in production; taint not independently proven
Only with an explicit policy override
unreachable
No path, plus a complete, sound analysis envelope
Never
unknown
No path under an incomplete or unsupported envelope
Never
The leading → marks autofix-eligible findings; · marks the rest. The bracketed value is the reachability verdict; the parenthesized value is the finding kind (taint, sca, control_flow, config, secret, or unsupported).
Why an envelope can be incomplete
Reachability is only as trustworthy as the analysis that produced it. Codna keys completeness on (language, framework, build_mode) and falls back from most to least specific. Reflection-heavy stacks deliberately get a bounded envelope, because reflection makes "absence of a path" unprovable:
python, javascript, typescript, go, java
complete
unreachable / production-reachable
java + spring
bounded
production-reachable (never unreachable)
ruby, php
bounded
production-reachable
any other language
unsupported
unknown only
A finding under a bounded envelope with no proven path is reported unknown (bounded envelope: production-reachability not independently provable), never unreachable. An off-matrix language yields unknown with proof type off-matrix (<language>).
Choosing the reachability engine
The same command runs against either engine. Pick the one that matches your trust and connectivity constraints:
The Codna engine over HTTP. It is the only path that can earn the exploitable verdict, and the only path that can drive --open-pr through the privilege-separated writer or write an evidence bundle with --evidence-dir.
Without further configuration, --engine remote talks to the local runtime Codna starts on your machine. Set CODNA_ENGINE_URL and CODNA_API_KEY to send classification to a remote Codna engine instead.
The bounded local reference engine — air-gapped, no external engine and no SAST scanner image. Its strongest verdict is production-reachable; it never claims exploitable. It can run a local fix loop that applies the verified patch to your checkout and re-runs verification there, but opens no PR — passing --open-pr with --engine local is refused.
For the local fix loop, the patch comes from the Codna agent with the model named in CODNA_FIX_MODEL.
Test-only sinks (paths matching tests/, spec/, __tests__/, or *_test.* / *.test.* / *_spec.*) are reported unreachable under a complete envelope (test-only sink; no production entrypoint), or unknown under a bounded one. Artifacts that escape the repository path are quarantined and never analyzed or fixed (they show as [quarantined] … artifact path escape).
Tier-2: remediation
Tier-2 is opt-in. It only ever touches autofix-eligible findings; ineligible findings are reported and never remediated (no patch is generated for them). Eligibility is decided by policy (gate G3) — by default only exploitable is eligible; production-reachable requires an explicit policy override (see the policy section below).
Local fix loop
codna secure --fix --engine local runs a local detect → fix → verify → re-prove-closure loop with no external engine and no SAST scanner image. Reachability and closure both come from the bounded local reference engine; the patch comes from the Codna agent (model from CODNA_FIX_MODEL).
For each eligible finding, in an isolated ephemeral worktree pinned to the scan commit:
Detect — re-classify reachability with the bounded local reference engine.
Fix — generate a patch with the Codna agent.
Integrity — run the anti-evasion check (G4) before the patch is ever applied; a rejected patch is never applied.
Verify — apply the patch and run the manifest's build + test commands.
Re-prove closure — independently re-prove the original obligation is
closed.
A finding is reported REMEDIATED only when the patch applied cleanly, passed integrity, did not regress the tests, and the obligation re-proves closed:
Other per-finding statuses you may see: blocked: patch integrity (<reason>), blocked: tests failed (closure=<status>), and no tests configured (printed instead of "tests pass" when the manifest carries no test commands, since tests_passed is then None).
Closure is a separate judgment from reachability
This is a deliberate, load-bearing distinction.
The local engine's closure proof is a bounded heuristic:
The sink file must be in the patch's changed paths. If untouched, closure is
open.A recognized barrier must appear in the added lines: a parameterized / bound-parameter query (
.execute(<sql>, (params))),shlex.quote,html.escape,bleach.clean, a prepared statement / placeholder /bindparam, or an escape/sanitize call.Sink touched but no recognized barrier added → conservatively
open(e.g. the sink merely moved).Empty diff or no primary sink location →
unknown.
The PR-open gate
A draft PR opens only on the logical-AND of nine conditions (G1…G9). A single failure blocks the PR, and the failing condition is named in the report. The gate is pure logic — it composes already-computed verdicts and never executes anything itself.
Expand each gate for the exact condition and what blocks the PR:
Without --open-pr, a finding that passes every gate prints would open (report-only) instead of opening anything.
Privilege separation behind the gate
No single step holds the proof, the sandbox, and the GitHub token at once. The work is split across three privilege-separated roles:
Engine — independent reachability and closure proofs (the authority).
Worker — runs untrusted code (baseline scan, patch-gen, build/tests, scanner rerun) in a sandbox with no write token, and emits a signed attestation.
Writer — cannot execute repository code; it independently re-verifies the attestation, the patch digest, and that the base is unchanged, then opens the draft PR with a narrowly-scoped token.
There is exactly one verified finding per PR.
End-to-end: open a fix PR
--open-pr requires --engine remote. It and remote --fix require a --verification codna-security.yaml manifest pinning the scanner configuration and the build/test commands. For --open-pr the manifest must additionally be resolved (see the manifest section). The repo argument must be a local checkout — remediation reads the working tree and pins to the SARIF's commit.
Two-job CI handoff
So that the write token never lives in a step that runs repository code, the worker and writer can be split across two jobs. The worker emits a signed evidence bundle (attestation.json + patch.diff + finding.json); a separate writer-domain command verifies it and opens the PR:
The writer verifies the attestation signature against the shared CODNA_ATTESTATION_KEY and re-checks digest_of(diff) == patch_digest before it will open anything. If verification fails, no PR is opened.
Expected result
Tier-1 always returns a per-finding verdict and never writes code. Tier-2 writes only after the finding is policy-eligible, the patch passes integrity checks, verification commands pass, closure is independently re-proven, and the writer verifies the signed evidence bundle. If any gate fails, Codna reports the blocking gate and does not open a PR.
The verification manifest (codna-security.yaml)
The manifest is the reproducible, digest-bound plan that replaces raw shell flags. The whole document is bound by one digest, so the baseline and patched phases provably run the same configuration — change any field and the digest flips and the gate aborts (MANIFEST-TAMPER). It is loaded with PyYAML if installed, else parsed as JSON.
scanner.id, scanner.command, and scanner.output are required; everything else has a default. --open-pr additionally requires a resolved manifest, enforced before any work begins:
scanner.imagepinned by@sha256:<64-hex>,scanner.rules_digestpresent,sandbox.networkisdenyornone,at least one
verification.testscommand.
Autofix policy
The policy block encodes gate G3 (which verdicts may receive a fix PR), the severities that block a PR when newly introduced (G8), and scanner-severity overrides. It carries a deterministic digest so it is provenance-pinned (G1).
autofix_classifications
["exploitable"]
Verdicts that may be auto-fixed. Add "production-reachable" to opt in (the operator's explicit override). unreachable and unknown are never eligible regardless.
block_new_severities
["high", "critical"]
New-finding severities that block a PR under G8
severity_overrides
{}
Per-rule severity remapping applied during SARIF ingest
With the default policy, a production-reachable finding reports production-reachable requires an explicit policy override and is not remediated. After the opt-in above it reports production-reachable allowed by explicit policy override and becomes eligible.
Troubleshooting
For the bug-fix workflow (failing tests, --from-junit, --apply) see the CLI Reference; for repository understanding and context reduction see How It Works; for running Codna inside Cursor or Claude Desktop see MCP Server.
Next steps
CLI Reference — every
codna secureandcodna secure-open-prflag.GitHub Action — run secure mode in CI with the packaged action.
MCP Server — expose read-only secure triage to Cursor or Claude Desktop.
Last updated
Was this helpful?

