# Solana fund-safety remediation

Completed 1 October 2026 UTC. This records the changes made after the [adversarial audit](/audits/groth16/solana/). The original report remains a snapshot of the vulnerable revision. The Solana and DogeOS fixes were committed together, merged into staging through frontend PR 44 and relayer PR 18, and deployed to staging. See the deployment record (`fund-safety-staging-20261001.md`). The protocol was not changed. Hosted encrypted-journal recovery was verified; no funded Solana settlement or historical archival-recovery drill was performed.

| Audit finding | Remediation |
| --- | --- |
| F1: definite unsigned refusal strands a selected note | The relayer HTTP route now forwards the service's durable `submissionNotAttempted` finding as a narrowly validated boolean. The browser closes only a proven unsigned attempt and can prepare a new proof. A signed or ambiguous attempt remains locked for reconciliation. |
| F2: unrelated PDA mention blocks recovery | Deposit and settlement recovery page through finalized signatures and verify the actual pool event, expected action and commitment-account slot. A newer unrelated transaction mentioning the address no longer masks the creation event. |
| F3: zero-output withdrawals exhaust the journal | A finalized withdrawal with no output notes now reaches a terminal journal state after its payout is verified. Existing records can migrate through reconciliation. |
| F4: processed transaction retried after blockhash expiry | The relayer persists the locally derived signature before sending, waits for finalized status, and does not treat mere blockhash expiry or ambiguous RPC responses as proof that the signed transaction failed. Uncertain attempts remain fenced for operator reconciliation. |
| F5: settled output secret exists only in browser storage | Before deposit signing or relay submission, the sender uploads an AES-GCM encrypted, sender-owned journal snapshot to the scoped PostgreSQL recovery store and checks its ID and SHA-256 digest acknowledgement. Missing durability acknowledgement refuses submission. Restore recovers the ciphertext after browser loss, while the UI distinguishes finalized settlement from pending note delivery and offers explicit retry. |

Further review found two related action-confusion paths and closed them. The relayer's durable nullifier ledger now binds a replay to a canonical SHA-256 digest of the entire action, including the destination, amount, fee, roots, inputs, outputs, proof, network and program. A different action with the same nullifiers returns a conflict; legacy settled rows without a digest fail closed. Frontend finalization likewise checks the event's full expected intent. This is especially important for zero-output withdrawals, where nullifiers and an empty commitment list alone cannot distinguish different payouts.

The backup uses immutable IDs so a changed snapshot after a definite unsigned refusal gets its own version. Restore checks each candidate against finalized chain evidence before using it; where multiple unconfirmed versions remain, the wallet presents the exact destination, amount and fee for the user to choose. The journal is also guarded against cross-tab changes between preparation, backup acknowledgement and delivery. Corrupt remote rows are quarantined individually, without hiding valid backups. Corrupt local rows remain untouched while other recoverable records are processed.

## Verification

- Privacy frontend: 151 suites and 1,116 tests passed; TypeScript typecheck, Prettier check, production build and `git diff --check` passed. Regression coverage includes definite versus ambiguous refusal, distractor PDA signatures, zero-output completion, backup refusal before signing, authenticated restore, delivery failure, conflicting backup versions, corrupt rows and cross-tab interleavings.
- Relayer: Solana pool suite 55/55 passed. PostgreSQL-backed engine data suite 181/181 passed, including 4/4 real HTTP backup and delivery round-trip tests against disposable PostgreSQL 16. Prettier and `git diff --check` passed.
- The broader relayer `npm test` command stops at an unrelated legacy generic-EVM smoke test because `GENERIC_EVM_RELAYER_DISABLED` is set in this checkout. The Solana and data-service suites above passed independently.
- No live RPC, device-wallet, deployed-bytecode, hosted backup/restore or mainnet transaction test was performed.

## Rollout requirements and residual risk

Follow the PostgreSQL and Solana rollout procedure (`.codex-worktrees/solana-audit-fixes-stealthos-relayer/docs/POSTGRES_SOLANA_SECURITY_ROLLOUT.md`). A hosted deployment must have the reviewed schema, restricted grants, durable PostgreSQL commits, backup and restore jobs, and `ENGINE_RECOVERY_DURABILITY_ACK=true` only after those checks. Confirm a wallet can PUT, verify and GET its encrypted journal after clearing browser storage; the client refuses new submissions if that acknowledgement is unavailable.

Fresh-browser recovery also needs historical finalized transaction events to reconstruct Merkle leaves and payout details. A commitment PDA alone does not contain that history. Provide archival RPC or authenticated retained leaf history for the full promised recovery period and test restoration after ordinary RPC history has aged out. Keep the public wallet origin stable because the Solana spending-key derivation signs it. Signed but uncertain relayer attempts intentionally need operator reconciliation; they must not be automatically resubmitted.

These local tests support the source-level fixes, but they do not establish a safe real-funds release until the hosted durability and historical-recovery drills pass.
