# Audit report

> Public review export cc7b7b6b58078c5cd3ecba00e8650348fe2cbcb0. Producing World source 1015b2c5c8663c6c4bfb46f135be84cacea1cae0.
>
> Review exact selected World source commit 1015b2c5c8663c6c4bfb46f135be84cacea1cae0, including three newly reproduced fixes and the entire existing staking, payments, shared GOLD, recovery, inventory, campaign and provider boundary. GOLD is a nontransferable/nonredeemable wallet-bound ledger credit, not a token, trading pair or AMM. No browser balance/receipt/game score can mint it. Retail topup/service/Mint admission remains closed; prices and real game NFT collections are unset. The existing owner-signed mainnet contracts are pinned, not redeployed by this release.
>
> R1: sixteen unsupported historical service jobs previously occupied the whole delivery page forever. SQL now filters provider-supported result-contract products before LIMIT16. Reproduce with sixteen fortune jobs then one research job; later resume the original jobs with their matching provider. Check bounded paging, SQL parameter binding, lease-expiry equality, independent provider idempotency and absence of new charges, deleted rights or invented refunds.
>
> R2: a valid800120-byte Python document produces a2400495-byte read response because the protocol carries both the document and canonical JSON string. The old2100000 limit stranded that delivered report. Bound reads at3*900000+16000, preserving16KB delivery summaries, stream cancellation,10s network timeout, fresh response HMAC, original order/wallet/reference and canonical-document hash. Test oversized/malformed/unauthenticated responses and CPU/memory limits separately; the size bound is not production capacity proof.
>
> R3: legacy GET /api/me/economy previously returned private results after logout/session expiry or a backward clock during I/O. Revalidate the original session/token/address/expiry and a monotonic clock before returning;401/503 must carry no private data. Check both legacy and new reads/writes for equivalent asynchronous-boundary problems.
>
> Build tools keep the original Wrangler/Miniflare versions, with exact patched sharp0.35.5 and source-map-js1.2.2 overrides. The clean isolated install and native D1 tests are separate evidence from the advisory scan. Do not equate absent known advisories with zero application defects.
>
> Recheck prior d6268e87 M1/M2/L1/I1 remediation: deny both unlinked legacy currencies in shared mode; reject unstructured error strings as reports; refuse expired Mint holds at fresh application and SQL boundaries; refuse an expired pending quote retry without revoking paid rights. Trace every credit/debit/hold/owed entitlement/refund/admin repair, immutable price version, old paid case while paused, capacity accounting, UTC/deadline/lease equalities, schema guard removal, concurrency, crash/replay and recovery. Verify inventory ownership/CAS/current housing/public-private projection and the empty NFT allowlist; trial campaign start/end/stop and external-profile exclusion. Only research has an authenticated actual VPS adapter. Read source manifests, portable adaptations, context/review-fixes-267fcf01.patch, scope and current evidence. Provide actionable reproductions, severity, fixes and regressions. Prepared materials/public availability, production deployment, capacity proof, paid submission and an accepted verdict are distinct facts.

| | |
|---|---|
| Repository | https://github.com/tungweb3/imd-ember-world-review.git |
| Commit | `cc7b7b6b58078c5cd3ecba00e8650348fe2cbcb0` |
| Job | `a211537e-407d-4494-bfbb-159f2665bc33` |
| Judged | 2026-10-19 14:50 UTC |
| Findings | 1 medium · 3 low · 1 info |

Four agents audited the code as it is at `cc7b7b6`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. Medium: Legacy GOLD challenge/pay admission is not scoped to the installed provider's products: a pending service quote for an unserved product is debited into a job that R1 paging never claims and no refund

`world-source/server/economy-ledger.ts:217`

```
  if(serviceReady!==true)throw new EconomyConflict('SERVICE_DELIVERY_UNAVAILABLE');
```

