Skip to content

fix(mpp): port Rust charge audit hardening to all language SDKs#169

Merged
lgalabru merged 16 commits into
mainfrom
fix/cross-language-audit
Jun 17, 2026
Merged

fix(mpp): port Rust charge audit hardening to all language SDKs#169
lgalabru merged 16 commits into
mainfrom
fix/cross-language-audit

Conversation

@lgalabru

Copy link
Copy Markdown
Collaborator

What

The Rust MPP/charge implementation was audited and hardened in #150. We cross-checked every other language SDK against those 45 findings and found the fixes had never been propagated — the same vulnerability clusters were present in all of them. This PR ports the fixes.

Methodology (all written up in notes/audit-cross-check/): per-language exposure analysis → adversarial verification (filtered first-pass false positives) → remediation → independent closure audit (re-verified each fix against the changed code, caught 2 real gaps that were then fixed).

Universal gaps closed

Server (TS / Go / Python / Ruby / PHP / Lua):

  • #24 weak HMAC secret accepted → enforce ≥32-byte floor (config + env)
  • #25 priority-fee drain → tight compute-unit-price cap in fee-sponsored mode
  • #15 shared default realm → per-recipient derived realm
  • #37 unknown network slug silently treated as mainnet → boot allowlist
  • #38 primary-in-splits + ataCreationRequired ATA-recreate drain → reject at issuance
  • #21 no split validation at issuance → count/parse/positive/dedup/overflow
  • #28 arbitrary Token-2022 mint → legacy program → resolve correctly / no silent fallback
  • #9 WWW-Authenticate parser missing size cap → 16 KiB cap

Client (TS / Go / Python / Kotlin / Swift):

  • #10 signs untrusted challenges → always-on expiry refusal + opt-in amount/network guards
  • #20 auto-funds split ATAs → only when ataCreationRequired
  • #26 signs unknown Token-2022 mints → refuse without opt-in
  • #42 SPL decimals silently default to 6 → require decimals

Language-specific high-severity: TS #3 replay-after-confirm, Go+Python #2 echoed-amount verify (deleted), PHP #19 unvalidated issuance, Go+Lua #16 feePayer-without-signer, push-mode opt-in (#5).

Not in this PR

TypeScript's server HMAC/realm/comparison logic lives in the external mppx dependency (findings #1/#24/#15/#9) — documented for upstream in notes/audit-cross-check/MPPX-UPSTREAM-REPORT.md.

Testing

All suites green: Go (go test ./...), Python (264 MPP tests), Ruby (449), PHP (431), Lua (602), TypeScript (418), Swift (125), Kotlin (233 + coverage gate). Each fix has added/updated tests.

Reviewable commit-by-commit — one commit per language.

🤖 Generated with Claude Code

