Skip to main content

Multi-agent code audit for Claude Code in which no finding reaches your code unverified

Project description

CCA-Audit — multi-agent code audit for Claude Code. Pipeline: parallel auditors, consolidate, verify (L2.5), fix, regression diff (L5.5), architect gate (L6).

CI Python 3.10–3.13 378 tests passing Type-checked with pyright MIT license

CCA-Audit

A multi-agent code auditor for Claude Code in which no finding reaches your code unverified.

Ten specialised auditors read your diff in parallel. Their findings are deduplicated, then each one is re-derived against the real code — mechanically, by pyright, semgrep, pytest or hypothesis wherever a tool can settle the claim, and by adversarial review where none can. Only findings that survive that gate are eligible to be fixed. The fix is then checked for scope creep, and the whole change is gated on a mapping proving every confirmed finding has a fix and every edit has a finding.

The design constraint throughout: a check that could not run must never be indistinguishable from a check that passed.


Verified in the field

Two upstream results, both from hunt mode, both checkable in under a minute.

SciPy — merged upstream

Hunt mode was pointed at scipy/scipy (~14.8k stars) and found a copy-paste defect in signal.decimate's complex-coefficient guard:

elif (any(np.iscomplex(system.poles))
      or any(np.iscomplex(system.poles))   # <- should be system.zeros
      or np.iscomplex(system.gain)):

The guard exists to route complex-coefficient filters away from zpk2sos, which cannot represent them. Because it tested the poles twice and never the zeros, a dlti filter with real poles and complex zeros was not recognised as complex, was forced through zpk2sos, and raised ValueError: complex value with no matching conjugate on valid input.

Submitted upstream PR #25654, 18 Jul 2026
Merged 23 Jul 2026 by a SciPy maintainer — approved "LGTM", labelled defect
What shipped the one-line fix and our regression test, test_complex_zeros_real_poles_iir_dlti
Latent for ~3 years 3 months — introduced by gh-17881 (Apr 2023)

The defect is sharper than its one-line diff suggests: gh-17881 was itself titled "Fix handling on user-supplied filters in signal.decimate" and added complex-filter support. The typo silently defeated half of the feature that PR shipped — complex poles worked, complex zeros crashed — and survived review, three years of releases, and the surrounding test suite, which covered the working half only. It is still live in the current release, 1.18.0.

To be precise about what that means: the fix was ours and it was merged into a core scientific library after maintainer review. What we did not do is submit it autonomously — SciPy's AI policy requires a human to check generated code and open the PR, and requires the PR prose to be the contributor's own words. Both were honoured, and the AI-assistance disclosure is in the PR body.

Polymarket py-sdk — reported, fixed by the maintainers

Hunt mode was pointed at Polymarket/py-sdk — a third-party SDK, ~70 stars, actively maintained — and found a client-side price-validation defect:

_resolve_price and _resolve_protected_market_price validated prices by decimal-place count rather than tick-grid membership. The two agree on power-of-ten ticks and diverge on 0.005 and 0.0025 markets, where a price like 0.007 has three decimals, passes validation, is signed into the EIP-712 order, and is only then rejected by the exchange.

Reported upstream issue #162, 14 Jul 2026
Fixed upstream PR #181, merged 21 Jul 2026 — body states "Fixes #162"
Author of the fix a Polymarket maintainer, independently
Issue status closed as completed

To be precise about what that means: here the upstream fix was written by a Polymarket maintainer, not submitted by us — an outside bug report was specific enough that the maintainers implemented and shipped it themselves.

How the finding survived to that point: /audit-fix hunt surfaced four candidates; the adversarial 2-of-3 verifier killed the flashiest one (already fixed in an open upstream PR) and one deliberate-by-design finding; the survivor was reproduced against ~23,000 exhaustive price/tick cases before a line was written.

Neither result was cherry-picked from a benchmark: both targets were third-party repos nobody here wrote, and in both cases the finding had to survive the same adversarial gate before it left the machine.