Service admission is split across two entry points with different readiness predicates. POST /api/gold/quote (world-gold-api.ts:63) is product-scoped: serviceReady = deps.serviceReady && serviceProducts.includes(product), so with the production wiring (worker/app.ts: serviceProducts = delivery.products = ['research']) a new fortune quote is refused with SERVICE_DELIVERY_UNAVAILABLE. The legacy money routes POST /api/economy/orders/:id/gold/challenge and POST /api/economy/orders/:id/gold (economy.ts:80 and :82) pass only the global boolean deps.serviceReady = hasGoldDeliveryProvider(delivery) into sharedServiceAdmission (economy-ledger.ts:212-221), which checks that some provider exists (line 217) and that the product has a result contract (line 218, true for both 'research' and 'fortune'). It never consults the provider product list. A still-pending linked service quote for a product the connected provider does not serve (quoted while that product was ready: provider swap, product-list change) is therefore challenged, signed, debited and turned into a durable world_gold_jobs row inside the pay batch (line 311). After R1, runGoldServiceJobs filters p.product IN (provider.products) before LIMIT 16, so the research adapter never selects the job; it stays 'queued' indefinitely. refundGold requires state='failed' and the admin refund-service command requires state IN ('failed','refunded'), so neither returns the GOLD. This contradicts REQUIREMENTS.md (a service debit creates one serviceable job) and the R1 acceptance note about absence of new charges for unsupported products. Precondition: a pending (not yet expired, 15 min) linked quote for a product outside the connected provider list, so a product/provider transition is needed and likelihood is low; the stranded debit with no repair path is the defect. Merged from audit_permissions and audit_economics (same root cause, same fix). Fix: pass serviceProducts through EconomyDeps into createEconomyGoldChallenge/payEconomyOrderGold and in sharedServiceAdmission require serviceProducts===undefined || serviceProducts.includes(linked.product), failing with SERVICE_DELIVERY_UNAVAILABLE before any debit (mirroring world-gold-api.ts:63); optionally add the same predicate to the SQL admission in payEconomyOrderGold so a removed application guard is caught. Regression: challenge/pay for a pending research quote still succeeds with products=['research']; a pending fortune quote gets SERVICE_DELIVERY_UNAVAILABLE and keeps its GOLD.

**Reproduction**

Reproduced with node --test against an isolated copy of world-source with its exact lock (Node 24), importing tests/world-gold-audit5-fixture.mjs. f=await fixture(); await f.topup() (book '10'); f.admin({action:'product',product:'fortune',kind:'service',title:'Fortune',gold:'1',enabled:true,version:1}); id=await f.quote('fortune') (pending linked order, as a fortune-capable wiring would have created). Insert an EOA/ECDSA session for W (token 'q'*43, expires NOW+60000), send cookie __Host-imd_session=q*43 with origin https://imdember.com. deps={db,config,now:()=>NOW+1,allow:async()=>true,serviceReady:true,serviceProducts:['research']} (production research-only wiring). Control: POST /api/gold/quote {expectedAddress:W,requestId:'s'*20,product:'fortune'} -> observed 503 SERVICE_DELIVERY_UNAVAILABLE. Then handleEconomyApi POST /api/economy/orders/<id>/gold/challenge {expectedAddress:W,requestId:'t'*20} -> observed 200 with a challenge; sign its message with the fixture account; POST /api/economy/orders/<id>/gold {expectedAddress:W,requestId:'t'*20,signature} -> observed 200 order.status='paid'. Expected: refusal with SERVICE_DELIVERY_UNAVAILABLE and no debit, like the quote route. Observed state: worldGoldSnapshot book '9', world_gold_jobs row state='queued'. runGoldServiceJobs(db,{id:'aimarket-vps-v1',products:['research'],deliver}) at NOW+1 and again after reconcileWorldGold at NOW+2 -> returns [] and deliver is never called; job still 'queued'. refundGold(db,id,NOW+3) -> rejects REFUND_NOT_ENTITLED; goldAdminCommand refund-service for the order -> rejects (write-check accepted=1 CHECK). Book stays '9'.

### 2. Low: Post-I/O monotonic-clock check on the new private reads (/api/me/gold, /api/gold/state, /api/me/aimarket-trial, /api/me/balances) only bounds against route start, so a clock regression during the snap

`world-source/server/world-gold-api.ts:47`

```
  const authReply=async(value:unknown)=>{const at=clock(),fresh=await readSession(request,deps.db!,at);if(!Number.isSafeInteger(at)||at<started||typeof fresh==='string'||fresh.tokenHash!==session.tokenHash||fresh.address.toLowerCase()!==wallet||fresh.expiresAt<=clock())return bad(401,'AUTH_REQUIRED');return reply(200,value);};
```

