fix(mpp): port Rust charge audit hardening to all language SDKs#169
Merged
Conversation
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: 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>
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>
EfeDurmaz16
approved these changes
Jun 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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):
#24weak HMAC secret accepted → enforce ≥32-byte floor (config + env)#25priority-fee drain → tight compute-unit-price cap in fee-sponsored mode#15shared default realm → per-recipient derived realm#37unknown network slug silently treated as mainnet → boot allowlist#38primary-in-splits +ataCreationRequiredATA-recreate drain → reject at issuance#21no split validation at issuance → count/parse/positive/dedup/overflow#28arbitrary Token-2022 mint → legacy program → resolve correctly / no silent fallback#9WWW-Authenticateparser missing size cap → 16 KiB capClient (TS / Go / Python / Kotlin / Swift):
#10signs untrusted challenges → always-on expiry refusal + opt-in amount/network guards#20auto-funds split ATAs → only whenataCreationRequired#26signs unknown Token-2022 mints → refuse without opt-in#42SPL decimals silently default to 6 → require decimalsLanguage-specific high-severity: TS
#3replay-after-confirm, Go+Python#2echoed-amount verify (deleted), PHP#19unvalidated issuance, Go+Lua#16feePayer-without-signer, push-mode opt-in (#5).Not in this PR
TypeScript's server HMAC/realm/comparison logic lives in the external
mppxdependency (findings#1/#24/#15/#9) — documented for upstream innotes/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