fix: secure POST /transactions/submit against third-party XDR (#117) - #125
Open
Marvelg256 wants to merge 1 commit into
Open
fix: secure POST /transactions/submit against third-party XDR (#117)#125Marvelg256 wants to merge 1 commit into
Marvelg256 wants to merge 1 commit into
Conversation
…-app#117) Bind submissions to the authenticated wallet (source account, fee-bump inner source, or Soroban invocation auth), enforce a per-type operation allowlist, make submission idempotent per transaction hash via a unique-index migration, rate limit per wallet and IP, and persist records before Horizon submission so persistence failures surface instead of being silently dropped. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
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.
🔗 Related Issue
Closes #117
🔖 Title
Secure
POST /transactions/submit: wallet binding, operation allowlist, idempotency, and rate limits📝 Description
TransactionsService.submitTransaction()previously accepted any well-formed XDR, immediately submitted it to Horizon, and recorded it against the authenticated wallet — without ever verifying that the wallet was actually a party to the transaction. This PR closes that open-relay hole:TRANSACTION_SOURCE_MISMATCHerror before any Horizon call or DB write.invokeHostFunctionwhose function name matches the declaredtype(loan_create,loan_repay,deposit,withdraw,vendor_approve,vendor_suspend) and, when configured, must target the contract owned by that flow. Mismatches are rejected withTRANSACTION_TYPE_MISMATCH/TRANSACTION_OPERATION_NOT_ALLOWED.transaction_hashandhash(with pre-existing-row dedupe); the service checks for an existing record before submitting and returns the original record (duplicate: true) instead of re-submitting, with the unique-constraint violation as the concurrency backstop. Duplicate hashes can no longer create duplicate rows or double Horizon submissions.WalletThrottlerGuardkeys@nestjs/throttleron the authenticated wallet (the repo's established middleware), and the route is limited to 10 req / 60 s per wallet AND per IP via the global guard.TRANSACTION_PERSISTENCE_FAILEDinstead of the old fire-and-forget behavior, and the transaction hash is always known to the status checker / indexer reconciliation paths.🔄 Changes Made
src/modules/transactions/transactions.service.ts— allowlist + source/auth validation, hash idempotency, persist-before-submit flow insubmitTransactionsrc/modules/transactions/wallet-throttler.guard.ts— new per-wallet throttler guardsrc/modules/transactions/transactions.controller.ts— per-wallet + per-IP rate limits, Swagger responses updated (400 codes, 429, 500)src/modules/transactions/dto/submit-transaction-request.dto.ts— documents the enforced guaranteessrc/modules/transactions/dto/submit-transaction-response.dto.ts—statusreflects the recorded status; newduplicateflagsupabase/migrations/20260825000001_add_unique_transaction_hash.sql— unique indexes + dedupetest/unit/modules/transactions/transactions.service.spec.ts— happy path + every rejection branch (source mismatch, fee-bump inner source, allowlist/function/contract mismatch, classic ops, duplicate pre-check, unique-violation race, persistence failure, Horizon error mapping)test/unit/modules/transactions/wallet-throttler.guard.spec.ts— new guard tracker teststest/unit/modules/transactions/transactions.controller.spec.ts— compiles withThrottlerModulecontext/progress-tracker.md— documented🗒️ Additional Notes
loan_createuses the wallet as source and passes the strict path.transactions.repository.tswas reviewed but not modified:backfill'supsert(onConflict: 'transaction_hash')remains compatible with the new partial unique index, andfindForReconciliationis unaffected.supabase/migrations/(outsidesrc//test/) as required by the issue andcontext/code-standards.md.✅ Verification
npm run build— zero TypeScript errorsnpm test— 30 suites / 358 tests passing (test count increased from baseline, per the mandatory checklist)