R3 made GET /api/me/economy (economy.ts:58-64) require each later clock sample to be >= the previously accepted one (finished>=now, returned>=finished) and answer 503 ECONOMY_UNAVAILABLE on any regression, then re-check the exact session expiry. The newer private reads do not apply the equivalent boundary. In world-gold-api.ts:47 authReply only requires at>=started (the sample taken before the first session read); it never compares against `now` (line 43), the sample that already authorised the request and stamped the snapshot (asOf=now), and the final clock() used for fresh.expiresAt<=clock() is not validated at all. member-balances.ts:70-71 has the same shape (freshAt>=started only, final clock() unvalidated). world-gold-report.ts:21 (at>=start only, final deps.now() unvalidated, regression answers 401 not 503) and aimarket-campaign.ts:42-43 (freshAt>=at, final deps.now() unvalidated) share the pattern. If the clock moves backward during the snapshot I/O to any value in [started, now), the late revalidation runs at an instant earlier than one the route already observed and the completed snapshot (book, reserved, available, history, orders, mints, pendingRewards; or embeo/staked/gold) is returned with 200 and an asOf that the route's own later clock contradicts. Because line 44 already rejects a session whose expiry is <= now, an actually-expired session cannot slip through this gap; the defect is the missing monotonic guard and parity with the R3 contract, so severity is low. Merged from audit_permissions and audit_flow (two findings on the same mechanism). Fix: keep a running `last` sample; require every new sample, including the one compared with expiresAt, to be a safe integer >= last and return 503 on regression as economy.ts does; compare fresh.expiresAt===session.expiresAt as member-balances already does. Add regressions for /api/me/gold, /api/gold/state, /api/me/aimarket-trial and /api/me/balances mirroring tests/economy-read-session.test.mjs backward-clock.

**Reproduction**

Reproduced with node --test against an isolated copy of world-source (Node 24) using tests/world-gold-audit5-fixture.mjs. f=await fixture(); await f.topup(); insert sessions row for W with expires_at=NOW+30, wallet_type 'EOA', verification_method 'ECDSA', token 'r'*43. deps.now returns the sequence [NOW, NOW, NOW+10] then NOW+5 for every later call (started=NOW, session read at NOW, pre-I/O now=NOW+10, every post-I/O sample NOW+5: a regression during the snapshot read). GET https://imdember.com/api/me/economy?expected_address=W with cookie __Host-imd_session=r*43 via handleEconomyApi({db,config,now,allow:async()=>true}) -> observed 503 (correct). Same request to /api/me/gold via handleWorldGoldApi with the same deps -> expected 503 with no private fields; observed 200 with body keys wallet, book, reserved, available, asOf, history, orders, mints, pendingRewards, pendingRewardsLimit, historyLimit, goldTransferable. Same sequence on GET /api/me/balances via handleMemberBalances({db,config:async()=>config,chain:{},allow:async()=>true,now,client:'c'}) -> expected 503; observed 200 {wallet, asOf, embeo:{status:'unavailable'}, staked, gold:{status:'ok',book,reserved,available}}.

### 3. Low: POST /api/gold/orders/:id/reconcile is the only ledger write without an in-batch live-session predicate or post-write revalidation: a session revoked after the fresh read still drives a committed cred

`world-source/server/world-gold-api.ts:70`

```
  const result=await reconcileWorldGold(deps.db,clock(),retry![1]);return result.failures.length?reply(409,{ok:false,error:result.failures[0].code}):reply(200,{ok:true});
```

