Blog

AI Security Audit: Make Every Finding Reproducible

An AI security audit becomes useful when each finding carries its code location, exploit path, command output, severity reasoning, and fix.

Drew Stone
code-auditorsecurity-auditai-security
An editorial still life about reviewing code and producing evidence

The security lead receives a report that says, “Possible authorization bypass in the admin export route.” There is a line number, a confident paragraph, and no request that another engineer can send.

The release risk is a report that sounds certain while leaving the second reviewer unable to reconstruct the claim. An AI security audit is ready for a release decision only when that second reviewer can reconstruct the claim from the report. That means the report identifies the exact code revision, the route an attacker would take, the inputs and permissions used, the observed result, the reason for the severity, and the change that should prevent it.

This is the standard behind the Code Auditor direction described by Tangle. The public product pages and service surface may change, so this article describes the evidence contract rather than promising a callable audit endpoint.

Reproducibility starts with one reviewer and one packet

Reproducible does not mean that every reader can run every test on every machine. It means the report makes the important conditions explicit and separates a failed setup from a failed security claim.

An illustrative finding packet might look like this:

{
  "id": "auth-001",
  "status": "reproduced",
  "title": "Signed-in user can read another account's records",
  "source": {
    "revision": "illustrative-release-digest",
    "files": ["export/handler.ts", "auth/authorize.ts"]
  },
  "setup": {
    "install": "npm ci",
    "test": "npm test -- export-auth",
    "runtime": "Node.js 22, test database fixture v3"
  },
  "request": {
    "method": "GET",
    "path": "/exports/account-b",
    "identity": "account-a"
  },
  "expected": "403 Forbidden",
  "observed": "200 OK with account-b records",
  "impact": "A signed-in user can read another account's export",
  "severity_reason": "The route exposes another tenant's data without a privileged role",
  "fix": "Authorize the requested account before loading the export",
  "rerun_after_fix": "pending"
}

The path names and values are illustrative, not a report from a private repository. The important property is the shape: a reviewer can see what was tested and what remains open without trusting the model’s prose.

The packet also makes uncertainty visible. If the test database was a fixture rather than production data, the report should say so. If the route was tested only with a normal user and not with a service token, that boundary belongs in the report.

A finding is a chain of claims

Security reports often compress several different claims into one severity label. An agent should keep them separate.

ClaimEvidence needed
The pattern existsTool output with rule, query, location, and version
The code is reachableCall path, configuration, caller identity, and prerequisites
The behavior is exploitableRequest, test, simulation, or transaction that triggers it
The impact is materialAsset, permission, data, or state change observed under the stated setup
The fix worksThe original proof fails after the change while intended behavior still passes

CodeQL describes its own analysis as database creation, query execution, and result interpretation. That sequence is a useful reminder that query output is an input to review, not the review itself.

Semgrep’s finding glossary distinguishes a rule match from the underlying truth of the code’s behavior. Its triage documentation also treats open, reviewing, fixing, and ignored states as workflow decisions that need context.

For web applications, the OWASP Web Security Testing Guide (WSTG) provides a testing structure and scenario identifiers. Use the WSTG versioned-reference guidance when a report relies on a particular scenario, because identifiers can change as the guide evolves.

A threat model states which attackers, assets, permissions, and execution paths the review covers. The report’s job is to connect those general tools to one repository and that threat model.

Severity must follow the demonstrated path

Severity describes what can happen, who can cause it, and what the affected system controls. The label should be the last part of the argument.

Use this downgrade table when proof stops short of the claim:

Initial claimWhat is missingReport state
High authorization flawNo reachable caller under the release configurationCandidate or low-confidence review item
Critical asset lossNo observed asset movement, state corruption, or permission changeUnconfirmed impact
Confirmed injectionThe input reaches a sensitive operation, or sink, in static analysis, but the runtime test never executes the pathReachable candidate, reproduction pending
Vulnerable dependencyThe affected package is installed, but the vulnerable function is not usedDependency exposure requires a reachability check
Any severe issueThe build or fixture fails before the proof beginsSetup failure, not a reproduced vulnerability

