Skip to main content
Full detail: docs/SECURITY_THREAT_MODEL.md and docs/audits/2026-07-28-production-readiness-audit.md.

Trust boundaries

Attacks defended

Blocked by client-side on-chain re-derivation. A self-consistent tamper fails because verification anchors to the chain. See DB-display-only trust.
The widget re-reads effectiveRecipient immediately before signing. Mismatch produces a recipient_changed state with no signature sent.
create-payment-intent performs an eth_call against MerchantRegistry.effectiveRecipient on the payment chain. No attestation returns HTTP 422 with a stable error code, so the customer never signs a transaction that would revert.
_shared/url-allowlist.ts is the single source of truth. merchants.webhook_url carries a CHECK constraint via is_safe_webhook_url(text), and the delivery function re-validates at fetch time. Blocks loopback, RFC 1918, 169.254 cloud metadata, CGNAT, IPv6 ULA and link-local, non-HTTP schemes, and non-canonical ports.
siwe-verify and siwe-whoami once shipped localhost:5173,localhost:8080 in their default allowlists and production never overrode them — a page on those ports could mint a real 24-hour production session, reproduced live. Loopback is now stripped unless an explicit flag is set and the Supabase URL is genuinely localhost; setting the flag on a hosted URL throws at boot. CORS refuses to credential-reflect loopback as a second line of defence.
siwe_nonces has explicit service_role ALL ALLOW plus anon and authenticated ALL DENY policies. The prior state was deny-by-default with no policies, which worked but left the intent undocumented — a future GRANT could have opened a hole silently.
Recipient and partner payout addresses are screened before being baked into a pool’s immutable init code. A sanctioned address in an immutable pool would permanently lock funds. The partner is screened here regardless of sign-in history, because a referrer auto-created from a code may never have signed in.

Known-unfixed and accepted

paymentId replay is keyed on paymentId alone, not (merchant, paymentId). With 128-bit random identifiers, cross-merchant griefing is infeasible at roughly 2⁻⁶⁴. Documented as deferred; a future redeploy could move to composite keying.
Open items from the 2026-07-28 audit, deliberately left:
  • RouterFee V3_SWAP_EXACT_OUT = 0x09 mis-encoding (that is V2’s opcode). Needs a redeploy, Phase 2 only.
  • Partner earnings always display $0.00, and partner-visible merchants RLS is incomplete.
  • Webhook 'none' secret option, and a test-versus-live signature mismatch.
  • TRON keeper drops expired intents.
  • TRON has no on-chain recipient anchor, so the trust guarantee does not extend there.

Economic attacks

Self-dealing at the floor. A merchant mints invoices at the minimum and pays them to themselves, recovering 99% while the platform eats the sponsored gas. Leverage was measured at 6.4:1 on Ethereum — roughly 2,100ofattackerspendtoburn2,100 of attacker spend to burn 13,700 of keeper float across 100,000 invoices. Largely defused once the merchant bears their own invoices’ gas: the attack costs the attacker more than it costs the platform. Cheap account creation. 10,000 fake merchant accounts cost nothing to create and could trigger keeper-funded pool deploys — 99onBase,99 on Base, **288,200 on TRON**. The cost is not the point on EVM; the unauthenticated ability to make the platform spend is. Closed by making pool deployment merchant-paid and a prerequisite for SDK access. Griefing the reimbursement cap. distribute() is permissionless, so anyone can call it with a cost parameter at the ceiling. They gain nothing but can shave a merchant’s payout up to the cap. Bounded nuisance rather than theft — provided the cap is min(bps, absolute) and tight. A bps-only cap on a large order authorises a very large claim. Address pre-derivation. For per-customer forwarders, the customer identifier must not be guessable, or a third party could pre-derive addresses and grief a merchant.

Fail-open decisions

Both sanctions layers fail open — an RPC outage returns unknown and allows the payment through. This is deliberate: a screening outage should not halt legitimate commerce, and the platform-level sign-in gate remains the enforcement point for merchants. Conversely, the economics floor must fail closed to a recalibrated conservative table, never to zero. An economics gate that fails open quotes a guaranteed loss.