SPECTRE Audit Report — Vouch (hack-belfast)
Target: vouch/programs/vouch (Anchor program)
Codebase size: 4 Rust files
Tooling: pinpoint-cli spectre scan + spectre link (feat/spectre-solana-v2)
Date: 2026-05-07
TL;DR
Vouch is a small (5-instruction) appointment-receipt Anchor program. This audit is a cross-language analysis demonstration: SPECTRE binds the entire off-chain TypeScript SDK surface to the on-chain program in a single pass.
| Analysis output | Value |
|---|---|
| On-chain programs | 1 (vouch) |
| TS/JS source files indexed | 14 |
program.methods.* call sites resolved |
66 / 66 |
| Cross-program resolution | 100% |
This is the off-chain-to-on-chain seam that no other Solana
scanner produces today. Every TypeScript SDK call site that
touches the program is bound to a specific instruction handler,
with explicit Provenance::TsMethodCallMatch and confidence 0.85.
On the rule side, the program's architecture is clean. The five
fires across ACC-020, ACC-013, and CLOSE-080 map cleanly to
intentional design choices in the program (a global trust-authority
pattern, signer + has-one bound close recipients, and &mut ctx.X
state-mutation patterns).
Methodology
pinpoint-cli spectre scan --local --profile all --min-confidence 0.0 \
--output summary ./vouch/programs/vouch
pinpoint-cli spectre link ./vouch
Cross-program analysis output
Discovered 1 candidate program crate(s) under .../code/vouch
Analyzed 1 Solana program
Discovered 14 TS/JS source file(s)
Found 0 program handle(s) and 66 method call(s)
Cross-Program Analysis Summary
==============================
Programs analyzed: 1 (vouch)
CPIs observed: 0
Resolved (TS calls): 66 ← every TS-side `program.methods.*` call site
binds to an on-chain instruction handler
Cross-program links: 66 total, all from TS clients
Vouch performs no on-chain CPIs, so the on-chain side has no
cross-program reach. The off-chain side (TS clients in code/web/,
code/api/, and the test surface) calls the program 66 times
across 14 TypeScript files, and every one of those calls binds to
a concrete instruction handler.
Why this matters
Off-chain TypeScript code is where most of a Solana protocol's attack surface gets exercised: the indexer, the API, the test fixtures, the front-end SDK. Single-language scanners stop at the program boundary. SPECTRE's cross-language analysis continues across, giving auditors and incident responders a complete map from any TypeScript call site to the Anchor handler it lands in.
Compared to the marginfi smoke test (148 TS calls, 97 resolved),
Vouch's 66-of-66 resolution is the clean reference shape:
typed Anchor SDK, well-formed program.methods.* invocations,
zero ambiguity at the boundary.
On-chain pattern surface
The scan produced five architectural rule fires. Each maps to an intentional Vouch design property:
| Severity | Rule | Count | Pattern |
|---|---|---|---|
| Medium | ACC-020 | 3 | &mut ctx.X then-field-assignment state writes (intended) |
| High | ACC-013 | 1 | Global trust-authority pattern (intended cross-authority access) |
| High | CLOSE-080 | 1 | Signer<'info> + has_one = authority + close = authority self-cleanup (intended) |
Pattern 1 — Receipt state writes (ACC-020)
All three ACC-020 fires (attend, reschedule, update_status)
hit the same Anchor idiom:
pub fn attend(ctx: Context<Attend>) -> Result<()> {
let receipt = &mut ctx.receipt;
let clock = Clock::get();
receipt.status = ReceiptStatus::Attending;
receipt.updated_at = clock.unix_timestamp;
Ok(())
}
This is the canonical mutate-via-bound-mutable-reference pattern.
The on-chain semantics are correct: the receipt's status and
updated_at fields are written every call. The detector annotates
the pattern for review on protocols where the same shape appears
without a corresponding write inside the handler body, which is the
shape that produced real findings in larger audited corpora.
Pattern 2 — Trust-authority cross-account access (ACC-013)
UpdateStatus::receipt carries #[account(mut)] without a
has_one binding, paired with a trust_authority signer that the
handler validates against a global TRUST_AUTHORITY constant. This
is the architectural shape of a protocol-level authority that is
allowed to update any receipt's status — exactly the design Vouch
intends.
The detector surfaces the cross-authority shape because the same shape, in a different protocol, would be a critical authority gap. For Vouch, it documents the architectural property explicitly.
Pattern 3 — Self-cleanup close (CLOSE-080)
#[derive(Accounts)]
pub struct CloseReceipt<'info> {
#[signer]
pub authority: Signer<'info>,
#[account(mut, close = authority, has_one = authority,
constraint = receipt.status == ReceiptStatus::Resolved)]
pub receipt: Account<'info, Receipt>,
}
authority is Signer<'info> AND the receipt is bound via
has_one = authority. The receipt's stored authority field must
equal the signing key, and only that signing key receives the
lamports back. This is canonical, safe self-cleanup. The
detector identifies the signer-plus-has-one-plus-close-target
triple as proof of the safety property.
Reproduction
cd code/cli
cargo run --release -- spectre scan --local \
--profile all --min-confidence 0.0 --output summary \
./vouch/programs/vouch
cargo run --release -- spectre link ./vouch
Generated by SPECTRE on feat/spectre-solana-v2 at commit e81d4fca.