{"slug":"visual-factory","total":10,"limit":100,"offset":0,"since":null,"commits":[{"hash":"26fb67d","date":"2026-05-19 17:25:40 -0700","author":"Steve","subject":"refactor: add .bak/.pre-/.orig 404 guard + broaden .gitignore snapshot patterns","body":"Express middleware now hard-404s any request for snapshot artifacts before\nthe static handler can serve them. .gitignore picks up the same patterns so\nauto-gitify won't sweep them into the repo.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"73b0163","date":"2026-05-13 08:58:42 -0700","author":"Steve","subject":"snapshot: 9 file(s) changed, +3 new, ~6 modified","body":""},{"hash":"407e25d","date":"2026-05-07 12:29:34 -0700","author":"Steve","subject":"tighten .gitignore: add missing standing-rule patterns (tmp/ dist/ build/ .next/)","body":""},{"hash":"ee44a36","date":"2026-05-01 18:50:22 -0700","author":"Steve","subject":"fix(ssrf): IPv6 reserved-range blocklist now catches in-range forms (codex P2)","body":"The textual prefix checks at server.js:190-194 missed valid in-range IPv6\nforms because they only matched specific shapes:\n  - 100::1 sits inside 100::/64 but failed /^100:0?:0?:0?:/\n  - fed0::1 and fee0::1 sit inside fec0::/10 but didn't match /^fec/\n  - any compressed (::) form drifted from the prefix regex\n\nReplaced with proper expand-then-bit-prefix-match:\n  _expandIPv6(addr) — normalize to 8-group, 4-hex-char-each canonical form\n  _ipv6InRange(addr, prefix, bits) — CIDR mask comparison\n\n7 RFC reserved ranges checked: fe80::/10, fc00::/7, 100::/64, 64:ff9b::/96,\n2001:db8::/32, fec0::/10, ff00::/8.\n\nVerified with node test: 100::1, fed0::1, fee0::1, 2001:db8::1 all now\nreturn true; IPv4 + hostnames return null (untouched).\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"19c935e","date":"2026-04-30 23:49:26 -0700","author":"SteveStudio2","subject":"prefetch: agent:false to force fresh socket per request","body":"The default global Agent pools sockets keyed by host:port. A pooled socket\nopened on a previously-validated IP could be reused by a future request\nwithout the lookup callback firing — which is safe (the pooled IP was\nvalidated) but obscures the pin. agent:false forces a one-shot Agent so\nthe lookup callback runs every prefetch, leaving no ambiguity about\nwhether DNS pinning was honored.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"05edcc6","date":"2026-04-30 23:46:57 -0700","author":"SteveStudio2","subject":"harden prefetchAndRehost: pin DNS, stream size, magic bytes, realpath","body":"- Replace whatwg fetch() with safeFetchToBuffer using http(s).request +\n  custom lookup callback. Hostname resolves once, validated IP gets pinned;\n  the actual TCP connection lands on that IP, so DNS rebinding between\n  validation and fetch can't redirect us into private space.\n- Stream-read body with running total; abort mid-stream once the chunks\n  exceed _MAX_PREFETCH_BYTES (25MB). A server lying about Content-Length\n  (or omitting it) can no longer OOM us.\n- _detectImageMagic: verify PNG / JPEG / GIF / WebP signatures in the\n  body bytes. Reject when Content-Type and bytes disagree -- don't trust\n  the header alone.\n- /runs/:id/external/:fname: fs.realpath() then re-assert containment in\n  SANDBOX_OUTPUT before sendFile, so a future symlink can't escape.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"30c6d24","date":"2026-04-30 23:32:16 -0700","author":"temp","subject":"B: pre-fetch user image_url and re-host on loopback (closes DNS rebinding)","body":""},{"hash":"5de52a6","date":"2026-04-30 23:26:47 -0700","author":"temp","subject":"use DNS-aware SSRF gate on /runs/:id/post-ig","body":""},{"hash":"287ac34","date":"2026-04-30 23:24:27 -0700","author":"temp","subject":"apply codex p2/p2/p3 abort-handling fixes","body":""},{"hash":"7c502d8","date":"2026-04-30 23:06:29 -0700","author":"temp","subject":"snapshot for codex review","body":""}]}