fix(lua): fee-sponsored compute-cap bypass + replay fail-open#225
Conversation
…ardening) Two security criticals that survived #216 rated only "required": - Fee-sponsored compute-unit-price cap bypass (false-paid): the real MPP verifier (solana/verifier.lua -> instructions.parse_compute_budget) applied the general 5,000,000 microLamport cap even on server-funded charges, letting a co-signed charge set a ~200x priority fee (merchant drain, audit #25). Now passes fee_sponsored=(fee_payer~=nil) and enforces the tight 10,000 cap, matching the Rust spine validate_compute_budget_instruction and the Ruby verifier. Client-paid charges keep the general cap. - Replay fail-open: build_mpp_server fell back to a volatile in-memory replay store on every network with only a warning; on multi-worker devnet/mainnet a settled signature could be replayed for a second on-chain settlement. Now fails CLOSED outside localnet (requires a durable, process-shared replay_store), mirroring the Ruby Mpp.create requirement. BREAKING. Plus the #216 Lua hardening on the #219 base. Regression tests added for both. busted 617 passed/1 skipped/0 failed; luacheck 0/0; coverage 90.26% >= 90.
Greptile SummaryThis PR hardens the Lua pay-kit implementation with two critical security fixes — fee-sponsored compute-cap enforcement (audit #25) and replay fail-closed outside localnet — plus the confirm-before-consume ordering fix for x402 broadcast flow. The changes are well-scoped and the core security logic is correct.
Confidence Score: 5/5The two critical security fixes are logically correct and well-covered by regression tests; no correctness issues were found in the changed code paths. The fee-sponsored compute cap, replay fail-closed enforcement, and confirm-before-consume reordering all implement the stated invariants correctly. Store interactions are exercised by targeted tests. The only findings are code-quality issues: replay_store_is_shared is defined identically in three modules, and default_sleep has a CPU busy-wait fallback that would only fire in environments missing both ngx and LuaSocket. The duplicated replay_store_is_shared in mpp/server/init.lua, mpp/init.lua, and mpp/server/charge_handler.lua is the most maintenance-sensitive area; divergence between the three copies would silently weaken the shared-store gate on one path. Important Files Changed
Reviews (9): Last reviewed commit: "Merge remote-tracking branch 'origin/spl..." | Re-trigger Greptile |
|
@greptile-apps please review |
… fix/lua-security-hardening
…o fix/lua-security-hardening
…o fix/lua-security-hardening
…o fix/lua-security-hardening
fix(lua)— Lua security hardening on the #219 base, scoped as one language PR per the maintainer split policy. Same re-delivery model as #222/#224.Criticals (each with a planted regression that fails pre-fix)
solana/verifier.lua→instructions.parse_compute_budgetapplied the general 5,000,000 microLamport cap even on server-funded charges. A co-signed charge could set a ~200x priority fee (merchant drain, audit fix: better compatibility / decimals handling #25). The hooks path already had the tight cap; the real verifier path (the one the adapter uses) missed it. Now derivesfee_sponsored = fee_payer ~= niland enforces the tight 10,000 cap — matching Rustvalidate_compute_budget_instruction(ix, fee_payer.is_some())and Rubyverifier.rb. Client-paid charges keep the general cap.build_mpp_serverfell back to a volatile in-memory replay store on every network with only a warning. On multi-worker devnet/mainnet a settled signature could be replayed for a second on-chain settlement (double-spend). Now fails CLOSED outside localnet: a durable, process-sharedreplay_storeis required, mirroring the RubyMpp.createrequirement. BREAKING — a non-localnet MPP deployment on the silent volatile fallback now errors untilconfig.mpp.replay_storeis wired; affected in-tree tests updated to inject a store, and a Kong operator doc note added.Review follow-ups
confirmed/finalized, tolerates propagation lag and transient RPC errors, and only then atomically consumes replay state.language: "lua"and preserves a recoverable vector ID even when malformed JSON is rejected, so harness accounting cannot silently lose the case.Verification