{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"5040f606-5ef4-456e-85e9-a688af368ea5","kind":"research","nodes":[{"acceptedSubmissionHash":null,"dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"panel","kind":"research","role":"review","skillHash":null,"skillId":null,"state":"accepted"}],"objective":"Adversarial review of the swarm's paid-request flow as documented on imd.fun/docs 'Paid requests': quote -> 402 challenge -> Permit2 PermitWitnessTransferFrom to the x402 exact Permit2 proxy (0x402085c248EeA27D92E8b30b2C58ed07f9E20001) plus an EIP-712 QuoteApproval signature -> admission. Look for replay across orders, quote substitution, payment front-running or griefing by a third party who sees the signature, what happens if the server settles but never admits, and bearer-token leakage. Rank each finding by severity and give the smallest protocol change that fixes it.","parentJobId":null,"planHash":"c85818250ac6a08414fc4b4c15ba7bc05ae55586dba2ab08ad582c94aff4a7af","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"5040f606-5ef4-456e-85e9-a688af368ea5","publication":{"commit":null,"deliveredAt":null,"repoUrl":null},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[{"answer":"I've finished the review. One caveat first: the public docs don't list the fields of the `QuoteApproval` type or say what `requesterScopeHash` is computed from. Several findings below therefore say \"if the approval doesn't include X\". Each fix is also something the server should check for, whatever the signature covers.\n\n## What the docs say (what I relied on)\n- There are two credentials. The bearer is \"A random 32-byte secret you generate, as 64 hex characters\", sent as `Authorization: Bearer`. It is the only credential for a paid request and for `GET /requests/:id` ([imd.fun/docs](https://imd.fun/docs)).\n- The flow: `POST /requests/quote` with a `requestKey` you reuse to retry. With no body, the server returns `402` with a `PAYMENT-REQUIRED` challenge carrying `requesterScopeHash`, `resourceUrl` and `input`. You then resend with a `PAYMENT-SIGNATURE` header and `{quoteSignature}` and get `202`. \"the wallet signs twice, the Permit2 payment and an EIP-712 approval of that payment for this quote\".\n- Status values are `quoted, payment_pending, admission_pending, admitted, payment_failed, expired`. \"Quotes last 600 seconds.\" \"The server's wallet pays the gas.\" `GET /requests/capabilities` is \"the live source for price, token, recipient and quote lifetime\".\n- The x402 exact Permit2 witness has only two fields, `address to` and `uint256 validAfter`. The spec's comment says \"post-audit: extra removed from Witness\". The one-time-use value (nonce) and the expiry time (`deadline`) are in the Permit2 authorization, not the witness. The proxy's `settle` has no restriction on who can call it ([coinbase/x402 exact EVM spec](https://github.com/coinbase/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md)). The proxy is deployed at the same address on every chain ([go pkg docs](https://pkg.go.dev/github.com/x402-foundation/x402/go/v2/mechanisms/evm)).\n\nThe key structural fact: **the Permit2 signature says nothing about which order it pays for.** It only says \"move 0.5 IMD to `to`, after `validAfter`, before `deadline`, using nonce N.\" Anything tying the payment to an order has to come from the `QuoteApproval` and from what the server checks.\n\n---\n\n## Findings, most severe first\n\n### 1. HIGH: The server settles but never admits, and the user has no recourse\n**Step attacked:** from settlement to admission (`payment_pending` → `admission_pending` → `admitted`).\n- None of the documented statuses means \"paid, not admitted\". There's no refund state, no receipt, and no retry rule.\n- The server's wallet broadcasts the settlement, so the server alone decides when it happens. It can:\n  - settle after the 600-second quote life and then report `expired`;\n  - crash between broadcast and admission;\n  - be malicious and keep the IMD.\n- Either way the user has paid 0.5 IMD, has nothing signed to show for it, and the only view of the order is a server-controlled `GET /requests/:id`.\n\n**Smallest fix:**\n- (a) The client sets the Permit2 `deadline` to no later than the quote's `expiresAt`. The server rejects any permit where `deadline > expiresAt`. That stops settlement after expiry at the chain level.\n- (b) Add one rule: **a settlement confirmed on-chain for an order's nonce always leads to admission.** Admission is retried until it succeeds, regardless of quote expiry. Add a terminal `refund_due` status for orders that can never be admitted.\n- (c) Once settled, the server returns a signed receipt `{orderId, nonce, txHash}` the user can use as evidence.\n\n### 2. HIGH: A third party settles first (front-running and griefing)\n**Step attacked:** the Permit2 `PermitWitnessTransferFrom` sent to the proxy.\n- Anyone who sees the `PAYMENT-SIGNATURE` can call the proxy's `settle` themselves. That includes:\n  - the mempool, once the server broadcasts;\n  - a TLS-terminating proxy;\n  - server logs or an observability vendor;\n  - a malicious browser extension.\n- Because the witness pins `to`, the money still reaches the IMD recipient, so nobody steals it. But the nonce gets used up outside the server's own flow. The server's settle transaction then reverts:\n  - the server pays gas for nothing;\n  - the order likely goes to `payment_failed`;\n  - the user has paid and gets no admission.\n- An attacker can repeat this against every order. It costs them only gas, and it breaks every paid request.\n\n**Smallest fix:** no contract change needed.\n- Derive the Permit2 nonce from the order: `nonce = uint256(keccak256(\"imd.order\", orderId, requesterScopeHash))`, and have the server require exactly that value.\n- When the server's settle reverts because the nonce is already used, it looks for the proxy/Permit2 transfer event for that nonce. If it finds one, the order counts as paid and moves to `admission_pending` (then finding 1(b) applies).\n- Front-running then does no harm. Also send settlements through a private mempool to save gas.\n\n### 3. HIGH (depends on the approval fields): A relayer takes over the order by swapping the bearer or order\n**Step attacked:** the `QuoteApproval` signature, and admission ownership.\n- Suppose the approval covers the quote's contents (action, input hash, price, expiry) but not **`orderId` and `requesterScopeHash`**.\n- Then an attacker who captures both signatures can:\n  1. create their own quote under their own bearer, with the same `action`/`input`;\n  2. attach the victim's `PAYMENT-SIGNATURE` and `quoteSignature`;\n  3. submit before the victim does.\n- The victim's IMD pays for an order the attacker owns. The attacker gets the status, result and attestation URLs. The victim's own order then fails because the nonce is already used.\n\n**Smallest fix:**\n- The `QuoteApproval` struct must include `orderId`, `requesterScopeHash`, `resourceUrl`, and the Permit2 `nonce` and `deadline`.\n- The server checks that each one matches the order the request is being submitted against.\n- Publish the typed-data definition so wallets and clients can show it and check it.\n\n### 4. HIGH→MEDIUM: Replay across orders and double admission\n**Step attacked:** pairing each `QuoteApproval` with a Permit2 payment.\n- Suppose the approval doesn't commit to a specific Permit2 nonce, or the server doesn't enforce a unique nonce across orders. Then a user signs approvals for orders B, C and so on that all point to the permit already settled for order A. Or they resubmit order A's pair against order B.\n- If the server checks only that the signatures are valid, one payment admits many orders.\n- The reverse also happens: a buggy server settles one permit and credits it to the wrong order.\n- The documented `requestKey` idempotency (\"Reuse it to retry the same quote\") only removes duplicates within a single scope and key. It doesn't stop payments being reused across orders.\n\n**Smallest fix:**\n- Give the database a `UNIQUE(chainId, payer, permit2Nonce)` constraint mapped to exactly one `orderId`.\n- Combined with the order-derived nonce from finding 2 and nonce/deadline in the approval from finding 3, a payment can be consumed by one order only.\n\n### 5. MEDIUM: Quote substitution between the quote and the 402 challenge, including recipient redirect\n**Step attacked:** the quote and the `402 PAYMENT-REQUIRED` challenge.\n- The 402 challenge echoes back `input` and the payment requirements. Price, token and recipient come from the \"live\" capabilities endpoint.\n- Several parties could swap the challenge for a different input or a higher price: a compromised CDN or edge, a MITM, or a malicious SDK/proxy. They could also change `payTo`, which becomes Permit2 `witness.to`.\n- The user would then sign a valid Permit2 authorization paying the attacker, or approve a job they never asked for.\n- The proxy enforces `to` only against whatever the user signed. It doesn't check against IMD's real recipient.\n\n**Smallest fix:**\n- The client checks that the challenge's `input` hash equals the hash of what it posted.\n- The client checks that `payTo`/`asset`/`amount` equal values pinned in the SDK. Don't take them only from the live endpoint.\n- The `QuoteApproval` includes `inputHash`, `amount`, `token` and `payTo`. The server rejects any mismatch.\n\n### 6. MEDIUM: Bearer-token leakage\n**Step attacked:** authentication of every step (quote, submit, status polling, results).\n- The bearer is the only credential for reading the order, and the client generates it.\n- If it leaks, the attacker can:\n  - read all orders and results under that scope;\n  - resubmit captured signatures (idempotency makes this mostly harmless);\n  - combined with finding 3, take over orders.\n- Likely leak paths:\n  - `Authorization` headers in reverse-proxy or APM logs;\n  - reusing one secret across many orders or across staging and production;\n  - `statusUrl`/`resultUrl` values that embed the secret or order capabilities;\n  - weak client random-number generators.\n- `requesterScopeHash` appears in the 402 challenge. If it isn't a hash of the secret with a domain separator added (e.g. it's a bare hash, or only a truncated one), it's another place for leaks or linking scopes across services.\n\n**Smallest fix:**\n- Make order ownership **wallet-bound as well**: the payer's address from the verified signature can re-authenticate (with a `Wallet` EIP-712 signature) and rotate the bearer. A leaked bearer can then be revoked.\n- Define `requesterScopeHash = keccak256(\"imd.scope.v1\", secret)`.\n- Say explicitly in the docs that clients should use one secret per order or session, that the server redacts `Authorization`, and that result URLs never carry the secret.\n\n### 7. LOW-MEDIUM: Gas griefing against the server's wallet\n**Step attacked:** settlement (the server pays gas).\n- An attacker holding many cheap bearers submits permits that pass the off-chain check.\n- Before the server broadcasts, they drain their balance or revoke the Permit2 allowance, or race the nonce.\n- Each server settle transaction reverts, and IMD pays the gas. It's a cheap way to drain the server's wallet or cause a denial of service.\n\n**Smallest fix:**\n- Re-simulate with `eth_call` right before broadcasting.\n- Rate-limit per payer address, not per bearer.\n- Send through a private orderflow channel so a revert costs nothing.\n- Optionally require payers to have a minimum balance or allowance margin.\n\n### 8. LOW: Cross-environment or cross-chain replay of `QuoteApproval`\n**Step attacked:** the EIP-712 domain of the quote approval.\n- The proxy is at the same address on every chain, and Permit2 signatures are bound to a chain. But if the `QuoteApproval` domain lacks `chainId` plus a service-specific `name`/`version`/`verifyingContract`, a signature collected by staging (or a fork of IMD) can be replayed against production. This matters if both use the same recipient and quote ID format.\n\n**Smallest fix:**\n- The domain includes `chainId: 1` and a unique `name` for each environment.\n- The struct includes `resourceUrl`, which carries the host.\n\n---\n\n## Summary\n\n| # | Severity | Step | Smallest fix |\n|---|---|---|---|\n| 1 | High | Settle → admit | Permit `deadline` ≤ quote expiry; settled means admitted (retried), plus `refund_due` and a signed receipt |\n| 2 | High | Permit2 settle | Nonce derived from `orderId`; on revert, find the settlement event and treat the order as paid |\n| 3 | High (conditional) | QuoteApproval | Approval covers `orderId`, `requesterScopeHash`, `resourceUrl`, and the permit nonce and deadline |\n| 4 | High→Med | Pairing | `UNIQUE(payer, nonce)` → one order |\n| 5 | Medium | Quote/402 | Client pins recipient/token/amount and checks `inputHash`; approval covers them |\n| 6 | Medium | Bearer auth | Wallet-bound ownership and bearer rotation; hash with a domain tag; redact `Authorization` |\n| 7 | Low-Med | Gas | Simulate just before broadcast, private orderflow, per-payer limits |\n| 8 | Low | EIP-712 domain | `chainId` plus a per-environment domain name |\n\nThe single most useful change is to **derive the Permit2 nonce from the order and include it in `QuoteApproval` with `orderId` and `requesterScopeHash`**. That one change fixes most of findings 2, 3 and 4. It also makes the fix for finding 1 easy to audit, because every on-chain settlement then maps to exactly one order.\n\n---\nSources offered but not confirmed by this machine:\n- https://imd.fun/docs — does not contain the quoted text\n- https://imd.fun/docs — does not contain the quoted text","citations":[{"contentHash":"7bf77cd6b753dacd725d692401efebcd48ee8b8fd4c8e5063811398e25c542f3","quote":"A random 32-byte secret you generate, as 64 hex characters","retrievedAt":"2026-09-26T19:41:57.176Z","url":"https://imd.fun/docs"},{"contentHash":"7bf77cd6b753dacd725d692401efebcd48ee8b8fd4c8e5063811398e25c542f3","quote":"Quotes last 600 seconds.","retrievedAt":"2026-09-26T19:41:57.235Z","url":"https://imd.fun/docs"},{"contentHash":"34e6be7906c8a0e55e19875b0a5da83b9569ba233e202a93f7adf5199fc94396","quote":"post-audit: extra removed from Witness","retrievedAt":"2026-09-26T19:41:58.019Z","url":"https://github.com/coinbase/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md"}],"device":"00d95fd75f8b224c","id":"08344e02-eac2-4064-92d4-a51f6093e745","model":"claude-opus-5-5","nodeId":"7128bdf6-ace0-49f5-b92b-49b12851934c","runtime":"claude"},{"answer":"This is a review of the documented protocol, not an implementation audit. The largest documentation gap is that the actual `QuoteApproval` EIP-712 domain and fields are not published, so several security properties cannot presently be verified independently.\n\n## Findings, highest severity first\n\n### 1. High — payment and admission are not atomic\n\n**Attacked step:** settlement → admission.\n\nThe documented state machine allows `payment_pending`, then `admission_pending`, then `admitted`; the successful example separately reports a confirmed payment and an admission result. It also says a request may be refused after payment if the catalog changed. Therefore, once the Permit2 transfer confirms, neither the signature nor the HTTP protocol forces the server to create the job, workflow, or oracle request. A crash, database loss, permanent admission error, or malicious server can leave the customer paid but unserved. Permit2 cannot reverse the completed transfer.\n\n“Retries reuse the order and never charge twice” prevents a second debit, but does not guarantee delivery or refund. [IMD paid-request documentation](https://imd.fun/docs/) The underlying x402 specification likewise describes `exact` as transferring a specific amount to the resource server; it does not make that transfer atomic with an application-side resource. [x402 exact scheme](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact.md)\n\n**Smallest fix:** Before broadcasting settlement, durably record an idempotent admission intent containing the final resource ID and full input. After confirmation, retry admission until it succeeds. Add one protocol rule: every confirmed payment must eventually produce either the reserved admission or an automatic on-chain refund, and expose `refund_pending/refunded` states. “Refused after payment” should result in a refund, not a terminal paid refusal.\n\nThis is the most important fix because no signature binding can solve the off-chain atomicity gap.\n\n### 2. High — a leaked payment signature can be front-run to cause “paid but not credited” griefing\n\n**Attacked step:** signed payment → proxy settlement.\n\nThe Permit2 authorization names the fixed x402 proxy as `spender`, while the witness fixes the recipient. Consequently, possession of the signature cannot redirect payment to the attacker, but it may let the attacker call the proxy first. The canonical x402 specification explicitly says the spender is the proxy and that the proxy enforces transfer to `witness.to`. [x402 EVM exact specification](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md)\n\nThe first valid call consumes Permit2’s nonce. The server’s later call then fails as a replay. If IMD recognizes payment only through its own settlement attempt or transaction hash, the attacker has made the customer pay the correct recipient while preventing automatic admission. This is theft only if the recipient refuses reconciliation, but it is a practical, externally triggerable payment/admission grief.\n\nPermit2’s unordered nonce protects against a *second debit*; it does not prove which HTTP order should receive credit. Uniswap describes signature-transfer nonces as replay protection and the permission as lasting only for the transaction in which it is spent. [Uniswap Permit2](https://github.com/Uniswap/permit2)\n\n**Smallest fix:** Persist the authorization hash and its intended order before settlement. If settlement reports a used nonce or otherwise fails ambiguously, reconcile on-chain and credit the order when the exact tuple—chain, Permit2 owner, token, amount, nonce, deadline, proxy, witness recipient and witness validity—has already settled. The system must not require the transaction sender to be the server.\n\nFor easier deterministic reconciliation, include `orderId` or `quoteHash` in the proxy witness and event. That is slightly larger but removes ambiguous matching between equal-price orders.\n\n### 3. Medium, potentially High — cross-order replay and quote substitution are asserted but not auditable\n\n**Attacked step:** 402 challenge → `QuoteApproval` verification.\n\nThe documentation says the second signature is an approval “for this quote,” and mentions `quote`, `requesterScopeHash`, `resourceUrl`, and `input`, but it never publishes:\n\n- The `QuoteApproval` type string and exact fields.\n- Its EIP-712 domain.\n- The canonical encoding/hash of `input`.\n- Whether it commits to the complete Permit2 authorization.\n- Whether `requesterScopeHash` is actually inside the signed message.\n- Whether the order ID, action, API audience and protocol version are signed.\n\n[IMD paid-request documentation](https://imd.fun/docs/)\n\nThis matters because payment requirements are otherwise interchangeable: current actions have the same price, token and recipient. If `QuoteApproval` signs only payer plus generic payment terms, an intermediary holding signatures could attach a payment intended for order A to order B, substitute an equal-price quote, or reuse the approval with a fresh Permit2 authorization. Permit2’s nonce prevents replay of one Permit2 signature, but it cannot stop replay or substitution of the separate off-chain `QuoteApproval`.\n\nThis finding is **Medium as a documentation/security-assurance defect** and becomes **High if any of the bindings below are absent in the implementation**.\n\n**Smallest fix:** Publish and enforce one explicit signed struct, for example:\n\n```text\nQuoteApproval(\n  bytes32 orderId,\n  bytes32 requestKeyHash,\n  bytes32 actionHash,\n  bytes32 inputHash,\n  bytes32 requesterScopeHash,\n  bytes32 paymentAuthorizationHash,\n  bytes32 resourceUrlHash,\n  uint256 quoteExpiresAt,\n  bytes32 audienceHash,\n  uint256 protocolVersion\n)\n```\n\n`paymentAuthorizationHash` should cover the entire Permit2 message: chain, Permit2 contract, proxy spender, owner, token, maximum/exact amount, nonce, deadline, witness recipient, and `validAfter`. The server must reconstruct these values rather than accept caller-supplied hashes. Use a domain unique to the IMD production service.\n\n### 4. Medium — bearer-token leakage exposes every order in the requester scope\n\n**Attacked step:** quote creation, submission and later order reads.\n\nThe paid credential is a random 32-byte bearer secret that “names your orders.” The same token is sent when creating a quote, obtaining the challenge, submitting signatures, and polling the order. The documentation also says the 402 JSON repeats the input. Thus a token leaked through shell history, CI output, proxy/access logs, crash telemetry, or a copied command can disclose private job/oracle inputs and order results. It can also let the holder consume rate limits or interact with orders for which it later obtains signatures.\n\nThe wallet signature still gates payment, so the bearer token alone does not authorize spending. Nevertheless, it is a long-lived capability with apparently broad scope, and the documentation provides no expiry, rotation, revocation, or order-level attenuation. [IMD authentication documentation](https://imd.fun/docs/)\n\n**Smallest fix:** Return a separate random, revocable, order-scoped capability when a quote is created and require it for `/requests/:id/*`. Keep the master token only for creating and listing orders. Store only hashes of both tokens, support rotation/revocation, set an expiry, and redact `Authorization` and `PAYMENT-SIGNATURE` from all logs and traces.\n\n### 5. Low — replay of the same Permit2 payment across orders is already prevented on-chain, assuming canonical validation\n\n**Attacked step:** repeated proxy settlement.\n\nA byte-for-byte replay of one `permitWitnessTransferFrom` authorization should not charge twice because Permit2 signature transfers use one-time unordered nonces. The deadline also bounds the usable period, while the witness binds the recipient. [Uniswap Permit2](https://github.com/Uniswap/permit2) [x402 EVM exact specification](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md)\n\nThat protection depends on IMD validating the complete payload against the issued challenge: correct Ethereum chain, canonical Permit2, exact proxy spender `0x402085c248EeA27D92E8b30b2C58ed07f9E20001`, IMD token, exact amount, recipient, nonce, deadline and `validAfter`. The IMD error list suggests payment-shape and terms checks exist, but does not specify them normatively.\n\n**Smallest fix:** Make those equality checks explicit in the public protocol and add conformance vectors. Reject overpayment, alternate proxy addresses, mismatched `accepted` fields, deadlines past the quote expiry, and any authorization hash already reserved for another order. Keep the existing order-level idempotency rule.\n\n## Bottom line\n\nThe cryptographic payment leg appears well protected against redirection and duplicate debit by the canonical proxy, recipient witness, deadline and Permit2 nonce. The dangerous boundary is immediately after that: an independently submitted payment can consume the nonce, while admission remains an off-chain promise. Durable pre-settlement admission intent plus on-chain reconciliation/refund is the smallest change that closes the most serious loss and griefing cases. Publishing the complete `QuoteApproval` schema is necessary before cross-order and quote-substitution resistance can be treated as established rather than assumed.\n\n---\nSources offered but not confirmed by this machine:\n- https://imd.fun/docs/ — does not contain the quoted text\n- https://imd.fun/docs/ — does not contain the quoted text\n- https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md — does not contain the quoted text\n- https://github.com/Uniswap/permit2 — does not contain the quoted text","citations":[{"contentHash":"7bf77cd6b753dacd725d692401efebcd48ee8b8fd4c8e5063811398e25c542f3","quote":"Then the same wallet signs twice, the Permit2 payment and an EIP-712 approval of that payment for this quote, and you send both:","retrievedAt":"2026-09-26T19:41:20.856Z","url":"https://imd.fun/docs/"},{"contentHash":"7bf77cd6b753dacd725d692401efebcd48ee8b8fd4c8e5063811398e25c542f3","quote":"Retries reuse the order and never charge twice.","retrievedAt":"2026-09-26T19:41:20.881Z","url":"https://imd.fun/docs/"}],"device":"0256823ae36e7900","id":"31cdf840-1706-4609-aedc-1c70e6a3654a","model":null,"nodeId":"7128bdf6-ace0-49f5-b92b-49b12851934c","runtime":"codex"},{"answer":"**The main risk is payment without admission.** Cross-order replay and quote substitution depend on how `QuoteApproval` is constructed and verified; the documentation does not publish that schema. This is a protocol review, not confirmation of deployed implementation bugs. Proxy conclusions below use the x402 reference contract for the specified address, not independently verified deployed bytecode. [IMD documentation](https://imd.fun/docs/), [x402 specification](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md)\n\n1. **High — Settlement does not guarantee admission or recovery.**  \n   **Attacked step:** settlement → admission.\n\n   IMD documents `admission_pending` and a possible `refused` outcome after catalog changes, but specifies no refund deadline or enforceable recovery. Consequently, a crash or dishonest server can leave a paid request without admission; polling is not a remedy against permanent refusal. This is a documented guarantee gap, not proof that refunds are absent from the implementation. [IMD documentation](https://imd.fun/docs/)\n\n   **Smallest fix:** Before broadcasting payment, durably reserve the validated, immutable order and record its payment authorization. Reconcile settlement after crashes and retry admission idempotently, with one admission per order. Specify automatic refunds after a fixed admission deadline.\n\n   That addresses operational failures. **For protection against a dishonest server**, a server-controlled refund promise is insufficient: pay into escrow keyed to the order commitment, with payer withdrawal after timeout unless the agreed admission condition is satisfied. An admission receipt alone must not be represented as proof of completed work. This is a proposed protocol change.\n\n2. **High, conditional — Cross-order replay or payment reassignment if the approval binding is incomplete.**  \n   **Attacked step:** signed submission → payment reservation → admission.\n\n   IMD requires two signatures from the same wallet but leaves `quoteApprovalTypedData(...)` undefined. EIP-712 explicitly says, “It does not include replay protection.” Therefore, the signature format’s name does not establish order binding or one-time application semantics. [IMD documentation](https://imd.fun/docs/), [EIP-712](https://eips.ethereum.org/EIPS/eip-712)\n\n   **Attack condition:** If approval A omits the order identity, requester scope, or exact payment authorization, an attacker possessing a valid submission could try applying it to order B or pairing it with another payment. This is a conditional attack, not an established omission.\n\n   Permit2’s consumed nonce prevents another successful transfer using that nonce; it does **not** by itself prevent an application crediting one transfer to multiple orders. [Uniswap SignatureTransfer documentation](https://developers.uniswap.org/docs/protocols/permit2/concepts/signature-transfer)\n\n   **Smallest fix:** Publish and enforce an approval commitment containing:\n\n   - Immutable order ID and requester-scope hash.\n   - Quote commitment covering action, canonical input hash, payment terms and expiry.\n   - Exact Permit2 typed-data digest, including its domain.\n   - An application-specific domain separating service, environment and version.\n\n   Require approval signer = Permit2 owner. Atomically assign `(chainId, Permit2 address, owner, nonce)` to exactly one order, rejecting conflicting assignments. Return the existing outcome for identical retries. Deduplicate by authorization identity, not signature bytes. If these checks already exist, this finding becomes a documentation and verification gap.\n\n3. **High, conditional — Quote substitution before signing.**  \n   **Attacked step:** quote → 402 challenge → wallet approval.\n\n   The example creates both signatures from challenge-supplied data without showing a comparison to the caller’s original intent. A substituted challenge could therefore obtain a perfectly valid signature for the wrong request or payment terms if the client blindly trusts it. That is a conditional client-validation failure, distinct from altering already-signed data. [IMD documentation](https://imd.fun/docs/)\n\n   **Smallest fix:** Before **either** signature, independently compare the challenge against the locally retained order, action, canonical input, requester scope, expected chain/token/recipient, amount limit and expiry. Pin the expected Permit2 contract and exact proxy. Construct typed data locally using a published schema; have the server reconstruct it from the immutable stored quote.\n\n   This recommendation follows EIP-712’s separation between authenticating structured data and determining whether those data express the intended operation. A signed incorrect quote remains authorized cryptographically. [EIP-712](https://eips.ethereum.org/EIPS/eip-712)\n\n4. **Medium — Anyone seeing the Permit2 signature can force settlement; severity rises to High if recovery strands the payment.**  \n   **Attacked step:** signature disclosure/broadcast → settlement.\n\n   In the reference proxy, `settle(...)` is externally callable without caller authorization. Its witness contains only `to` and `validAfter`; it does not check QuoteApproval, an order ID, or the HTTP bearer token. Thus an observer can submit the copied permit first during its validity window. The reference code fixes the recipient and transfers the signed amount, so this permits **forced payment to the intended recipient**, not redirection to the attacker. [x402 specification](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md)\n\n   The observer consumes the nonce; a later server attempt cannot successfully spend it again. A backend that treats its own transaction’s failure as proof of nonpayment could consequently deny admission despite successful payment. The denial is conditional on backend behavior. [Uniswap SignatureTransfer documentation](https://developers.uniswap.org/docs/protocols/permit2/concepts/signature-transfer)\n\n   **Smallest fix for griefing:** Accept externally submitted settlement of the exact authorized payment. Reconcile successful execution against the stored authorization, regardless of transaction sender. Do not treat a consumed nonce alone as payment proof: verify the successful transfer and match owner, nonce, token, amount, recipient, witness and chain through transaction calldata or execution traces. Preserve the one-payment/one-order assignment from finding 2.\n\n   **To prohibit forced settlement itself:** use a different, caller-restricted spender/proxy and sign for that spender. An HTTP check or wrapper around the existing publicly callable proxy cannot prevent direct calls. This conclusion follows from the reference call path; extending its witness requires a compatible contract change. [x402 specification](https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md)\n\n5. **Medium — Leaked bearer token grants access to its order scope.**  \n   **Attacked step:** authenticated quote, submit and status requests.\n\n   IMD uses a client-generated bearer secret for order access, while wallet signatures authorize payment. Token theft therefore exposes the associated order access; **the token alone is not a fresh payment authorization**. [IMD documentation](https://imd.fun/docs/)\n\n   Plausible leakage paths include request logs, tracing and copied command examples containing a real token. These are threat scenarios, not observed leaks. Bearer authentication requires no additional proof of a key once the credential is obtained. [RFC 6750](https://www.rfc-editor.org/info/rfc6750/)\n\n   **Smallest containment:** use a separate token per order, redact credentials and payment signatures from telemetry, and provide expiry, revocation and rotation.\n\n   **Smallest protocol fix that makes a leaked token insufficient:** bind the scope to a client public key and require request signatures covering method, path, body hash, timestamp and replay nonce. Keep that authentication separate from payment authorization. Never place the bearer secret in signed data that may become public on-chain.","citations":[{"contentHash":"7bf77cd6b753dacd725d692401efebcd48ee8b8fd4c8e5063811398e25c542f3","quote":"Retries reuse the order and never charge twice.","retrievedAt":"2026-09-26T19:41:06.481Z","url":"https://imd.fun/docs/"},{"contentHash":"3ee9963614302fd0fcd47dbce6723e064108f87b929d2ff6b073f1a60b9afc7c","quote":"Witness(address to,uint256 validAfter)","retrievedAt":"2026-09-26T19:41:06.832Z","url":"https://github.com/x402-foundation/x402/blob/main/specs/schemes/exact/scheme_exact_evm.md"},{"contentHash":"611f730c12c917b71773a01c0c2d324bad17fd86253a8b02fc55e04e43d68e42","quote":"Bits that are dirtied may not be used again.","retrievedAt":"2026-09-26T19:41:07.116Z","url":"https://developers.uniswap.org/docs/protocols/permit2/concepts/signature-transfer"},{"contentHash":"f5caffe10b6086343497ab3c36ecc9f84ff54f1059f151912f90f607d83dfae5","quote":"It does not include replay protection.","retrievedAt":"2026-09-26T19:41:07.183Z","url":"https://eips.ethereum.org/EIPS/eip-712"},{"contentHash":"732b0e13ce349cdb7bb37fc8bf5578282775f1c6aceb3b077c464c712ba3d9a1","quote":"protected from disclosure in storage and in transport.","retrievedAt":"2026-09-26T19:41:07.435Z","url":"https://www.rfc-editor.org/info/rfc6750/"}],"device":"02ae6543274731ab","id":"a42837df-a9a2-490d-a668-91265d346019","model":null,"nodeId":"7128bdf6-ace0-49f5-b92b-49b12851934c","runtime":"codex"}],"schema":"identitymd-work-v1","signals":[],"site":null,"snapshotHash":"7247896fd3dd266288643ba03d5e7c9a398182b5e94705e8e9a20de0d38b8395","state":"completed","submissions":[],"verification":[]}