It audits itself, and the self-audit finds things

The most recent release, v3.5, shipped through five task reviews and a whole-branch review. CCA was then pointed at it. It found two Critical defects that the entire review process had missed:

  • CI had never executed the feature's test suite. The workflow installed an extra that omitted mpmath; both test files importorskip it, so ~25 tests — including the integrity gate, the feature's central safety guarantee — skipped silently on all four Python versions. 2 skipped became 31 passed.
  • One environment variable made correct code confirm as defective. CCA_SUBSTRATE_TOL=1e-20 drove the deliberately-correct test fixture to raise a violation at a measured relative error of 8.3e-18 — ordinary float64 noise. That verdict would have been binding: protected from being overturned, exempt from the adversarial panel, and feeding an automated fix plan.

Both were confirmed by execution rather than by re-reading, and both now carry red→green regression tests. The full round is PR #22.

What is actually different

Multi-agent review is common. These parts are not:

Mechanical verification, not a second opinion. A finding is classified into a claim type and settled by the tool that can actually decide it — pyright for definedness/nullability/type, semgrep for taint, a pytest repro for crash impact, hypothesis for arithmetic. The verdict carries a machine-produced artifact. An LLM may not overturn an artifact-backed verdict; it adjudicates only what no tool could settle.

Asymmetric verdicts, stated honestly. Each settler can only conclude what its evidence supports. semgrep can prove a sink is absent but never that an injection is real. The numeric checker can confirm with a falsifying example but never refute, because a property holding across a bounded search is the absence of a counterexample, not proof of correctness. A clean run is UNCERTAIN, not a green pass.

Anti-regression on the fix itself. After fixes land, a differential pass verifies each hunk maps to its finding and introduced no behaviour change beyond it. Scope creep and regression risk are kicked back.

Fix→finding mapping as a merge gate. The architect gate emits a table proving every confirmed finding has a fix and every edit traces to a finding. An orphan finding or a phantom edit forces REVISE.

Risk-tiered, not user-tiered. Trivial diffs run cheap; money, auth, and numeric diffs are forced to the full adversarial treatment. You do not choose the tier by asserting your change is safe.

Honest limits

A verification tool that cannot state where it is blind is asking for trust it has not earned. These are enforced as tests, not disclaimers:

  • The numeric substrate check cannot see sign or formula errors. It is decorrelated on evaluation and correlated on transcription: both substrates faithfully compute whatever structure was written down, so a flipped sign survives into both and they agree. That class belongs to the property helpers. test_sign_trap_does_not_violate asserts this.
  • Its integrity gate proves the returned value, not every intermediate. A target that delegates to an unpatched second module can return a high-precision-typed value carrying float64 precision. Measured: the gate passes a reference of 0.0 where the true answer is 0.5. test_gate_does_not_catch_cross_module_precision_loss asserts this.
  • A property is authored by the same agent that raised the finding, so a wrong declared relation produces a real counterexample to a wrong claim. A confirmation obliges you to re-read the relation, not just the verdict.
  • Deterministic settlers are Python-only. Other languages fall back to LLM adjudication.

Pipeline

flowchart LR
    A["Step 0\nDetect Files"] --> B["Step 0.5\nLanguage + Domain"]
    B --> T["Step 0.6\nTier Select\nFAST/STANDARD/DEEP"]
    T --> C["Step 1\nParallel Auditors"]
    C --> D["Step 2\nConsolidate + Dedup"]
    D --> V["Step 2.5\nVerify findings\n(anti-hallucination)"]
    V --> E["Step 3\nFix Plan"]
    E --> F["Step 4\nImplement\n(P1 red→green)"]
    F --> G["Step 5\nRe-verify\n(tests + lint)"]
    G --> R["Step 5.5\nRegression Diff\n(anti-regression)"]
    R --> H["Step 6\nArchitect Gate\n+ fix→finding map"]
    H --> I["Step 7\nCommit"]