R3 asks that new and legacy reads and writes be checked for equivalent asynchronous-boundary problems. Every other persistent write on this surface rechecks the live session inside the D1 batch after its awaits: goldQuote and goldAdminCommand bind authCheck (world-gold.ts:15, :61, :196) with EXISTS(SELECT 1 FROM sessions ... revoked_at IS NULL AND expires_at>?); createEconomyOrder, createEconomyGoldChallenge and payEconomyOrderGold embed authorizationSql (economy-ledger.ts:201-203); the inventory write embeds liveSql. The reconcile route reads the session at lines 42-44 and again at line 60, then awaits the order lookup (line 69) and calls reconcileWorldGold (line 70), whose statements (topup credit INSERT, reconcile_errors DELETE, refundGold batch in world-gold.ts:69-93) carry no session predicate, and the handler returns 200 without revalidating. A logout or expiry that commits between the fresh read and the batch therefore still produces a committed ledger write attributed to that request and a 200 response. Harm is bounded: the credit or refund is one the paid order is already entitled to (the same statement runs from the minute cron), the response carries no private data, and no new charge, deleted right or invented refund results. It is an asymmetry in the boundary this release claims to have closed, not a loss, so severity is low. Fix: when invoked from the HTTP route, include authCheck(db,wallet,now,{tokenHash:fresh.tokenHash}) (plus DELETE FROM world_gold_write_checks) in the reconcile batch, or revalidate the session after reconcileWorldGold and answer 401 (the write is idempotent, so a 401 after a committed credit is still correct), mirroring authReply.

**Reproduction**

Reproduced with node --test against an isolated copy of world-source (Node 24) using tests/world-gold-audit5-fixture.mjs with all migrations. f=await fixture(); await f.topup(); id=await f.quote('pack'); await f.paid(id) (paid topup, not yet credited; snapshot book '10'). Insert a live EOA/ECDSA session for W (token 'p'*43, expires NOW+60000). Wrap f.db.prepare so that the second execution of 'SELECT address,expires_at,revoked_at,wallet_type,verification_method FROM sessions WHERE token_hash=?1' (the fresh read at world-gold-api.ts:60) runs UPDATE sessions SET revoked_at=NOW+1 WHERE token_hash=sha256(token) immediately after it returns the still-live row (a logout committing during the awaits before the write). POST https://imdember.com/api/gold/orders/<id>/reconcile with headers origin=https://imdember.com, content-type=application/json, cookie __Host-imd_session=p*43, body {"expectedAddress":W}; deps {db,config,now:()=>NOW+2,allow:async()=>true,serviceReady:true}. Expected (the contract the other writes keep): the batch matches no live session, nothing is written by this request, response 401 AUTH_REQUIRED. Observed: status 200 body {ok:true}; sessions.revoked_at is set; worldGoldSnapshot book moved from '10' to '20' (the topup credit was committed by the revoked-session request). Control from the specialist run, consistent with the code at world-gold.ts:61: the same wrapper on POST /api/gold/quote fails the batch's authCheck (503 GOLD_UNAVAILABLE) and inserts no order.

### 4. Low: GET /api/gold/orders/:id/report accepts sessions that every other private GOLD/economy read refuses (no verification-method or safe-integer expiry check, no expiresAt equality on revalidation), return

`world-source/server/world-gold-report.ts:15`

```
    const s=await readSession(request,deps.db,start);if(typeof s==='string')return reply(401,{error:'AUTH_REQUIRED'});
```

handleWorldGoldReport (the R2 read-back route that fetches up to 2.7 MB from the VPS and returns the whole report document) authenticates only with readSession plus the expected_address match. Every sibling private read applies a stricter predicate: /api/me/gold and /api/gold/state (world-gold-api.ts:44) require verificationMethod in ['ECDSA','ERC1271'] and expiresAt>now; /api/me/economy (economy.ts:45) requires the same plus a safe-integer expiry; /api/me/balances (member-balances.ts:71) and the R3 legacy fix (economy.ts:64) additionally pin fresh.expiresAt===session.expiresAt on revalidation. The report route omits all three (line 15 and line 21 check tokenHash/address/expiry>now only). A sessions row whose wallet_type/verification_method are NULL (rows predating the migration 0003 columns, or any row written without them) therefore cannot read its own balance but can read the paid research document, and the asynchronous-boundary policy for this new read is weaker than the one R3 just established for the legacy read. The document belongs to the session's own wallet, so this is a policy inconsistency rather than a cross-wallet disclosure; severity low. Fix: after readSession apply the same predicate as world-gold-api.ts:44 (verificationMethod in ['ECDSA','ERC1271'], Number.isSafeInteger(expiresAt), expiresAt>start) and compare fresh.expiresAt===s.expiresAt on the post-readReport revalidation. Regression: an EOA/ECDSA session still receives 200 with the document; the logout/expiry/backward-clock cases from tests/economy-read-session.test.mjs applied to this route must return 401/503 without a document.