lgalabru and others added 9 commits June 15, 2026 16:17
Server: weak-secret floor (#24), fee-sponsored compute cap (#25),
per-recipient realm (#15), network allowlist (#37), primary-in-splits
ATA guard (#38), split validation at issuance (#21), token-program
resolution (#28), WWW-Authenticate size cap (#9), delete echoed-amount
VerifyCredential (#2), feePayer-without-signer gate (#16), push-mode
opt-in (#5). Client: untrusted-challenge guards + expiry (#10),
flag-gated split ATA creation (#20), unknown-Token-2022 gate (#26),
required SPL decimals (#42), method/intent gate (#17).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server: #24, #25, #15, #37, #38, #21, #28, #9, delete echoed-amount
verify_credential (#2), push-mode opt-in (#5), parse_units strictness
(#44/#45). Client: untrusted-challenge guards + expiry (#10),
unknown-Token-2022 gate (#26), required SPL decimals (#42), confirmed
blockhash commitment (#36).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server-only: weak-secret floor (#24), fee-sponsored compute cap (#25),
per-recipient realm (#15), network allowlist (#37), primary-in-splits
ATA guard (#38), split validation at issuance (#21), on-chain
token-program resolution (#28). Bumps harness default secret to >=32 bytes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server-only: weak-secret floor (#24), fee-sponsored compute cap (#25),
per-recipient realm (#15), primary-in-splits ATA guard (#38), split
validation at issuance (#21), WWW-Authenticate size cap (#9), issuance
request validation with Adapter-pinned currency/network/recipient (#19),
token-program resolution (#28), push-mode opt-in (#5). Bumps harness
default secret to >=32 bytes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server-only: weak-secret floor (#24), fee-sponsored compute cap (#25),
per-recipient realm (#15), network allowlist (#37), primary-in-splits
ATA guard (#38), split validation at issuance (#21), token-program
resolution (#28), WWW-Authenticate size cap (#9), feePayer-without-signer
gate (#16), push-mode opt-in (#5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In-repo (@solana/mpp): replay reservation + post-timeout recovery (#3),
flag-gated split ATA creation (#20), unknown-Token-2022 gate (#26),
required SPL decimals (#42), split validation at issuance (#21), network
allowlist (#37), primary-in-splits ATA guard (#38), fee-sponsored compute
cap (#25), untrusted-challenge guards + expiry (#10), challenge-header
size cap (#9). Server HMAC/realm/comparison findings live in the external
mppx dependency; see notes/audit-cross-check/MPPX-UPSTREAM-REPORT.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Client-only: required SPL decimals (#42), untrusted-challenge guards +
always-on expiry (#10), flag-gated split ATA creation (#20),
unknown-Token-2022 gate (#26), WWW-Authenticate size cap (#9).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Client-only: unknown-Token-2022 gate (#26), untrusted-challenge guards +
always-on expiry (#10), flag-gated split ATA creation (#20), required SPL
decimals (#42), WWW-Authenticate size cap (#9), confirmed blockhash
commitment (#36).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eports

Per-language exposure analysis, adversarial verification, closure audit,
the cross-language matrix (SUMMARY.md), and the mppx upstream report.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lgalabru lgalabru requested a review from EfeDurmaz16 June 15, 2026 20:18
lgalabru and others added 6 commits June 15, 2026 16:37
Fixes the Lint & Format CI job (pnpm format:check) — the audit edits to
charge.test.ts, client/Charge.ts, server/Charge.ts were not Prettier-clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Annotate the _base kwargs dict as dict[str, Any] so pyright doesn't widen
the heterogeneous literals to a union and reject every Config(**kwargs)
argument. Fixes the Python tests CI job (pyright step).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The php harness server lagged two library audit fixes, failing the PHP
harness CI job:
- #5 made push-mode credentials opt-in; enable acceptPushMode when the
  harness drives the server in push mode (charge-push expects 200).
- #21 made too-many-splits a refuse-to-issue; catch that construct-time
  rejection and surface it as a 402 (charge-splits-too-many expects 402),
  mirroring the TypeScript fixture's challenge_unavailable allowlist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit #21 made the Go server refuse to issue a >8-split challenge; the
harness server returned that as 500. charge-splits-too-many expects 402.
Surface the issuance config rejection as a 402 (mirrors the PHP fixture).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Same as the Go/PHP harness fixtures: catch the #21 refuse-to-issue from
charge_with_options and surface it as the 402 the conformance suite expects
instead of crashing the server.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit #24 added a 32-byte minimum HMAC secret floor; the Go playground E2E
set a 20-byte MPP_SECRET_KEY, so the server exited at boot ("secret key must
be at least 32 bytes") and Playwright hit connection-refused.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cross-check analysis/verification notes were scratch working docs, not
part of the shipped change. Remove them from the PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lgalabru lgalabru merged commit b23324b into main Jun 17, 2026
30 checks passed
@lgalabru lgalabru deleted the fix/cross-language-audit branch June 17, 2026 20:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants