[object Object]

← 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 →