This is not a universal scoring formula. It is a writing discipline that prevents a strong adjective from hiding a missing condition.

The report should preserve the raw tool signal even when the severity changes. An engineer may need to tune the rule, add a test, or revisit the conclusion after a configuration change.

Read the report in the order a release decision happens

A reviewer rarely starts by reading every model message. The first pass should answer whether the report has enough material to spend more time on it.

Start with the source identity. If the report does not say which revision, dependency lockfile, or deployment configuration it examined, the location may describe code that is no longer shipping.

Then read the expected and observed results. The difference between “the request reached the handler” and “the request returned another tenant’s record” is the difference between a path candidate and a demonstrated data disclosure.

Next, inspect the caller and preconditions. An administrative route may be public at the HTTP layer while an edge service, role check, or service identity blocks the dangerous action before the handler runs. The report should name that boundary instead of summarizing the route as public or private.

Finally, read the fix check and limits. If the patch was never tested, the report is a recommendation rather than a verified repair. If the test used a fixture, the report should state what the fixture omits.

Review questionIf the answer is missing
Which exact source and dependencies ran?Hold the finding until the code identity is pinned
Which identity and input reached the path?Treat exploitability as unresolved
What did the command return?Treat model explanation as unsupported
What state, data, or permission changed?Do not infer impact from a category label
Did the same proof fail after the patch?Keep the fix unverified

This order keeps the review efficient without making it shallow. It directs attention to the claims that can change a release decision and leaves stylistic commentary for later.

Setup failures are security-relevant evidence, but they are not exploits

Suppose the agent reports that it “validated” a missing authorization check, but the test command exited before loading the application. The report must say that no reproduction occurred.

The same distinction applies to missing dependencies, unavailable chain state, a failed database migration, a blocked network request, or a test that used the wrong identity. Each failure changes what the reader may conclude.

An evidence table makes that boundary easy to inspect:

Captured resultWhat it tells the reviewer
Exit code 0 with a passing exploit assertionThe test ran and observed the asserted bad behavior
Exit code 1 during dependency installationThe proof did not reach application behavior
HTTP 403 for the unauthorized requestThe tested authorization path rejected the caller
Timeout before a responseThe path is unresolved, not proven exploitable
A model summary without tool outputNo independent execution evidence is attached

The audit runtime, the process that runs commands and tests, must preserve command output and exit status as separate fields. Otherwise a model can describe a test that never ran, and the report will make that mistake hard to notice.

Verify the fix against the same proof

The first reproduction is only half of the release check. After the code change, rerun the same request or test against the patched revision.

A useful fix note contains four lines of reasoning:

  1. The original code allowed a specific caller and input to reach a protected action.
  2. The patch changes the authorization or state transition that made the action possible.
  3. The original proof now fails for the attacker while the intended caller still succeeds.
  4. Any untested configuration, dependency, or cross-service path remains listed.

For an authorization flaw, the regression test should cover both sides of the boundary. The unauthorized caller must receive the expected denial, and the authorized caller must retain the intended behavior. For a contract finding, the old exploit test should fail after the patch while balance and permission invariants continue to pass.

Do not replace a failed rerun with a new prose explanation. If the proof cannot be repeated because a fixture disappeared or a chain state is unavailable, report the reason and leave the finding open.

A missing proof needs a useful next step

Some findings cannot be reproduced in the first run without becoming false or unsafe experiments. The production service may depend on a third-party identity provider, a queue race, a live block range, a private package, or a permission that the audit worker must not receive.

The report should turn that boundary into a request a reviewer can answer. “Could not reproduce” is a status, not an explanation.

Missing conditionBetter report detailNext step
Identity provider unavailableName the token claims and authorization branch that were not exercisedSupply a redacted fixture or run the check in an approved environment
Race requires two concurrent requestsState the interleaving the test attempted and the scheduler it usedAdd a deterministic concurrency test or request specialist review
Chain state has movedPin the network, block, contracts, and state snapshot that were availableRe-run against a preserved fork or downgrade the claim to historical evidence
Private dependency cannot be installedRecord the package boundary and the command that failedProvide a trusted build artifact or mark the affected path unresolved
Live data would expose usersDescribe the redacted assertion and missing sampleUse synthetic records that preserve the authorization relationship