FAST runs three core auditors and skips the regression gate — but still verifies every P1 before fixing it. No finding is edited into your code unverified, on any tier.

The auditors

Each has a non-overlapping scope, so findings do not duplicate and no auditor arbitrates another's domain.

Core (FAST runs only the first three):

Auditor Scope Does NOT check
Security (single authority) OWASP Top 10, injection, auth, secrets, CVEs Runtime bugs, code quality
Bug Null refs, error handling, races, resource leaks Security, style
Code Quality Type safety, DRY, complexity, naming, dead code Security, runtime, performance
Performance Slow queries, hot paths, memory, pooling Security, style
Documentation Missing docs, stale comments contradicting new code TODOs, debug statements
Environment Config completeness, format validation, naming Secrets (Security owns those)

Conditional, dispatched only when the diff touches their concern:

Auditor Runs when Checks
High-Stakes / Safety money / auth / delete / irreversible paths Bounds, guards, kill-switches, idempotency
Numerical / Units non-trivial arithmetic Sign, units, scaling, rounding, conversions
Data-Integrity migrations / SQL / schema Migration+grant, type assumptions, safe accessors
Dependency a manifest or lockfile changed Maintenance health, licences, unused deps, pin breakers
Deployability deployable code / units / migrations Generated files, pin/lock breakers, service↔scheduler pairing, deploy-target assumptions

Plus the verification agents: fp-check (anti-hallucination), differential-review (anti-regression), and the read-only architect-reviewer final gate.

Hunt mode — auditing code you did not write

/audit-fix reviews your diff. Hunt mode turns the same pipeline on a codebase you did not write — a dependency, a repo you are evaluating, a legacy service — to find pre-existing defects:

/audit-fix hunt src/payments        # a whole subtree, no diff required
/audit-fix hunt path/to/file.py     # or specific files

What changes:

  • Whole-file audit. "Pre-existing bugs are the target" replaces "only audit the diff." Age is not evidence of correctness.
  • A target-viability pre-flight runs first, before a single auditor is spawned: is the repo alive, does it accept contributions, is there a test harness, is the language one this pipeline audits well. An archived or deprecated repo is rejected up front — auditing a corpse burns the run and produces a fix nobody can merge.
  • Forced DEEP tier, so every finding faces the adversarial 2-of-3 verifier.
  • Upstream-duplicate check. L2.5 searches the target's own issues and PRs; a bug someone already reported is dropped as DUPLICATE rather than re-submitted.

The output is a finding you can stand behind: reproduced with a failing test, not already known upstream, and survivable under adversarial review. That is the process that produced both field results above — the merged SciPy fix and the Polymarket report.

Install

pip install cca-audit
cca-audit install          # run from the root of the project you want to audit

That copies the auditor agents into .claude/agents/ and /audit-fix into .claude/commands/, and puts the cca_checks verifier (python -m cca_checks) on the same interpreter. Re-run cca-audit install to upgrade — files you have customized are preserved as <name>.md.bak rather than overwritten.

Install without pip (shell script, requires git)
# Unix/macOS
curl -fsSL https://raw.githubusercontent.com/GiulioDER/cca-audit/master/claude-code/install.sh | bash
# Windows PowerShell
irm https://raw.githubusercontent.com/GiulioDER/cca-audit/master/claude-code/install.ps1 | iex

Same result: it clones the repo to a temp directory and copies the same files. Both paths read the markdown from cca_checks/plugin/, so there is one copy on disk and they cannot drift.

For the deterministic verification layer, have pyright, pytest and semgrep on your PATH. Without them the definedness / nullability / type / taint claim types fall back to LLM-only verification — no crash, no regression.

