← back to Visual Factory
prefetch: agent:false to force fresh socket per request
19c935ed8526c185c4943ca9636ed566e9142590 · 2026-04-30 23:49:26 -0700 · SteveStudio2
The default global Agent pools sockets keyed by host:port. A pooled socket
opened on a previously-validated IP could be reused by a future request
without the lookup callback firing — which is safe (the pooled IP was
validated) but obscures the pin. agent:false forces a one-shot Agent so
the lookup callback runs every prefetch, leaving no ambiguity about
whether DNS pinning was honored.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 19c935ed8526c185c4943ca9636ed566e9142590
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Thu Apr 30 23:49:26 2026 -0700
prefetch: agent:false to force fresh socket per request
The default global Agent pools sockets keyed by host:port. A pooled socket
opened on a previously-validated IP could be reused by a future request
without the lookup callback firing — which is safe (the pooled IP was
validated) but obscures the pin. agent:false forces a one-shot Agent so
the lookup callback runs every prefetch, leaving no ambiguity about
whether DNS pinning was honored.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
server.js | 24 +++++++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/server.js b/server.js
index d4f222b..cc9ba7e 100644
--- a/server.js
+++ b/server.js
@@ -334,6 +334,10 @@ async function safeFetchToBuffer(input, { maxBytes, timeoutMs }) {
path: (u.pathname || '/') + (u.search || ''),
headers: { Host: u.host, 'User-Agent': 'visual-factory-prefetch/1' },
timeout: timeoutMs,
+ // agent:false forces a fresh socket per request so the global keep-alive
+ // pool can't hand back a connection that bypasses our `lookup` callback.
+ // Marginally slower; eliminates ambiguity on whether the pin was honored.
+ agent: false,
// Custom DNS lookup → always returns the validated IP, regardless of
// what real DNS says now. This is the DNS pin.
lookup: (_h, _o, cb) => cb(null, pinnedIp, pinnedFamily),
@@ -827,10 +831,13 @@ app.post('/runs/:id/activate', async (req, res) => {
// fails with EEXIST if the target already exists). When overwrite=true,
// accept the inherent rename-clobber race (atomic at the FS level — one
// of two concurrent renames wins, neither corrupts).
+ // Cycle-2 Claude review P1: stat the destination unconditionally so the
+ // audit event correctly reports `existed_before=true` on overwrite. The
+ // 409 short-circuit only applies when overwrite=false.
let existed = false;
- if (!overwrite) {
- try { await fs.access(destPng); existed = true; } catch {}
- if (existed) return res.status(409).json({ error: `target exists: ${destPng}`, hint: 'pass ?overwrite=true' });
+ try { await fs.access(destPng); existed = true; } catch {}
+ if (existed && !overwrite) {
+ return res.status(409).json({ error: `target exists: ${destPng}`, hint: 'pass ?overwrite=true' });
}
// Codex P1 #8 fix: copy to .tmp first, claim destination atomically, then
@@ -854,6 +861,17 @@ app.post('/runs/:id/activate', async (req, res) => {
if (tmpHtml) await fs.rm(tmpHtml, { force: true }).catch(() => {});
return res.status(409).json({ error: `target exists: ${destPng} (lost activation race)`, hint: 'pass ?overwrite=true' });
}
+ // Cycle-2 Claude review P2: PUBLISH_ROOT on a different filesystem
+ // from output/ produces EXDEV (or ENOTSUP on some FS). Surface a
+ // hint instead of an opaque 500.
+ if (e.code === 'EXDEV' || e.code === 'ENOTSUP') {
+ await fs.rm(tmpPng, { force: true }).catch(() => {});
+ if (tmpHtml) await fs.rm(tmpHtml, { force: true }).catch(() => {});
+ return res.status(500).json({
+ error: `cross-device link not supported (${e.code})`,
+ hint: 'PUBLISH_ROOT and output/ are on different filesystems. Set PUBLISH_ROOT to a path on the same device, or accept the rename-clobber semantics with ?overwrite=true (which will hit the same EXDEV).',
+ });
+ }
throw e;
}
await fs.rm(tmpPng, { force: true }).catch(() => {});
← 05edcc6 harden prefetchAndRehost: pin DNS, stream size, magic bytes,
·
back to Visual Factory
·
fix(ssrf): IPv6 reserved-range blocklist now catches in-rang ee44a36 →