3rd-audit closeout + adversarial-review hardening (F13 double-spend, worker guard, F21)#459
Merged
Merged
Conversation
… ceiling The per-IP hit maps (waitlistHits, regByIp) only pruned an entry when the SAME IP returned, so a flood of one-time IPs accumulated keys without bound; the waitlist also had only a per-IP limit, so a distributed source (many IPs each under the limit) could mass-insert leads. src/rate-sweep.js (unit-tested, 10 cases): - sweepStaleTsMap(map, windowMs) evicts keys whose timestamps are all stale. - makeWindowCounter(windowMs, limit) is a global rolling-window ceiling. server.js wiring: - waitlist: a global ceiling (60/min across ALL IPs, checked after the per-IP gate) + an inline stale-sweep backstop when the map exceeds 5000 keys. - register: inline stale-sweep backstop (it already had a 30/hr global cap). - a 60s periodic sweep of both maps (mirrors the powChallengeHits / operatorSessions sweeps). Also adds docs/audit-closeout-f01-f25.md — the final ledger for the 3rd audit: 21/25 Fixed, F22 Accepted (Low, CSP inline; no live XSS, nonce refactor too risky for the severity), and copy-paste owner steps for the 4 owner-gated items (F01 rulesets, F13 capacity decision, F17 DMARC, F25 DNSSEC/CAA/MTA-STS/www). full test-all clean (214 strict + 284 lenient, 0 failures). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d rate maps) + audit closeout F21 (Low) — bound the one-time-IP rate maps (waitlistHits, regByIp) with a periodic + inline stale sweep and add a global waitlist ceiling (src/rate-sweep.js, 10 unit tests). Closes the memory-growth + distributed mass-submit finding. F13 (Med) — idempotent durable credit for capacity-refused paid renders, approach B, FLAGGED OFF (RENDER_CREDIT_ENABLED, default off → billing byte-identical). x402 settles before the handler, so a paid render/screenshot that then 503s on the bounded Chromium pool was charged but not served. On such a 503 the server mints a one-time 256-bit credit TOKEN, returned only in that buyer's own 503 (X-Render-Credit header + renderCredit body), bound to route+body; a retry presenting the token skips the gate and is served without a second charge, consumed only on 200, surviving repeated 503s. Security: the token is a server-minted bearer secret handed only to the paying caller (NOT the public payer address), so a third party knowing a wallet can't forge or steal it; bound to route+body so it can't be spent elsewhere. Verified live that a forged token still 402s. src/render-credit.js + 21 unit tests (single-use, forgery/cross-request rejection, TTL, 503-persistence, eviction). Boot verified flag on and off; full test-all clean (214 strict + 284 lenient). docs/audit-closeout-f01-f25.md — final ledger for the 3rd audit: 23/25 Fixed (F13 flag-off), F22 Accepted (Low CSP inline; no live XSS), and copy-paste owner steps for F01 rulesets, F17 DMARC, F25 DNSSEC/CAA/MTA-STS/www. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ouble-spend + worker guard) Independent auditor agents reviewed everything built this session. The earlier audit batches (F03/F09/F10/F12/F14/F15/F16/F20/F23/F24) and F21/F11/F07 verified clean. Three real issues were found in code added this session and are fixed here (F13 was flag-OFF, so prod was never exposed): F13 render-credit — CONFIRMED double-spend (TOCTOU). The credit was validated as a read (valid()) and consumed on res 'finish', so a burst of concurrent retries with the same token all passed validation before any finished → many free renders from one paid credit. FIX: atomic admission — claim() validates AND removes the token in one synchronous step; only the first of N concurrent retries wins, the rest fall through to the paywall. A failed pre-paid retry re-issues a fresh token so the credit isn't lost. Test asserts exactly 1 of 100 concurrent claims succeeds (24 cases). Worker isolation (F02/F04/F06): - C1a: the boot guard was an incomplete denylist (12 names) while the app reads 30+ secret env vars. Now a pattern-based fail-closed guard (KEY|SECRET|TOKEN|MNEMONIC|PRIVATE|PASSWORD|CREDENTIAL + non-pattern extras) so an unlisted secret (GITHUB_TOKEN, E2B_API_KEY, provider keys, burners) also blocks boot. Enforced at boot only (isMain), never on import, so a CI process with its own GITHUB_TOKEN can still import the exports. Railway health-gates the deploy, so a false positive fails the new deploy, not prod. - C1b: /call auth failed OPEN when RENDER_WORKER_TOKEN was empty. Now fails closed, and the worker refuses to boot without a token. - Tool dispatch uses Object.hasOwn so slug "constructor"/"toString" can't hit an inherited prototype function. - F04 defense-in-depth: Chromium launched with --proxy-bypass-list=<-loopback> so loopback/link-local literals (incl 169.254.169.254 metadata) also traverse the validating egress proxy, making it the single chokepoint. The DNS-rebinding defense itself reviewed SOLID (no hole). Full test-all clean (214 strict + 284 lenient, 0 failures); all offline security suites green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…RAILWAY_PRIVATE_DOMAIN) The pattern-based boot guard matched RAILWAY_PRIVATE_DOMAIN — Railway's own private-network hostname, not a secret — via the bare "PRIVATE" token, so the hardened worker refused to boot. Railway's health-gated rollout caught it (new deploy failed, old worker kept serving; prod render unaffected). Fix: drop "PRIVATE" from the pattern (a private KEY is already caught by "KEY"; matching "PRIVATE" alone false-positives on the infra var). Extract the guard logic into an exported pure `forbiddenSecretsIn(env)` and unit-test the exact regression: the secretless worker env (incl. RAILWAY_PRIVATE_DOMAIN + its own token) does NOT trip the guard, while GITHUB_TOKEN / E2B_API_KEY / burners / provider keys / WALLET_ADDRESS / DATABASE_URL still do. 27/27 offline; a clean Railway-like env boots green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MikeyPetrillo
temporarily deployed
to
agent402 / production
July 19, 2026 13:58 — with
GitHub Actions
Inactive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the last code-actionable items of the 3rd security audit and hardens what was built, after an independent adversarial self-review of the whole session's work.
Findings closed
waitlistHits,regByIp) with a periodic + inline stale sweep, and added a global waitlist ceiling.src/rate-sweep.js+ 10 unit tests.RENDER_CREDIT_ENABLED, default off → billing byte-identical). On a 503 of a settled paid render/screenshot the server mints a one-time 256-bit credit token, returned only in that buyer's 503, bound to route+body; a retry presenting it is served without a second charge.src/render-credit.js+ 24 unit tests.docs/audit-closeout-f01-f25.md: 23/25 Fixed, F22 Accepted (Low CSP inline, no live XSS), and copy-paste owner steps for F01 rulesets, F17 DMARC, F25 DNS.Adversarial self-review (this is the important part)
Four independent auditor agents were told to hunt for exploits. The earlier batches (F03/F09/F10/F12/F14/F15/F16/F20/F23/F24) and F21/F11/F07 verified clean; the F04 DNS-rebinding defense reviewed solid. Three real issues in code added this session were found and fixed here:
claim()(validate-and-remove in one synchronous step) + re-issue on failed delivery. Test asserts exactly 1 of 100 concurrent claims wins. F13 was flag-OFF, so prod was never exposed.GITHUB_TOKEN/E2B_API_KEY/burners/provider keys all block boot), enforced at boot only (import-safe). Railway health-gates the deploy, so a false positive fails the new deploy, not prod./callauth (C1b) — failed open on an empty token. Now fails closed + the worker refuses to boot withoutRENDER_WORKER_TOKEN. PlusObject.hasOwndispatch and--proxy-bypass-list=<-loopback>so metadata/loopback literals also traverse the F04 egress proxy.Tests
New offline suites wired into CI:
test-rate-sweep.js(10),test-render-credit.js(24, incl. concurrent-claim double-spend),test-egress-proxy.js(12),test-worker-isolation.js(17). Fulltest-allclean (214 strict + 284 lenient, 0 failures).Rollback
F13 stays OFF unless
RENDER_CREDIT_ENABLEDis set. Worker hardening is health-gated. No settlement path changed while the flag is off.