numeric is the exception: it fails closed rather than falling back. On DEEP, a NUM-* P1 may not enter the fix plan on an LLM-sourced verdict — it carries a Hypothesis artifact or it is escalated as UNCERTAIN. DEEP is forced for every high-stakes or numeric diff and for all of hunt mode, so without the numeric extra, arithmetic findings on money-path code cannot be auto-fixed at all. That is deliberate — a sign error reads fluently, so a second LLM opinion is not evidence — but it is a hard stop, not graceful degradation.

The extras carry it:

pip install 'cca-audit[verify]'    # the whole deterministic layer in one install
pip install 'cca-audit[numeric]'   # just hypothesis + mpmath + pytest

From a clone, the editable equivalents are pip install -e ".[verify]" / -e ".[numeric]".

Worked example: examples/sign-trap — a real sign error, the property that catches it, and the resulting artifact.

Usage

/audit-fix                 # audit + fix uncommitted changes (tier auto-selected)
/audit-fix deferred        # second pass: fix P3 items deferred by the previous round
/audit-fix no-fix          # audit + verify only
/audit-fix p1-only         # fix only P1 Critical findings
/audit-fix fast | deep     # override the auto-selected tier
/audit-fix commit 3        # audit the last 3 commits
/audit-fix files src/app.py
/audit-fix hunt src/       # audit code you did NOT write

You normally do not pick a tier — the pipeline does.

Tier When (auto) Auditors Verification gates P1 fix style
FAST trivial, low-stakes, non-deploy diff 3 core L2.5 on P1 only direct
STANDARD normal diff all 6 core + conditional L2.5 + L5.5 + mapping red→green test
DEEP high-stakes / numeric / hunt / forced all of STANDARD + adversarial 2-of-3 on high-stakes P1 red→green test
Priority Criteria Action
P1 Critical Security, data corruption, auth bypass, injection, unsafe money/irreversible handling Fix before deploy, with a red→green test
P2 High DRY divergence, stale misleading comments, config inconsistencies, unit mismatches Fix now
P3 Cosmetic, style, naming, unused params Deferred to round 2

Round 2 (/audit-fix deferred) reads the deferred list from the previous commit, re-checks each item is still relevant, fixes what remains and marks the rest stale — so no audit leaves a tail.

Engineering

Tests 378, on every push and PR
Python 3.10, 3.11, 3.12, 3.13 — full matrix in CI
Packaging wheel built and smoke-installed into a clean venv in CI
Lint ruff, zero warnings
Types pyright
Design of record docs/v3-design.md, versioned slices v3.0 → v3.5

Every deterministic-verification slice ships with a written spec and an implementation plan under docs/superpowers/, including the measured dead ends — approaches that were tried, failed, and are recorded so they are not re-attempted.

Documentation

Writing

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cca_audit-0.7.1.tar.gz (133.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cca_audit-0.7.1-py3-none-any.whl (111.5 kB view details)

Uploaded Python 3

File details

Details for the file cca_audit-0.7.1.tar.gz.

File metadata

  • Download URL: cca_audit-0.7.1.tar.gz
  • Upload date:
  • Size: 133.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for cca_audit-0.7.1.tar.gz
Algorithm Hash digest
SHA256 f57022e896e15664b30476e5414b8afc725ce9472acfcfefe7c12a1490aa2162
MD5 fc8935ac36e6e7ddee698bbeeb706efe
BLAKE2b-256 e15c375e9e7a58c8748adb47d4a588039a6f7336ba88b4e3f1d810e4fbbf7611

See more details on using hashes here.

File details

Details for the file cca_audit-0.7.1-py3-none-any.whl.

File metadata

  • Download URL: cca_audit-0.7.1-py3-none-any.whl
  • Upload date:
  • Size: 111.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for cca_audit-0.7.1-py3-none-any.whl
Algorithm Hash digest
SHA256 bc474efb88725711df3e442b71ca07c692b565a63efcbfeb558466cea20da971
MD5 851ba92dc0a5e32356d5c59752fac263
BLAKE2b-256 0bc4a03bdf531daa36a9b50dd8f8fee62cd51d06f803de25e8397e9a87d4c46b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page