Repository navigation
fix(merchant): contain payment-goroutine panics - #360
Conversation
Rebase of OpenTollGate#360 onto rewritten main — carries only the panic-containment change (merchant.go + purchasesession_panic_test.go + CHANGELOG). Dropped: deploy-backup-20260730/ (leaked secrets, see incident #364), DEPENDS packaging change, pre-commit config, local-build script, token-recovery binary. Original-PR: OpenTollGate#360
0e1532e to
6c974f7
Compare
|
This branch (and #361, #362, #363) was based on pre-incident history and still carried The branch has been rebased onto rewritten main (
Dropped: the secrets directory, the Verified locally: build clean, and |
|
Thanks for this — panic containment in the payment goroutine is a solid hardening fix. The A few observations:
Test coverage looks good — verifies the <5s timeout, correct event kind (21023), correct notice code ( |
SEC-AUDIT T2 review — APPROVED (cold review) + verification evidenceCold review verdict (kimi-k3, cross-family): {"verdict": "APPROVED", "summary": "defer/recover correct and race-free; test pins the contract end-to-end; CHANGELOG accurate", "issues": [{"severity":"low","file":"src/merchant/merchant.go","line":479,"issue":"recover discards stack trace"},{"severity":"low","file":"src/merchant/merchant.go","line":479,"issue":"raw panic value into customer-facing notice"},{"severity":"nit","file":"src/merchant/merchant.go","line":477,"issue":"panic(nil)/Goexit degrade to 30s timeout (safe)"},{"severity":"nit","file":"src/merchant/purchasesession_panic_test.go","line":68,"issue":"no signature verification on notice"},{"severity":"nit","file":"src/merchant/purchasesession_panic_test.go","line":39,"issue":"no post-panic merchant-health check"}], "test_quality": "Strong — end-to-end contract test through real PurchaseSession, discriminates all three failure modes"}Panic containment test (empty-proofs / malformed-keyset mint does NOT crash daemon): The panic is recovered and surfaced as an explicit Full test suite on merged tree (main + PR #360): all packages pass except Merge check: PR branch is based on pre-#361 main (merge-base 14e4884) and still carries gonuts-tollgate v0.10.0 in cli/merchant + the felixfelix-bot fork replace in tollwallet. However, PR #360 does NOT touch any go.mod — a squash merge into current main yields main's v0.11.1 everywhere ( |
|
E2E results (follow-up to review): deployed head
The panic-containment change itself carries the strongest evidence in its unit test — re-verified on the cleaned branch: |
Rebase of OpenTollGate#360 onto rewritten main — carries only the panic-containment change (merchant.go + purchasesession_panic_test.go + CHANGELOG). Dropped: deploy-backup-20260730/ (leaked secrets, see incident #364), DEPENDS packaging change, pre-commit config, local-build script, token-recovery binary. Original-PR: OpenTollGate#360 Co-authored-by: Amperstrand <amperstrand@users.noreply.github.com> Co-authored-by: c03rad0r <1100745+c03rad0r@users.noreply.github.com>
Context
PurchaseSessionruns the walletReceivecall in a goroutine with norecover:Any panic in the wallet layer (e.g. a mint serving malformed keysets — see the empty-proofs
proofsToSwap[0]class of bugs) therefore kills the entiretollgate-wrtprocess, taking down every concurrently active customer session, and can be triggered by any unauthenticated portal client that submits a crafted token.Fix (W-1b: panic containment)
The goroutine now defers a recover that sends an explicit error into the existing buffered channel:
Fast-error contract: the caller's
select(which keeps its 30s timeout arm unchanged) receives this result immediately and returns a signedpayment-processing-failednotice — instead of process death or a misleading 30-secondpayment-processing-timeoutnotice.Channel safety: a panic can only fire before the normal send, so the deferred send never double-sends;
chis buffered(1), so the deferred send never blocks even if the caller has already taken the timeout arm.Test
TestPurchaseSessionPanicContainment(src/merchant/purchasesession_panic_test.go) drives the realPurchaseSessionwith a stubWalletPortwhoseReceivepanics, and asserts:payment-processing-failedcomes back,panicked,On unpatched code this test crashes the test binary (
panic: wallet exploded ... at merchant.go:475inPurchaseSession.func1) — i.e. it demonstrably bites.Note: running tests inside
src/merchantrequiresgo mod tidyfirst — a pre-existing go.mod staleness onmain(gonuts pinned at v0.10.0 while the code imports v0.11.1), unchanged by this PR and out of scope here.