**Reproduction**

Reproduced with node --test against an isolated copy of world-source (Node 24) using tests/world-gold-audit5-fixture.mjs. f=await fixture(); await f.topup(); id=await f.service(); runGoldServiceJobs(db,{id:'stub',products:['research'],deliver:async()=>({kind:'delivered',result:{provider:'aimarket-vps-v1',report:'Published research: 2026-10-19',reportId:'a'*64,reportDate:'2026-10-19',documentHash:'a'*64}})},()=>NOW+1) so the job is delivered. Insert sessions(token_hash=sha256('z'*43),address=W,chain_id=1,created_at=NOW-1,expires_at=NOW+30,nonce='n-zzzz',wallet_type=NULL,verification_method=NULL). GET https://imdember.com/api/me/gold?expected_address=W with cookie __Host-imd_session=z*43 via handleWorldGoldApi({db,config,now:()=>NOW,allow:async()=>true}) -> observed 401 AUTH_REQUIRED. GET https://imdember.com/api/gold/orders/<id>/report?expected_address=W with the same cookie via handleWorldGoldReport({db,provider:{readReport:async()=>({report_id:'a'*64,content:'secret'})},now:()=>NOW,allow:async()=>true}) -> expected 401 AUTH_REQUIRED (same session policy as the other private reads); observed 200 {orderId:<id>,document:{report_id:'aaaa...',content:'secret'}}.

### 5. Info: Admin product creation ignores the per-product provider-support guard the API computes, so an enabled service product no installed provider serves is published to every wallet while its quote always a

`world-source/server/world-gold.ts:197`

```
 if(cmd.action==='product'){
```

handleWorldGoldApi (world-gold-api.ts:65) computes ready=deps.serviceReady&&(!deps.serviceProducts||cmd.action!=='product'||cmd.kind!=='service'||deps.serviceProducts.includes(cmd.product)) and passes it as serviceReady to goldAdminCommand, clearly intending that a service product can only be defined for a product the installed provider supports. goldAdminCommand reads args.serviceReady only in the control branch (line 207, feature 'service'); the product branch at line 197-201 never checks it. With the production wiring (serviceProducts=['research'] from the aimarket adapter) an owner wallet can therefore create fortune:1 kind=service enabled=1. The row is immutable (world_gold_products_no_update), is returned to every signed-in wallet in /api/gold/state products with a GOLD price, and POST /api/gold/quote for it always answers 503 SERVICE_DELIVERY_UNAVAILABLE. No GOLD moves and no job is created, so this is a dead-guard/consistency defect rather than a fund risk; it does not by itself enable the pay-admission finding because the quote route stays closed. Fix: in the product branch fail('SERVICE_DELIVERY_UNAVAILABLE') when cmd.kind==='service'&&cmd.enabled&&args.serviceReady!==true (mirroring the control branch), or remove the dead computation at world-gold-api.ts:65 if publishing unservable products is intended.

**Reproduction**

Reproduced with node --test against an isolated copy of world-source (Node 24) using tests/world-gold-audit5-fixture.mjs. f=await fixture(); insert an EOA/ECDSA session for OWNER (token 'o'*43). deps={db,config,now:()=>NOW,allow:async()=>true,serviceReady:true,serviceProducts:['research']}. POST https://imdember.com/api/gold/admin (origin https://imdember.com, content-type application/json, cookie __Host-imd_session=o*43) body {expectedAddress:OWNER,requestId:'u'*20,reason:'probe reason',command:{action:'product',product:'fortune',kind:'service',title:'Fortune',gold:'1',enabled:true,version:1}}. Expected: refused with SERVICE_DELIVERY_UNAVAILABLE because ready=false for fortune. Observed: 200 {ok:true,result:{id,results:[1,1,1,1,1,3]}}; world_gold_products contains {id:'fortune:1',enabled:1}. Follow-up POST /api/gold/quote {expectedAddress:OWNER,requestId:'v'*20,product:'fortune'} with the same deps -> 503 SERVICE_DELIVERY_UNAVAILABLE.

---

Judge's submission `5490756eaaf21027e30d992b21f09a9fc077827bef86a2a3ef3b93847a237863`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