This format keeps the report honest while giving engineering a path to completion. It also protects the audit from silently widening permissions until a desired result appears.

A reviewer can then distinguish three outcomes. The issue may be disproved by a safe fixture, confirmed in an approved environment, or left open because the missing condition is part of the risk itself. Each outcome deserves a different release decision.

The automated smart contract audit guide shows how to turn a confirmed contract proof into a regression test. The AI code audit guide covers the isolated execution workflow that makes this rerun safer.

Tangle can carry the evidence, but each layer proves something different

Tangle’s public Sandbox is an isolated machine for an agent. Its quickstart shows an SDK client creating a machine, running a command, capturing its output, and deleting the machine afterward. That makes it a possible execution boundary for the scan and rerun, not a substitute for the finding packet.

If a future audit workflow is packaged for Tangle, a Blueprint would be the service package defining its jobs, metadata, contracts, and runtime rules. An operator would be the service provider that runs the live instance for a caller. A service instance would be that live registration and execution context, not the reusable Blueprint package. The runtime would be the container, microVM, or other environment that executes the scan and reproduction. The public AI Agent Sandbox Blueprint overview describes that split between protocol lifecycle and live machine I/O.

An agent profile records the model, instructions, tools, permissions, and resource limits used by the run. Pinning that profile gives the reviewer a comparison point when two runs produce different hypotheses.

A trace is the ordered record of the run’s actions, inputs, outputs, and results. It answers “what did the agent do?” and “what did the tools return?” It does not answer “was the threat model complete?” without a separate review of scope.

An evaluation is a repeatable collection of audit tasks and expected outcomes used to compare runs under the same conditions. It makes the allocation measurable. For this workflow, the evaluation should include known true findings, known false positives, setup failures, duplicate alerts, and cases where the correct answer is “not enough evidence.” Score the system on reproducible accepted findings, correct rejections, missed issues, rerun success, and reviewer time.

The product boundary is important. Protocol state can record that a service exists, an operator can run a runtime, and a trace can record actions. Those facts still need to be joined to the code revision and security evidence before an engineer accepts the finding.

What a reproducible audit cannot prove

An AI security audit does not prove that a codebase has no vulnerabilities. It proves what the recorded tools, inputs, environment, threat model, and tests support.

It can miss logic that no rule or task asks about. It can miss races across services, production-only settings, dependency behavior, economic attacks, and interactions that the test fixtures do not model. It can also reach a correct conclusion for the wrong reason, which is why raw tool output and repeatable tests matter.

Human auditors remain responsible for high-value systems and final release decisions. Independent methods, monitoring, incident response, and a clear disclosure process cover risks that a repository run cannot see.

Use the packet as the release decision

Accept an AI security audit finding when a second reviewer can locate the code, follow the stated path, inspect the captured action and result, understand the impact, and rerun the relevant proof. Downgrade or hold the claim when any of those pieces is missing.

For the next step, use the OWASP WSTG scenarios for web test coverage, CodeQL’s analysis model for query provenance, and Tangle’s Sandbox quickstart for the execution boundary. Then compare the workflow with AI Vulnerability Scanner Vs Agent Audit before choosing where agent time belongs.

What is an AI security audit?

It is a security review assisted by an agent that combines code analysis, context inspection, executable tests, and a report of evidence and limits.

What makes an audit finding reproducible?

The report identifies the exact source revision, setup, caller, inputs, commands, observed result, impact reasoning, and fix check needed for another reviewer to repeat or inspect the claim.

Should a severe finding be accepted without an exploit proof?

Treat it as a candidate unless the report has another strong proof path, such as a formal argument or a deterministic invariant failure, and explains why an exploit demonstration is unavailable.

Does this replace a human security auditor?

No. It can reduce triage work and produce better review material, while humans still choose the threat model, inspect high-risk behavior, and approve the release.

What should happen when a test cannot run?

Mark the reproduction incomplete, record the setup error, preserve the original candidate, and state what a reviewer must provide before the severity can increase.