{"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":"e9fc751f-0479-4d33-afb2-c4b2f79f0113","kind":"audit","nodes":[{"acceptedSubmissionHash":"4b14c00605e0342d9ee3216623a75c97f244cd0913a984aa512dd63dd9bed2f5","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"7706b1a17ae83daf4e2b7c65fa1f8d9b2415bb5d874985901dd2cc1ef02b9bd5","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"ccc6be6a724843f45db0cf1f4e3beb0810b7313a3f5777b94652e38cd05225bb","dependsOn":["audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"e3674d8010721aaf052136ea6492eb43fed1020275dac0be0969fa8da772e1c0","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"38a8d3030e5648dc050d3222366d79c339921064a7280df5aa4d0f74fc79713b","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"}],"objective":"Review Submission7/ only (read Submission7/AUDIT_README.md first; Submission6 and Submission5 AUDIT_README are the reference for everything unchanged). Not deployed. It fixes Swarm audit 944905c3: the PaidAuthorization now includes the payment token (address token after recipient), so an order pays only in the token it was signed for; the signer can stop issuing orders for a token (allowedTokens); approve returns mintPaid.token; addPaymentToken requires usableAt >> 40 == 0. Diff: Submission7/build/EmberEvoGenesisCandidate.Submissio6-to-7.diff (runtime 24,427 bytes).\n\n1. Token binding: with imdUSD v1, USDC and imdUSD v2 listed, can an order signed for one token be paid in another, at the same or a different GOLD price? Does any field of the order, the wallet's GOLD consent or the PaidAuthorization fail to bind the token?\n2. Signer policy: with allowedTokens set, can a buyer still get or reuse an order for a token outside it (prepare, approve, a ticket for another token, a retried approve)?\n3. GOLD book (secondary scope, offchain/gold-book, World 0727200): can a hold be made at a price no listed token charges, or for a token the registry entry does not list?\n4. Any path where usableAt >> 40 == 0 refuses a valid add, or where the larger digest changes mintPaid's behaviour other than adding the token?\n\nReport severity, reproduction and fix. Not in scope: artwork, the Mint web page, World 3D.","parentJobId":null,"planHash":"457747f6172879a0be690672e025dcd47ca9524bd8987990e087aa5b0b860059","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"e9fc751f-0479-4d33-afb2-c4b2f79f0113","publication":{"commit":null,"deliveredAt":null,"repoUrl":null},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52252","feedbackHash":"a0aac773a1e93b3d8b09e3fbb302e95abd0b6dc6b729a0c9d53ad458923a52fa","nodeKey":"audit_economics","submissionHash":"4b14c00605e0342d9ee3216623a75c97f244cd0913a984aa512dd63dd9bed2f5","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52193","feedbackHash":"17e5a1ce39beccba3a70972af04f6f30ec117f4aa7944efa0a6fb0e8b16e1644","nodeKey":"audit_flow","submissionHash":"7706b1a17ae83daf4e2b7c65fa1f8d9b2415bb5d874985901dd2cc1ef02b9bd5","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51450","feedbackHash":"1061886c2f966f932073aa60c2bb72da5a9099405e4475da86de4b9cfb82a1fb","nodeKey":"audit_judge","submissionHash":"ccc6be6a724843f45db0cf1f4e3beb0810b7313a3f5777b94652e38cd05225bb","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51070","feedbackHash":"fde780b4233e6333e2b8813e3448e6999f66215072efe72726c1bc3be24e35ed","nodeKey":"audit_math","submissionHash":"e3674d8010721aaf052136ea6492eb43fed1020275dac0be0969fa8da772e1c0","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"54231","feedbackHash":"9485161325b771ca81f54d89ad0bccfbdd6f83f7f003fda261069c763c8974a6","nodeKey":"audit_permissions","submissionHash":"38a8d3030e5648dc050d3222366d79c339921064a7280df5aa4d0f74fc79713b","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"dfc658ae94eb2cb18bffc992ae98fb0dd5f0c596f1b8b6d3c605f60b00fedb9d","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a0f92fec784312b7","findings":[{"citation":"resolved","description":"Submission 7 answers audit 944905c3 finding 2 (a listed token can never be disabled on chain) with an off-chain policy: 'the signer can stop issuing orders for a token by policy (allowedTokens, Worker variable PAYMENT_TOKENS)'. The only use of that policy is in prepare(), line 70: `if(this.allowedTokens&&!this.allowedTokens.includes(token))fail('PAYMENT_TOKEN_UNAVAILABLE');`. approve() (lines 103-127) checks the HMAC ticket, the uri hash and the wallet's consent, calls gold.reserve, and signs the PaidAuthorization for `order.token` without ever reading this.allowedTokens (grep: the field appears only at lines 44, 45, 51, 52 and 70). The ticket binds deploymentId, chainId, contract and the order fields but not the policy, and it has no lifetime of its own beyond the order deadline (ttlSeconds, 600 s by default, 900 s at most). Consequences: (1) every order prepared while a token was still allowed, or by a Worker that had no PAYMENT_TOKENS set, can be approved after the operator withdraws the token from PAYMENT_TOKENS, for the rest of its ttl: GOLD is held at that token's price and a valid signature for that token is issued, which mintPaid accepts because the entry stays listed on chain for good; (2) a retried approve of such an order returns the same signature again. The shipped test for the policy (tests/signer/paid.test.mjs lines 150-153) exercises prepare only. The README's own attack question 2 names 'a retried approve' and 'a ticket made for another token' as the cases to check. Impact is bounded by the remaining ttl of each outstanding ticket and GOLD is still held at the registry's listed price, so this is a policy bypass rather than a loss: Low. Fix (keeps the design): in approve(), before gold.reserve, add `if(this.allowedTokens&&!this.allowedTokens.includes(order.token))fail('PAYMENT_TOKEN_UNAVAILABLE');` (order.token is the lowercase value prepare produced, guaranteed by the ticket). Keep the check before the signing step too, so a retried approve of an order the book already holds is refused as well and the hold is released by the book at its deadline as today. Operationally, rotate TICKET_SECRET together with PAYMENT_TOKENS so outstanding tickets die at once, and add a test that approves a USDC order under an imdUSD-only signer and expects PAYMENT_TOKEN_UNAVAILABLE.","line":103,"path":"Submission7/offchain/signer/paid-approval.mjs","reproduction":"Verified live against the Submission 7 contract compiled from build/standard-input.json (solc 0.8.38, via-IR) on a local anvil chain (chain id 31337) with the Submission 7 signer code and a stub GOLD binding that always answers {state:'held'}. Setup: deploy with FixtureToken as imdUSD v1 (paidCost 20e18, paidGoldCost 1200), addPaymentToken(DecimalsToken(6), 1600) as USDC, configureSnapshot, setRevealURIs, setRevealCommit with the signer's acceptance, approvePaidPrice, setMintPaused(false), advance the chain 3 days. Steps: 1) open = new PaidApprovalSigner({...common, ticketSecret:S}) with no allowedTokens; p = await open.prepare({wallet:alice, look, choice:1, token:usdc}) -> p.order.token = usdc (lowercase), p.order.goldCost = '1600'. 2) imdOnly = new PaidApprovalSigner({...common, ticketSecret:S, allowedTokens:[v1]}) (the Worker after PAYMENT_TOKENS was set to imdUSD only). 3) imdOnly.prepare({..., token:usdc}) -> rejects PAYMENT_TOKEN_UNAVAILABLE (expected). 4) a = await imdOnly.approve({order:p.order, uri:p.uri, ticket:p.ticket, consentSignature: alice's GenesisGoldHold signature over p.consent}). Expected: rejects PAYMENT_TOKEN_UNAVAILABLE. Actual (observed output): 'approve under imdUSD-only policy -> SIGNED for token 0xe7f1...0512' (the USDC fixture); 'gold.reserve called with [[usdc, \"1600\"]]'; a second identical approve returned the same goldSignature ('retried approve -> same signature returned'); mintPaid(a.mintPaid.lookKey, choice, uri, orderId, deadline, goldSignature, [], usdc) from alice succeeded (status 1) and the recipient's USDC balance became 20000000 (20 USDC). So the policy stops new USDC orders at prepare but neither new holds nor signatures at approve.","severity":"low","snippet":" async approve(request){\n  if(!request||typeof request!=='object'||Array.isArray(request)||Object.keys(request).sort().join()!=='consentSignature,order,ticket,uri')fail('REQUEST_FIELDS_INVALID',400);\n  const {order,uri,ticket,consentSignature}=request;","title":"Signer token policy (allowedTokens / PAYMENT_TOKENS) is enforced only in prepare: approve still holds GOLD and signs a PaidAuthorization for a token the policy excludes"}],"hash":"38a8d3030e5648dc050d3222366d79c339921064a7280df5aa4d0f74fc79713b","nodeId":"597299fb-b4d5-4f5d-9cad-ade9c6230ff3","outcome":"completed","summary":"Review complete. The findings file holds one Low finding, verified live; the on-chain changes in Submission 7 came through the permission audit clean.\n\n**Outcome**\n\n- **On-chain (primary scope): no permission defect found.** The PaidAuthorization digest now binds the token as the seventh field, in the same order the signer's type definition uses. A Foundry test against the contract compiled from the submission's standard input confirmed that an order signed for imdUSD v1 is refused in imdUSD v2 at the same GOLD price, refused in USDC, and refused for an unlisted address, while paying in the signed token succeeds. The access-control inventory is unchanged from Submission 6: every state-changing owner function carries the owner modifier, the public entry points are the two mints and settle, and nothing writes a guarded slot through a weaker path. The new usableAt check cannot refuse a valid add before the year 36812, and the larger digest changes nothing else in mintPaid.\n- **GOLD book (secondary scope): no defect found.** A first hold is refused unless the order's price equals the registry price listed for its token, an unlisted token yields no price and is refused, the consent and the stored document both carry the token, and an identical retry must match the stored document byte for byte.\n- **Signer policy (Low, reported).** The allowedTokens policy that answers audit finding 2 is checked only in prepare. In approve the signer verifies the ticket and consent, holds GOLD and signs for whatever token the order names. I reproduced it against an anvil chain with the submission's own signer code: a USDC order prepared before the policy change was approved by an imdUSD-only signer, GOLD was held at 1,600, the retry returned the same signature, and the USDC mint succeeded on chain. The window per order is the ticket's remaining ttl, so the impact is bounded. The fix is a one-line policy check in approve before the GOLD hold, plus rotating the ticket secret whenever the token list changes.\n\n**Checks run**\n\n- Foundry sanity test of token binding and the usableAt path, in a scratch project outside the repository.\n- Live Node reproduction of the signer finding against anvil, also outside the repository.\n- No repository files were changed. The findings file validates as JSON and its quoted snippet matches the source.\n\n**Not covered**\n\n- The GOLD book's economy-config module and the World worker are not in the package, so address normalisation there was inferred from the passing test suite rather than read.\n- Real USDC proxy behaviour and the Mint page were out of scope and not examined.","treeHash":null,"usage":{"cachedInputTokens":2916312,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":36480,"runtime":"claude","turns":48,"wallClockMs":663743}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c0fc4ea4f50e3380","findings":[{"citation":"resolved","description":"Submission7 answers audit 944905c3 finding 2 (a listed token can never be disabled on chain) with an off-chain policy: `allowedTokens` (Worker variable PAYMENT_TOKENS) is meant to let the signer stop issuing orders for a token while it stays listed on chain. The check `if(this.allowedTokens&&!this.allowedTokens.includes(token))fail('PAYMENT_TOKEN_UNAVAILABLE')` exists only in `prepare` (line 70). `approve` (lines 103-127) verifies the HMAC ticket, the uri hash and the wallet consent, asks the GOLD book to hold, and then signs the PaidAuthorization for `order.token` without ever consulting `this.allowedTokens`. The ticket is an HMAC over the order fields under the long-lived TICKET_SECRET; it does not encode the policy that was in force when the order was prepared. So every order prepared while a token was allowed stays approvable for the rest of its deadline window (PAID_TTL_SECONDS, at most 900 s), and a retried approve of an already-held order (the documented 'lost response' retry, which the book answers from the stored hold) is signed again regardless of policy. The thing mintPaid actually consumes is the signature issued by approve, not the prepare answer, so the policy does not do what the AUDIT_README (section 1, finding 2; section 2, question 2) says it does during that window. The tests only cover prepare (tests/signer/paid.test.mjs line 152: `imdOnly.prepare(...)` refused); no test approves an order prepared under a wider policy. The signer README (offchain/signer/README.md) does not mention PAYMENT_TOKENS at all. Impact is bounded: the token is still listed and usable on chain, the GOLD held is the correct price for that token and the buyer pays the full dollar amount, so no value is lost; what fails is the operator's ability to cut off a token immediately (the stated reason for the policy is 'in case anything goes wrong with imdUSD', when a 15-minute tail of new USDC/imdUSD signatures is exactly what the operator wants to stop). Fix: re-check the policy in approve before signing, e.g. after the ticket check: `if(this.allowedTokens&&!this.allowedTokens.includes(String(order.token).toLowerCase()))fail('PAYMENT_TOKEN_UNAVAILABLE');` (place it before `gold.reserve` so no GOLD is held for an order that will not be signed; for an order already held, the book releases it at its deadline as today). Optionally mix a policy version or the sorted allowed list into the ticket HMAC so a policy change invalidates outstanding tickets explicitly, and add a test that prepares under `allowedTokens:[imdUSD,USDC]`, approves under `allowedTokens:[imdUSD]` with the same ticketSecret, and expects PAYMENT_TOKEN_UNAVAILABLE.","line":121,"path":"Submission7/offchain/signer/paid-approval.mjs","reproduction":"State: USDC listed and usable on chain; two PaidApprovalSigner instances share ticketSecret 'rehearsal-ticket-secret-0123456789abcdef', the same signer key, chainId and contractAddress (the Worker before and after a PAYMENT_TOKENS redeploy). A = new PaidApprovalSigner({...opts, allowedTokens:[imdUSD, usdc.target]}); B = new PaidApprovalSigner({...opts, allowedTokens:[imdUSD]}). Input: p = await A.prepare({wallet:alice, look, choice:1, token:usdc.target}) -> order with token = usdc (lowercase), ticket t. Then: await B.approve({order:p.order, uri:p.uri, ticket:p.ticket, consentSignature:<alice's GenesisGoldHold signature over p.consent>}). Expected (per AUDIT_README section 1 finding 2 and section 2 question 2): refused with PAYMENT_TOKEN_UNAVAILABLE, like B.prepare({...token:usdc.target}) is. Actual: approve passes the ticket check (line 107: ticket recomputed from the same secret and order), the consent check, the book holds USDC's GOLD price, and line 121-122 produce goldSignature for auth.token = USDC; the returned mintPaid arguments are accepted by the contract (digest includes token = USDC, listed and usable). The same holds for a second call B.approve(sameRequest) after A.approve already held the order (retry path: book answers 'held' from the stored order). Window: until order.deadline (block.timestamp at prepare + ttlSeconds, <= 900 s).","severity":"low","snippet":"  const signer=this.signer,auth={...order,wallet:getAddress(order.wallet),recipient:getAddress(order.recipient),token:getAddress(order.token)};","title":"Signer token policy (allowedTokens) is enforced only in prepare; approve signs a PaidAuthorization for a token the policy no longer allows"},{"citation":"resolved","description":"The constructor normalizes each allowed token with `wallet(t)`, the request-field validator, which throws ApprovalError('WALLET_INVALID', 400). Every other configuration check in the constructor throws a CONFIG_* code with status 500 (CONFIG_CHAIN_ID, CONFIG_METADATA_BASE, CONFIG_TTL, CONFIG_TICKET_SECRET, CONFIG_GOLD_BINDING, CONFIG_PAYMENT_TOKENS). worker-entry.mjs line 32 splits PAYMENT_TOKENS on commas and trims but does not validate the entries, and worker.mjs line 68 constructs the PaidApprovalSigner inside the request's try block, so the ApprovalError is caught at line 91 and returned to the client as {ok:false,error:{code:'WALLET_INVALID'}} with HTTP 400 on every /v1/paid/prepare and /v1/approval/paid call. The buyer is told their wallet is invalid; the operator sees no 5xx and no console.error (line 92 runs only for non-ApprovalError exceptions). Because the paid route is constructed per request, this is not a one-time startup failure but a silent total outage of paid minting that is misattributed to the user. Fix: validate allowedTokens with its own code, e.g. `this.allowedTokens=allowedTokens?.map(t=>{try{return getAddress(t).toLowerCase();}catch{fail('CONFIG_PAYMENT_TOKENS',500);}})??null;` (and reject the zero address the same way), so a misconfiguration surfaces as a 500 CONFIG_PAYMENT_TOKENS that monitoring catches. Also consider validating in worker-entry.mjs at deploy time and documenting PAYMENT_TOKENS in offchain/signer/README.md, which currently does not mention it.","line":52,"path":"Submission7/offchain/signer/paid-approval.mjs","reproduction":"Input: Worker var PAYMENT_TOKENS='0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, 0xabc' (second entry truncated), GOLD binding, GOLD_DEPLOYMENT_ID and TICKET_SECRET present. Request: POST /v1/paid/prepare with a valid body {wallet, look, choice, token:<imdUSD>} from an allowed Origin. Expected: either the Worker refuses to start or a 500 CONFIG_PAYMENT_TOKENS, as for every other constructor misconfiguration. Actual: worker.mjs line 68 calls paidApprover(env) -> new PaidApprovalSigner -> line 52 wallet('0xabc') -> ApprovalError('WALLET_INVALID',400) -> worker.mjs line 91 replies HTTP 400 {ok:false,error:{code:'WALLET_INVALID'}}; the same for /v1/approval/paid. No server-side log is written.","severity":"info","snippet":"  this.allowedTokens=allowedTokens?.map(t=>wallet(t).toLowerCase())??null;","title":"A malformed PAYMENT_TOKENS entry is reported to every buyer as 400 WALLET_INVALID instead of a 500 configuration error, and the paid route goes dark"}],"hash":"4b14c00605e0342d9ee3216623a75c97f244cd0913a984aa512dd63dd9bed2f5","nodeId":"db329435-c41c-4b3a-9825-35e29fda9cd1","outcome":"completed","summary":"Findings are written and verified against the files. Here is the recap.\n\n**Scope reviewed.** Submission7 contract (`EmberEvoGenesisCandidate.sol`, with the Submission6-to-7 diff), the signer (`paid-approval.mjs`, `worker.mjs`, `worker-entry.mjs`), the GOLD book (`genesis-gold.ts`, `world-gold.ts`, `world-gold-result.ts`, `genesis-gold-reader.ts`, registry) and the contract, signer and GOLD-book tests. Economic Security, Invariant and Flow Gap passes were applied to the four questions in the AUDIT_README. No Foundry proofs were produced: the repository has no foundry.toml or lib, nothing found reached high severity, and the two findings are in JavaScript.\n\n**Answers to the four questions**\n\n1. **Token binding holds.** The digest encodes the token as an address, identical to the signer's typed data. The order, the HMAC ticket, the GenesisGoldHold consent and the stored book document all carry the token. Cross-token payment at the same or a different GOLD price fails the signature check, and an unlisted token is refused on chain, by prepare and by the book. Letter case is normalized everywhere that compares strings.\n2. **Signer policy has a gap.** The policy is enforced only in prepare. An order prepared while a token was allowed can still be approved, and re-approved on retry, for the rest of its deadline window after the policy is tightened. Reported as Low.\n3. **GOLD book is tight.** A hold needs the order's price to equal the registry price for the order's token. An unlisted token yields no price and is refused. Retries compare the stored document byte for byte, so a token swap on retry conflicts.\n4. **No regression from the new check or the larger digest.** The usableAt bound cannot refuse a valid add for tens of thousands of years, and the extra field changes only gas.\n\n**Findings in `.imd-findings.json`**\n\n- **Low.** Approve signs a PaidAuthorization for a token outside allowedTokens when the order was prepared under a wider policy or is retried after a policy change. Window is at most the order deadline (up to 900 seconds). Fix: re-check the policy in approve before the GOLD hold, and add a test for it.\n- **Info.** A malformed PAYMENT_TOKENS entry makes the constructor throw the request-level WALLET_INVALID 400 on every paid request, with no server log, so a config error reads as a user error and silently closes the paid route. Fix: validate with a CONFIG_PAYMENT_TOKENS 500 and document the variable in the signer README.\n\n**Trust assumptions, not defects.** The owner can list up to two tokens at any GOLD price, with only the two-day delay and the registry as gates. Events still do not name the paying token. Paid mints can exhaust supply ahead of free-list wallets, as in earlier submissions.","treeHash":null,"usage":{"cachedInputTokens":838355,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":19213,"runtime":"claude","turns":24,"wallClockMs":303523}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2d027bc56749d95c","findings":[],"hash":"65c94074e635ad7d281762491575c687a8d6aceb446e616bc84a5e1f4a2661d5","nodeId":"5fdaafe8-093a-4c29-b4e7-adacc91d65b4","outcome":"failed","summary":"API Error: Your account can't receive replies from Claude right now. You can find the next step in the Anthropic Verification Portal: https://portal.anthropic.com/…","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"claude","turns":1,"wallClockMs":2662}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5d667e4b0751bcb5","findings":[{"citation":"resolved","description":"Submission7 answers audit 944905c3 finding 2 (\"a listed token can never be disabled\") with an off-chain policy: PaidApprovalSigner.allowedTokens (Worker var PAYMENT_TOKENS) is meant to let the signer \"stop issuing orders for a token\". The check exists only in prepare() (line 70: `if(this.allowedTokens&&!this.allowedTokens.includes(token))fail('PAYMENT_TOKEN_UNAVAILABLE')`). approve() never consults allowedTokens: it verifies the HMAC ticket (line 107), the consent, calls gold.reserve (line 114) and then signs the PaidAuthorization over order.token (lines 121-122). The ticket is an HMAC over the order fields with TICKET_SECRET, which worker-entry.mjs reads from the same secret for every PaidApprovalSigner instance, and it carries no policy version. So every order prepared under the old policy (PAYMENT_TOKENS unset, or still listing the token) remains approvable after PAYMENT_TOKENS is changed, until the order's deadline (prepare sets deadline = block.timestamp + ttl, ttl 600 s by default and at most 900 s). In that window the signer still issues the authorization the contract accepts (mintPaid only checks the signature, the token's on-chain entry and the deadline), and the GOLD book holds GOLD for the excluded token. A retried approve of an already-held order is also signed again (the book answers from the stored hold; the signature is deterministic). Scenarios where this matters are exactly the ones the policy was added for: the owner excludes USDC because the recipient Safe was blacklisted by USDC (every USDC mintPaid then reverts, so GOLD is held for orders that cannot be minted until the deadline releases it), or excludes imdUSD v1 at the v2 migration. The policy therefore takes effect for new authorizations only after the longest outstanding ticket expires, not when PAYMENT_TOKENS changes, and no test covers approve under a policy (tests/signer/paid.test.mjs only exercises imdOnly.prepare). Expected: once PAYMENT_TOKENS excludes a token, approve refuses every order for it with PAYMENT_TOKEN_UNAVAILABLE before anything is held or signed. Actual: approve holds GOLD and returns a valid goldSignature with mintPaid.token = the excluded token. Fix: in approve(), after the ticket check and before gold.reserve, add `if(this.allowedTokens&&!this.allowedTokens.includes(order.token))fail('PAYMENT_TOKEN_UNAVAILABLE');` (order.token is lowercase, as the ticket binds it), and add a test: an order prepared by a signer without allowedTokens is refused by approve on a signer with allowedTokens that excludes its token. Optionally fold a policy identifier into the ticket so a policy change invalidates old tickets outright.","line":114,"path":"Submission7/offchain/signer/paid-approval.mjs","reproduction":"Two PaidApprovalSigner instances share ticketSecret 'rehearsal-ticket-secret-0123456789abcdef' (as two Worker requests do). Instance A: no allowedTokens. Instance B: allowedTokens:[IMD] where IMD=0xa1..a1 (imdUSD) and USDC=0xb2..b2. Order o = {orderId:0x11..11, wallet:<buyer>, lookKey:keccak256('look'), choice:1, uriHash:keccak256(uri), recipient:0xd4..d4, token:USDC, embeoCost:'20000000000000000000', goldCost:'1600', deadline:String(1800000000+600), signerEpoch:'1'}; ticket = await A.ticket(o); consentSignature = buyer.signTypedData(A.consentDomain, GOLD_CONSENT_TYPES, A.consentMessage(o)); gold stub records reserve calls. Then: B.prepare({wallet,look,choice:1,token:USDC}) -> rejects PAYMENT_TOKEN_UNAVAILABLE (expected). B.approve({order:o,uri,ticket,consentSignature}) -> expected PAYMENT_TOKEN_UNAVAILABLE and no reserve call; actual: resolves with mintPaid.token = 0xb2b2...b2b2 (USDC), goldSignature recovering to the approval signer over PAID_TYPES, and gold.reserve was called with [USDC,'1600']. B.approve(...) again -> the same signature is returned (retry also signed). Run with Node 24 and ethers 6.17 after copying offchain/signer/*.mjs next to a vendor/ethers.min.js that re-exports ethers; approve() reads nothing from the chain, so no provider is needed (provider only needs getBlock for prepare).","severity":"low","snippet":"  try{held=await this.gold.reserve({deploymentId:this.deploymentId,order,consentSignature});}","title":"Signer policy (allowedTokens) is enforced only in prepare: approve still holds GOLD and signs a PaidAuthorization for an excluded token (pre-policy ticket or retried approve)"}],"hash":"7706b1a17ae83daf4e2b7c65fa1f8d9b2415bb5d874985901dd2cc1ef02b9bd5","nodeId":"facff3fc-04cf-40fb-8878-f54a70d0215e","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds one substantiated finding.\n\n**Outcome.** The on-chain fix is sound. The only defect found is in the signer's new token policy, which is enforced in `prepare` but not in `approve`.\n\n**Finding (Low), Submission7/offchain/signer/paid-approval.mjs line 114.** `allowedTokens` is checked only in `prepare`. `approve` verifies the HMAC ticket, holds GOLD and signs the PaidAuthorization without consulting the policy. The ticket is an HMAC under the Worker-wide TICKET_SECRET and carries no policy version, so an order prepared before PAYMENT_TOKENS changed is still approved until its deadline, and a retried approve of a held order is signed again. I reproduced this with a copy of the signer and ethers 6.17 outside the repository: a signer limited to imdUSD refused `prepare` for USDC but `approve` of a USDC ticket held 1,600 GOLD and returned a valid signature with `mintPaid.token` = USDC. The window is bounded by the order TTL (600 s default, 900 s max), hence Low. Fix: add the same `allowedTokens` check in `approve` before `gold.reserve`, plus a test for it.\n\n**Verified clean, with evidence.**\n- Token binding (Q1): a Foundry test against the standard-input sources (solc 0.8.38, viaIR, cancun) confirms an order signed for imdUSD v1 is refused in v2 at the same GOLD price and in USDC, a USDC order is refused in v1, and an unlisted token fails before the signature check. The contract type string, the signer's PAID_TYPES, the GenesisGoldHold consent and the GOLD book's stored order all carry the token. The ticket binds the token, so a ticket cannot be reused for another token.\n- GOLD book (Q3): `parseOrder` requires `goldCost` to equal the price listed for the order's own token, so no hold exists at an unlisted price or for an unlisted token. The price-free path runs only for an identical retry of an already stored order.\n- usableAt guard (Q4): the Foundry test shows `addPaymentToken` accepts at the last second before `usableAt` reaches 2^40 and refuses the next second. The larger digest changes nothing else in `mintPaid`.\n\n**Limits.** The signer's Node suite and the GOLD book tests could not be run as shipped because the World sources and the vendored ethers are not in the package. The reproduction used only the signer files plus an installed ethers. The scratch Foundry test lives in `test/scratch/TokenBinding.t.sol` and is not part of the submission.","treeHash":null,"usage":{"cachedInputTokens":2422526,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":36762,"runtime":"claude","turns":41,"wallClockMs":510230}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"f2101c240e0b52a9","findings":[{"citation":"resolved","description":"Submission 7 answers audit 944905c3 finding 2 (a listed token can never be disabled on chain) with an off-chain policy: PaidApprovalSigner.allowedTokens (Worker variable PAYMENT_TOKENS, worker-entry.mjs line 32) is meant to let the signer 'stop issuing orders for a token' that stays listed on chain. The only use of the policy is in prepare() (line 70: `if(this.allowedTokens&&!this.allowedTokens.includes(token))fail('PAYMENT_TOKEN_UNAVAILABLE');`). approve() (lines 103-127) verifies the HMAC ticket (line 107), the uri hash and the wallet's consent, calls gold.reserve (line 114) and signs PAID_TYPES over order.token (lines 121-122) without ever reading this.allowedTokens (the field appears only at lines 44, 45, 51, 52 and 70). The ticket is an HMAC over [deploymentId, chainId, contract, order fields] under TICKET_SECRET, which worker-entry.mjs hands to every PaidApprovalSigner instance; it carries no policy version and has no lifetime of its own. So every order prepared while the token was still allowed (or by the Worker before PAYMENT_TOKENS was set) stays approvable after the operator excludes the token, for the rest of its deadline window (PAID_TTL_SECONDS, 600 s by default, 900 s at most): GOLD is held at that token's price and a PaidAuthorization naming the excluded token is issued, which mintPaid accepts because the on-chain entry stays listed for good. A retried approve of an order the book already holds is signed again as well (the book answers 'held' from the stored order and the signature is deterministic). The policy therefore takes effect only after the longest outstanding ticket expires, not when PAYMENT_TOKENS changes, which matters exactly in the cases the policy was added for (USDC recipient blacklisted, imdUSD v1 retired at the v2 migration). The AUDIT_README (section 1 finding 2, section 2 question 2) and the signer header comment present allowedTokens as the way to stop issuing orders for a token; question 2 itself names 'a retried approve' as a case to check. The shipped test (tests/signer/paid.test.mjs lines 150-153) exercises imdOnly.prepare only; no test approves an order prepared under a wider policy. Impact is bounded: the token is still listed and usable on chain, GOLD is held at the registry's listed price and the buyer pays the full dollar amount, so no value is lost; what fails is the operator's ability to cut a token off immediately. A ticket made for another token does not bypass anything: changing order.token alone fails the HMAC (ORDER_TICKET_INVALID, verified). Fix (keeps the design): in approve(), after the ticket check and before gold.reserve, add `if(this.allowedTokens&&!this.allowedTokens.includes(order.token))fail('PAYMENT_TOKEN_UNAVAILABLE');` (order.token is the lowercase value prepare produced; the ticket guarantees it), so neither a new hold nor a signature is issued for an excluded token and an order already held is released by the book at its deadline as today. Add a test that prepares a USDC order with an unrestricted signer and asserts imdOnly.approve(...) rejects with PAYMENT_TOKEN_UNAVAILABLE and that gold.reserve was not called. Optionally fold a policy identifier (sorted allowed list) into the ticket HMAC, or document that TICKET_SECRET is rotated together with PAYMENT_TOKENS, so outstanding tickets die at once. Merged from four specialist reports (economics, flow, permissions, math) that describe the same mechanism.","line":114,"path":"Submission7/offchain/signer/paid-approval.mjs","reproduction":"Run with Node 24 and ethers 6.17 against the unmodified Submission7/offchain/signer/*.mjs (copied next to a vendor/ethers.min.js that re-exports ethers; approve reads nothing from the chain, prepare's contract reads are stubbed). State: imdUSD v1 = 0xa1..a1 (1,200 GOLD, 20e18) and USDC = 0xb2..b2 (1,600 GOLD, 20e6) listed and usable; block.timestamp 1800000000; GOLD binding stub records reserve calls and answers {state:'held'}. Two PaidApprovalSigner instances share ticketSecret 'rehearsal-ticket-secret-0123456789abcdef', signer key, chainId 11155111 and contract (the Worker before and after a PAYMENT_TOKENS change). Steps: (1) open = new PaidApprovalSigner({...common}) with no allowedTokens; p = await open.prepare({wallet:alice, look, choice:1, token:USDC}) -> p.order.token = '0xb2b2...b2b2', p.order.goldCost = '1600', deadline 1800000600. (2) imdOnly = new PaidApprovalSigner({...common, allowedTokens:[IMD]}); imdOnly.prepare({..., token:USDC}) rejects with PAYMENT_TOKEN_UNAVAILABLE (as documented). (3) consentSignature = alice.signTypedData(p.consent.domain, p.consent.types, p.consent.message); a = await imdOnly.approve({order:p.order, uri:p.uri, ticket:p.ticket, consentSignature}). Expected (AUDIT_README section 1 finding 2 / section 2 question 2): rejects with PAYMENT_TOKEN_UNAVAILABLE and gold.reserve is not called. Actual (observed output): 'imdOnly.approve(USDC order) -> SIGNED; mintPaid.token = 0xb2b2b2b2b2B2b2B2B2b2b2B2B2b2B2B2b2b2b2b2 ; signature by signer = true ; holds = [[\"0xb2b2...b2b2\",\"1600\"]]' (verifyTypedData over PAID_TYPES with token = USDC recovers the approval signer, which is exactly the digest mintPaid rebuilds at contract line 626, so mintPaid(..., token = USDC) succeeds on chain). (4) imdOnly.approve(same request) again: 'retried approve -> SIGNED again; same signature = true'. (5) Control: imdOnly.approve({...req, order:{...p.order, token:IMD}}) -> ORDER_TICKET_INVALID (the ticket does bind the token). Script: test/scratch/sig/repro.mjs.","severity":"low","snippet":"  try{held=await this.gold.reserve({deploymentId:this.deploymentId,order,consentSignature});}","title":"Signer token policy (allowedTokens / PAYMENT_TOKENS) is enforced in prepare only: approve still holds GOLD and signs a PaidAuthorization for a token the policy excludes (pre-policy ticket or retried a"},{"citation":"resolved","description":"The constructor normalizes each allowed token with wallet(t), the request-field validator, which throws ApprovalError('WALLET_INVALID', 400). Every other configuration check in the constructor throws a CONFIG_* code with status 500 (CONFIG_CHAIN_ID, CONFIG_METADATA_BASE, CONFIG_TTL, CONFIG_TICKET_SECRET, CONFIG_GOLD_BINDING, and CONFIG_PAYMENT_TOKENS for an empty list on line 51). worker-entry.mjs line 32 splits PAYMENT_TOKENS on commas and trims but does not validate the entries, and worker.mjs line 68 constructs the PaidApprovalSigner inside the request's try block, so the ApprovalError is caught at worker.mjs line 91 and returned to the client as HTTP 400 {ok:false,error:{code:'WALLET_INVALID'}} on every /v1/paid/prepare and /v1/approval/paid call; console.error at line 92 runs only for non-ApprovalError exceptions, so nothing is logged. Because the paid signer is constructed per request this is not a one-time startup failure but a total outage of paid minting that is misattributed to the buyer's wallet. Fix: validate allowedTokens with its own code, e.g. this.allowedTokens=allowedTokens?.map(t=>{try{const a=getAddress(t);if(a===ZeroAddress)throw 0;return a.toLowerCase();}catch{fail('CONFIG_PAYMENT_TOKENS',500);}})??null; so a misconfiguration surfaces as a 500 that monitoring catches, and document PAYMENT_TOKENS in offchain/signer/README.md.","line":52,"path":"Submission7/offchain/signer/paid-approval.mjs","reproduction":"Node 24, ethers 6.17, unmodified Submission7/offchain/signer/{worker,paid-approval}.mjs. handler = createHandler({approver, paidApprover: async()=>new PaidApprovalSigner({...valid options, allowedTokens:['0xa1..a1','0xabc']}), allowedOrigins:['https://mint.example.test']}) (what worker-entry.mjs builds from PAYMENT_TOKENS='0xa1..a1,0xabc'). Request: POST https://signer.example.test/v1/paid/prepare, Origin https://mint.example.test, Content-Type application/json, body {wallet:<valid>, look:<valid>, choice:1, token:<imdUSD>}. Expected: a 500 CONFIG_PAYMENT_TOKENS (as for every other constructor misconfiguration) or a refusal to start. Actual (observed output): 'PAYMENT_TOKENS=\"...,0xabc\": /v1/paid/prepare -> 400 {\"ok\":false,\"error\":{\"code\":\"WALLET_INVALID\"}}'; the same for /v1/approval/paid; no console.error. Script: test/scratch/sig/repro.mjs (last step).","severity":"info","snippet":"  this.allowedTokens=allowedTokens?.map(t=>wallet(t).toLowerCase())??null;","title":"A malformed PAYMENT_TOKENS entry is reported to every buyer as 400 WALLET_INVALID instead of a 500 CONFIG_* error, and the paid route goes dark without a server-side log"},{"citation":"resolved","description":"Audit 944905c3 finding 2 is answered off chain: the operator stops issuing orders for a listed token by setting the Worker variable PAYMENT_TOKENS (worker-entry.mjs lines 31-32, PaidApprovalSigner.allowedTokens). The operator document for the Worker, offchain/signer/README.md, is unchanged from Submission 6 and its paid-route section (line 90) still lists only the GOLD binding, GOLD_DEPLOYMENT_ID and TICKET_SECRET. PAYMENT_TOKENS, its format (comma-separated addresses), that an empty value fails the Worker (CONFIG_PAYMENT_TOKENS) and that the policy is applied at prepare only (see the low finding) appear nowhere; PAID_TTL_SECONDS is also undocumented. Since an on-chain entry can never be removed, this variable is the only operational control for a token and belongs in the runbook. Fix: document PAYMENT_TOKENS (and PAID_TTL_SECONDS) next to the paid-route variables, including the behaviour of orders already prepared when the value changes and the recommendation to rotate TICKET_SECRET with it.","line":90,"path":"Submission7/offchain/signer/README.md","reproduction":"grep -n PAYMENT_TOKENS Submission7/offchain/signer/README.md returns nothing; grep -n PAID_TTL Submission7/offchain/signer/README.md returns nothing; diff -q Submission6/offchain/signer/README.md Submission7/offchain/signer/README.md reports no difference, while Submission7/offchain/signer/worker-entry.mjs lines 30-32 read env.PAID_TTL_SECONDS and env.PAYMENT_TOKENS. Expected: the README's Worker configuration section names PAYMENT_TOKENS and its semantics. Actual: not mentioned.","severity":"info","snippet":"要開啟付費路線，Worker 必須有 `GOLD` binding、`GOLD_DEPLOYMENT_ID` 和 `TICKET_SECRET`，缺一個就回 501。Sepolia 的設定在 `wrangler.sepolia.toml`，binding 指向 `imd-world-gold-sepolia`，**不是**正式的 `imd-world`。","title":"The new signer policy variable PAYMENT_TOKENS (and PAID_TTL_SECONDS) is not documented in the signer README, which is byte-identical to Submission 6"}],"hash":"ccc6be6a724843f45db0cf1f4e3beb0810b7313a3f5777b94652e38cd05225bb","nodeId":"5fdaafe8-093a-4c29-b4e7-adacc91d65b4","outcome":"completed","summary":"Review complete. The findings file holds three substantiated findings, all in the off-chain signer; the contract changes check out.\n\n**What I verified**\n\n- **Signer policy bypass (Low, merged from four specialist reports).** `allowedTokens` is checked only in `prepare`. With the unmodified Submission7 signer code under Node 24 and ethers 6.17, an order prepared for USDC by an unrestricted signer was approved by an imdUSD-only signer sharing the same ticket secret. The GOLD book stub was asked to hold USDC's price, the returned PaidAuthorization named USDC and recovered to the approval signer, and a retried approve returned the same signature. Swapping the token in the order alone fails the HMAC ticket, so the only gap is the missing policy check before the hold and signature. The fix is one line in approve before the GOLD reserve call, plus a test.\n- **Malformed PAYMENT_TOKENS surfaces as 400 WALLET_INVALID (Info).** The constructor validates policy entries with the request-field validator. Through the worker handler, an entry like `0xabc` turned every paid prepare into a 400 WALLET_INVALID with no server log, a silent paid-route outage blamed on the buyer.\n- **PAYMENT_TOKENS undocumented (Info).** The signer README is byte-identical to Submission 6 and never mentions PAYMENT_TOKENS or PAID_TTL_SECONDS.\n\n**What did not reproduce as a defect**\n\nI compiled the contract from `build/standard-input.json` with forge (solc 0.8.38, via-IR, cancun) and got the receipt's exact runtime size. A four-test Foundry suite against it confirmed: an order signed for imdUSD v2 is refused in v1 and USDC at the same GOLD price, a USDC order pays only in USDC and only at 1,600 GOLD, an unlisted token is refused before the signature, and the `usableAt >> 40` check accepts an add today and refuses only from timestamp 2^40 minus two days. The GOLD book's parseOrder refuses a price no listed token charges and a token the entry does not list; its retry path compares the stored document byte for byte. The dropped specialist items were only duplicates of the Low finding.\n\n**Limits.** The book's address normalizer lives in economy-config.ts, which is not in the package, so the book's case handling rests on its own shipped tests. No critical or high finding exists, so no proof test was attached. Scratch files live under test/scratch/ and a temporary build directory outside the repository.","treeHash":null,"usage":{"cachedInputTokens":1656533,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":30821,"runtime":"claude","turns":38,"wallClockMs":447809}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"89214b73ec1e0b7b","findings":[{"citation":"resolved","description":"Submission7 answers audit 944905c3 finding 2 by letting the signer stop issuing orders for a listed token through allowedTokens (Worker variable PAYMENT_TOKENS). The check exists only in prepare (line 70: `if(this.allowedTokens&&!this.allowedTokens.includes(token))fail('PAYMENT_TOKEN_UNAVAILABLE')`). approve (lines 103-127) validates the HMAC ticket, the uri hash and the wallet consent, calls gold.reserve and signs PAID_TYPES with token = order.token, but never compares order.token with this.allowedTokens. The ticket is an HMAC over the order with TICKET_SECRET only; it does not depend on the policy, has no expiry of its own, and approve does not check order.deadline either (only the GOLD book does, for a fresh hold). So every order prepared before the policy change, by any Worker instance sharing TICKET_SECRET (the old version during a rolling redeploy, or the same Worker before PAYMENT_TOKENS was set), is still approved: GOLD is held at that token's price and the signer returns a valid PaidAuthorization naming the disallowed token, which mintPaid accepts because the token stays listed on chain. A retried approve of an already-held order returns the same signature again, and on the retry path the book skips the deadline check (genesis-gold.ts line 118 uses min(now, deadline-1)), so the signature is re-issued for as long as the hold exists. Impact: the operator's only way to stop a token without pausing everything (the fix chosen for finding 2) does not take effect for in-flight orders for up to the order TTL (PAID_TTL_SECONDS, default 600 s, at most 900 s), and the README's claim that a signer limited to imdUSD 'refuses USDC' holds for prepare only. Fix: in approve, after the ticket check, refuse an order whose token is outside the policy: `if(this.allowedTokens&&!this.allowedTokens.includes(String(order.token).toLowerCase()))fail('PAYMENT_TOKEN_UNAVAILABLE');` (before gold.reserve, so no GOLD is held for it; an order already held before the policy change is then released by the cron at its deadline, as any unused order is). Add a test next to tests/signer/paid.test.mjs line 152 that prepares a USDC order with an unrestricted signer and asserts imdOnly.approve(...) rejects with PAYMENT_TOKEN_UNAVAILABLE.","line":107,"path":"Submission7/offchain/signer/paid-approval.mjs","reproduction":"State: imdUSD v1 (1,200 GOLD) and USDC (1,600 GOLD, usable) listed on chain; TICKET_SECRET shared by both signer instances. Steps (run against the package's signer with a mocked read-only chain; same result with the Node harness of tests/signer/paid.test.mjs): (1) open = new PaidApprovalSigner({...base}) (no allowedTokens); p = await open.prepare({wallet, look, choice:1, token: USDC}) -> p.order.token = USDC, p.order.goldCost = '1600'. (2) imdOnly = new PaidApprovalSigner({...base, allowedTokens:[IMD]}); imdOnly.prepare({..., token: USDC}) rejects with PAYMENT_TOKEN_UNAVAILABLE (as documented). (3) a = await imdOnly.approve({order: p.order, uri: p.uri, ticket: p.ticket, consentSignature}) where consentSignature is the wallet's GenesisGoldHold signature over p.consent. Expected: PAYMENT_TOKEN_UNAVAILABLE and no GOLD held. Actual: resolves; gold.reserve was called with order.token = USDC, goldCost '1600' (GOLD held), a.mintPaid.token = USDC (checksummed) and a.mintPaid.goldSignature verifies under PAID_TYPES/domain for token = USDC by the signer key, so mintPaid(..., token = USDC) succeeds on chain. (4) imdOnly.approve(same request) again resolves with the identical signature. Observed output of the reproduction script: 'prepare (imdUSD only) refused with PAYMENT_TOKEN_UNAVAILABLE' then 'approve (imdUSD only): ACCEPTED; mintPaid.token = 0xb2b2...; holds = [[\"0xb2b2...\",\"1600\"]]; signature by signer = true' and 'retried approve (imdUSD only): ACCEPTED again; same signature = true'.","severity":"low","snippet":"  if(typeof ticket!=='string'||ticket!==await this.ticket(order))fail('ORDER_TICKET_INVALID',400);","title":"Signer policy (allowedTokens) is enforced in prepare only: approve still holds GOLD and signs a PaidAuthorization for a token outside the policy"},{"citation":"resolved","description":"Audit 944905c3 finding 2 is answered off chain: the operator stops issuing orders for a listed token by setting the Worker variable PAYMENT_TOKENS (worker-entry.mjs line 31-32, PaidApprovalSigner.allowedTokens). The operator document for the Worker, offchain/signer/README.md, is byte-identical to Submission 6 (diff empty) and its paid-route section (line 90) still lists only the GOLD binding, GOLD_DEPLOYMENT_ID and TICKET_SECRET; PAYMENT_TOKENS, its format (comma-separated addresses), the fact that an empty list fails the Worker at construction (CONFIG_PAYMENT_TOKENS, 500) and that the policy is applied per prepare (see the low finding on approve) appear nowhere. Since the on-chain entry can never be removed, this variable is the only operational control for a token and should be in the runbook. Fix: document PAYMENT_TOKENS next to the paid-route variables (and PAID_TTL_SECONDS, which the README also omits), including the retry behaviour of orders already prepared when the value changes.","line":90,"path":"Submission7/offchain/signer/README.md","reproduction":"grep -n PAYMENT_TOKENS Submission7/offchain/signer/README.md returns nothing; diff Submission6/offchain/signer/README.md Submission7/offchain/signer/README.md is empty, while Submission7/offchain/signer/worker-entry.mjs line 31-32 reads env.PAYMENT_TOKENS. Expected: the README's Worker configuration section names PAYMENT_TOKENS and its semantics. Actual: not mentioned.","severity":"info","snippet":"要開啟付費路線，Worker 必須有 `GOLD` binding、`GOLD_DEPLOYMENT_ID` 和 `TICKET_SECRET`，缺一個就回 501。Sepolia 的設定在 `wrangler.sepolia.toml`，binding 指向 `imd-world-gold-sepolia`，**不是**正式的 `imd-world`。","title":"The new signer policy variable PAYMENT_TOKENS (allowedTokens) is not documented in the signer README, which is unchanged from Submission 6"}],"hash":"e3674d8010721aaf052136ea6492eb43fed1020275dac0be0969fa8da772e1c0","nodeId":"b70318c7-3cff-46a6-8191-fd29e0bbaa16","outcome":"completed","summary":"The review is complete and the findings file is written. Here is the recap.\n\n**Outcome.** Submission7's token binding holds on chain and in the GOLD book. One real gap is in the signer: the new `allowedTokens` policy is enforced only in `prepare`, not in `approve`. Two findings are in `.imd-findings.json`, one low and one info. No critical or high findings, so no Foundry proof was needed. The tree has no Foundry project and the Node test harness references folders not in the package, so I verified the signer gap with a scratch Node run outside the repository using the package's own signer sources and a mocked chain. That run printed the exact behaviour quoted in the finding and was deleted afterwards.\n\n**Findings**\n\n- **Low, signer policy not applied in approve.** With the signer limited to imdUSD, a USDC order prepared earlier by any instance sharing the ticket secret is still accepted by `approve`: GOLD is held at 1,600 and a valid PaidAuthorization naming USDC is signed, which the contract accepts since USDC stays listed. A retried approve re-signs it. The window is the order TTL, up to 900 seconds. Fix: refuse `order.token` outside the policy in `approve` before `gold.reserve`.\n- **Info, documentation.** The signer README is byte-identical to Submission 6 and never mentions `PAYMENT_TOKENS`, the only operational control for a listed token.\n\n**Answers to the four questions, with no defect found**\n\n1. **Token binding.** The digest includes the token and that token's own GOLD price, both read on chain, so v1, v2 and USDC orders are mutually unusable at equal or different prices. The HMAC ticket, the wallet consent and the stored book document all carry the token. Case is normalized at every comparison and an unlisted token fails on chain, in the signer and in the registry.\n2. **Signer policy.** `prepare` enforces it correctly and a ticket cannot be altered to another token. The gap is the approve path above.\n3. **GOLD book.** A hold needs `goldCost` equal to the price the registry lists for exactly that token. An unlisted token yields no price and fails. The retry path skips the price check only for a byte-identical stored order.\n4. **usableAt and digest.** The 40-bit check can only refuse an add after the year 36812 and packing has no bit overlap. The larger digest changes nothing but the added token field, and its field order matches the signer's type.\n\n**Not covered.** The World book's `economy-config` helpers and the D1 schema are not in the package, so their address normalization and integer limits are taken from how the shipped tests use them. The package's Node suites could not be executed here.","treeHash":null,"usage":{"cachedInputTokens":1976338,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":29970,"runtime":"claude","turns":33,"wallClockMs":432484}}],"verification":[]}