Scanner Evidence & Assurance Model
The vault prototype is analysed in CI by Slither. This page explains what happens to that output: which artifact is the evidence, which artifact is only a view of it, how a human adjudication is bound to the scanner run it adjudicated, and why a stable finding identity does not prove that the reasoning attached to it is still true.Two layers: evidence and presentation
One pinned Slither execution produces two kinds of output, and they serve different purposes.- Raw evidence
- GitHub presentation
What it contains: every result Slither reports, including results located entirely inside third-party dependencies and repeated copies of the same finding.Where it goes: the raw JSON feeds the triage completeness gate and the scanner receipt. Raw JSON and raw SARIF are both kept as unmodified workflow artifacts.What it is authoritative for: what the scanner observed. The evidence chain reads only the raw JSON; it has no SARIF input at all.
Why a projection exists
Uploading the raw SARIF directly turned every raw result into its own GitHub alert. Two properties of the pinned toolchain made that view misleading:- Dependency-only results. Findings located entirely inside OpenZeppelin library code are real scanner output, but they are not useful alerts about this repository.
- Compilation-unit copies. The prototype is compiled one entry point at a time, so a source file reached from several entry points is reported several times. GitHub does not collapse byte-identical results, so one finding appeared as several alerts.
- Drop dependency-only results. Ownership is decided from the raw JSON across every element of a finding, not only the primary location SARIF carries. A finding that involves project code and a dependency does not silently disappear: the upload fails and a human decides. The single narrow exception is compiler-version lints whose primary location is a dependency and whose only project elements are
pragmadirectives; each such exclusion is listed by name, and the raw SARIF keeps it. - Collapse exact copies. Copies collapse only when every member is byte-identical in both the raw JSON and the SARIF. If two distinct project findings would collapse into one alert, the projection fails rather than keep one and hope.
New findings stay visible
Adjudication completeness is a separate gate, and it never suppresses presentation. A new, structurally valid finding that nobody has triaged yet is uploaded to GitHub and turns CI red. The finding is visible in Code Scanning while the build explains why it is failing.Evidence lineage: subject, triage and container
Triage is a committed file,slither-triage.json, holding one human adjudication per distinct finding. A receipt, SCANNER_EVIDENCE.json, binds that adjudication to the scanner run it adjudicated. The repository distinguishes three commits:
Carrying a scanner result from one commit to another is licensed only by byte equality of the scanner input scope (the contracts tree, the dependency import closure and the scanner configuration). Equal finding counts, equal raw hashes and commit ancestry are not enough: all three once held across a change that moved 21 of 33 adjudicated findings.
A rationale edit is not evidence-neutral
The receipt generator requires the triage file on disk to be byte-identical to the triage file at the receipt’s declared triage subject. That binding is what stops a receipt from claiming one adjudication while a different one is applied. It has a consequence that is easy to miss: changing only the prose of a rationale changes the triage bytes, so the old receipt no longer describes the triage in the tree. The correction of two false-positive rationales inv0.13.15 showed this directly:
- the scanner findings were unchanged, and the receipt’s source subject did not move;
- both classifications stayed
FALSE_POSITIVE; - only the rationale text changed;
- so a successor triage subject and a republished receipt were still required.
The v0.13.15 commits, for reviewers
The v0.13.15 commits, for reviewers
Read the receipt itself:
SCANNER_EVIDENCE.json at v0.13.15.Stable identity, stale rationale
A stable finding identity proves that the scanner is still describing the same finding. It does not prove that the human rationale attached to that finding is still true after the surrounding code changes.
semanticId): a hash over the detector, each element’s file, element chain and signature, and the detector’s message with line references removed. It contains no line numbers, so a finding that only moves keeps its identity and its adjudication. That is deliberate. In the same drift described above, an earlier location-based key re-keyed 21 of 33 adjudicated findings when code merely moved, and reported them as untriaged.
Stability of identity is the right property for matching. It is the wrong property for trusting. The triage validation gate proves that every triage key still matches a live finding; it never reads rationale prose. A rationale can therefore become false — or be false from the start — while its finding stays byte-stable and every gate stays green.
Two findings in the prototype showed both ways this happens.
A premise that was never true — zero-default movement in egress (GitHub alert #148)
A premise that was never true — zero-default movement in egress (GitHub alert #148)
Finding:
uninitialized-local on the local moved in the kernel’s egress.Old premise: moved “is assigned on every path” and is “never read before assignment”.Why it was false: the ERC-20 branch assigns moved only when the vault holds a positive balance of the asset. When the balance is zero, nothing assigns it.Correct rationale: a Solidity numeric local holds zero until assigned — it is never indeterminate. A zero-balance ERC-20 egress intentionally attempts no transfer, and emitting Egressed(asset, destination, 0) reports the correct movement: none. The classification stays FALSE_POSITIVE on the corrected argument.Pinned by execution (P-148): a vault egressing an ERC-20 it holds none of succeeds, moves nothing, emits Egressed(asset, destination, 0) and changes no other state. The positive control shows the same revert-on-transfer token with a balance is reached and refused, so the zero-balance result is attributable to the premise and not to a broken fixture.A premise invalidated by a later change — verifier admission in initialize (GitHub alert #349)
A premise invalidated by a later change — verifier admission in initialize (GitHub alert #349)
Finding:
reentrancy-events on the factory’s deployVault, which deploys a clone, calls its initialize, then emits VaultDeployed.Old premise: the clone’s initialize “makes no external call”. That was accurate when written.What changed: the verifier-provenance lane (SD-11) made initialize call the verifier authority the factory binds — one read-only STATICCALL. Slither’s message names only the outer initialize call, so the finding never changed, and the entry was carried forward without its prose being re-read.Correct rationale: the static execution context propagates through every frame the authority opens, so a callback cannot CREATE2, SSTORE or LOG — it cannot deploy a second vault or emit a second event. A callback into the half-built clone’s initialize is refused because the initialized flag is set before the call. The factory holds no mutable state. The classification stays FALSE_POSITIVE on the corrected argument.Pinned by execution (P-349): a hostile authority’s reentrant deployVault halts with a state-change-during-static-call failure; no second vault or event appears and the factory is unchanged. Two controls make the result attributable: the identical callback from an ordinary CALL frame does deploy a vault, and a kernel mutant that makes the admission call with CALL instead of STATICCALL does reenter. The STATICCALL is the controlling mechanism — not the fixture and not the gas.Pinning a rationale’s premises
The remedy is to turn the premises a rationale depends on into executable tests, so that a premise that stops holding turns a test red instead of leaving stale prose behind a green gate:- State the premise as behaviour. Each premise test exercises the real kernel and factory, not a description of them.
- Pair every attack with a positive control. A refusal only counts when the same seam is shown to be reachable, so the test cannot pass because its setup is broken.
- Tie the prose to the tests. A separate check fails if a committed rationale drops its citation to the premise tests or reintroduces a withdrawn premise. It checks phrases and citations; it does not, and cannot, prove English prose true.
Accepted does not mean erased
The triage uses four classifications:
An accepted residual is not a false positive, and it is not hidden. It stays visible because:
- the scanner still observes the pattern, and the evidence should say so;
- the tradeoff is intentionally documented, and visibility keeps that documentation attached to the code it describes;
- a future code change can invalidate the reasoning that made the tradeoff acceptable — the same failure described in stable identity, stale rationale;
- an open alert gives every reviewer, not only the original adjudicator, the chance to notice that drift.
Status at the v0.13.15 checkpoint
Measured at
walletwall-vault v0.13.15 (6463caee, 19 September 2026). These figures are a dated snapshot of an operational view. They are not architectural constants, and any later code change can move them.
At this checkpoint, GitHub Code Scanning exposes 26 intentionally retained observations. Twenty-three are accepted residuals or design tradeoffs: the 21 vNext kernel findings classified
ACCEPTED_DESIGN_TRADEOFF, and two calls-in-a-loop observations in the separate Slither scan of the repository’s main contracts/ tree (the current vault contracts, outside the vNext prototype). Three are separate source, style or conformance observations still under review, all in that same scan:
- #110 — an interface/declaration conformance review;
- #111 and #115 — naming-style observations.
How the open-alert count moved from 309 to 26 — and what that was not
How the open-alert count moved from 309 to 26 — and what that was not
The drop in open alerts was not 283 vulnerabilities fixed. It was mostly scanner-presentation reconciliation. The raw evidence stayed complete throughout, and accepted residuals stayed visible.
What this evidence does not establish
The scanner receipt states its own limits. In summary:- Detector coverage bounds the result. Slither cannot report a defect its detector model cannot express.
- No Solidity result from CodeQL. GitHub CodeQL has no Solidity extractor, so CodeQL runs cover the prototype’s TypeScript tooling only.
- Identity is a judgement at the edges. A finding reported under a different element chain presents as one finding removed and another added — the conservative direction, but a judgement.
- A change the fingerprint cannot see is not proven harmless. Surrounding-source changes force re-adjudication without asserting more than that.
- Nothing outside the prototype’s contracts and declared dependency closure. Audit status, formal verification and verifier assurance are stated elsewhere and are not implied here.
Related
vNext kernel prototype status
What the prototype is, its boundary, and how verifier admission is checked.
Independent Authority
The assurance pattern behind per-mechanism proof credit.
Vault Boundaries & Disclosures
Custody, testnet and quantum-security boundaries for the Vault surfaces.
walletwall-vault on GitHub
The public repository that owns the contracts, the triage and the receipts.

