{"record":{"id":"7b781d4480bd5822","repo":"garrytan/gstack","slug":"remote-image-blocked-offline-posture-src-r","errorCode":null,"errorMessage":"remote image blocked (offline posture): ${src} — re-run without --strict or pass --allow-network","messagePattern":"remote image blocked \\(offline posture\\): (.+?) — re-run without --strict or pass --allow-network","errorType":"validation","errorClass":"StrictModeError","httpStatus":null,"severity":"warning","filePath":"make-pdf/src/diagram-prepass.ts","lineNumber":607,"sourceCode":"  const memo = new Map<string, { dataUri: string; attrs: string }>();\n\n  return html.replace(IMG_TAG_RE, (tag) => {\n    const srcMatch = tag.match(SRC_RE);\n    if (!srcMatch) return tag;\n    const src = srcMatch[2] ?? srcMatch[3] ?? \"\";\n\n    if (src.startsWith(\"data:\")) return annotateFromDataUri(tag, src);\n\n    // Windows drive-letter paths (C:/x.png, C:\\x.png) look like single-letter\n    // URL schemes — they are local paths, not URLs.\n    const isDrivePath = /^[a-zA-Z]:[\\\\/]/.test(src);\n\n    if (!isDrivePath && /^[a-z][a-z0-9+.-]*:/i.test(src)) {\n      // Absolute URL with a scheme (http, https, file, …)\n      if (opts.allowNetwork && /^https?:/i.test(src)) return tag;\n      if (/^https?:/i.test(src)) {\n        const msg = `remote image blocked (offline posture): ${src}`;\n        if (opts.strict) throw new StrictModeError(msg + \" — re-run without --strict or pass --allow-network\");\n        opts.warn(msg);\n        // Leaving the tag would make Chromium fetch it at print time anyway —\n        // the warn would be a lie. Replace with a visible placeholder.\n        return buildBlockedRemotePlaceholder(src);\n      }\n      // file:// and friends fall through to the local path branch\n      if (!src.startsWith(\"file:\")) return tag;\n    }\n\n    // decodeURIComponent throws on malformed escapes (foo%zz.png) — a broken\n    // URL must degrade to the missing-image path, not crash the run.\n    let decodedSrc = src;\n    try {\n      decodedSrc = decodeURIComponent(src);\n    } catch { /* keep raw src */ }\n\n    const filePath = src.startsWith(\"file:\")\n      ? fileURLToPath(src)","sourceCodeStart":589,"sourceCodeEnd":625,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/make-pdf/src/diagram-prepass.ts#L589-L625","documentation":"StrictModeError thrown by inlineLocalImages() when an <img src=\"https://...\"> is encountered, --allow-network is not set, AND --strict is set. make-pdf runs in an offline posture by default (Chromium would fetch the URL at print time, leaking and risking inconsistency); strict mode turns the warn into a fatal error so a self-contained PDF is guaranteed.","triggerScenarios":"inlineLocalImages() with opts.strict=true and opts.allowNetwork=false (or unset), processing markdown containing ![alt](https://example.com/img.png). The regex matches an absolute http/https URL, the allowNetwork branch is skipped, and the strict branch throws.","commonSituations":"Running make-pdf --strict on docs that embed a CDN-hosted diagram; a markdown importer that rewrote local paths to absolute URLs; a CI policy mandating --strict for reproducible builds; forgetting to bundle images locally before a strict render.","solutions":["Download the remote image locally and rewrite the markdown src to a relative path.","Re-run without --strict (it becomes a warn + placeholder) if network independence is not required.","Pass --allow-network to permit http(s) fetches (Chromium fetches at print time).","Convert the remote image to a data: URI embedded in the markdown."],"exampleFix":"<!-- before -->\n![logo](https://cdn.example.com/logo.png)\n\n<!-- after: download and reference locally -->\n![logo](./assets/logo.png)","handlingStrategy":"validation","validationCode":"// Scan markdown for remote images before invoking strict mode.\nfunction findRemoteImages(markdown: string): string[] {\n  const re = /!\\[[^\\]]*\\]\\((https?:\\/\\/[^)]+)\\)/g;\n  const out: string[] = [];\n  let m: RegExpExecArray | null;\n  while ((m = re.exec(markdown))) out.push(m[1]);\n  return out;\n}\n\nconst remotes = findRemoteImages(markdown);\nif (opts.strict && !opts.allowNetwork && remotes.length) {\n  throw new Error(`strict mode blocks ${remotes.length} remote image(s): ${remotes.join(', ')}`);\n}","typeGuard":"import { StrictModeError } from './diagram-prepass';\nfunction isStrictModeError(e: unknown): e is StrictModeError {\n  return e instanceof StrictModeError;\n}","tryCatchPattern":"try {\n  inlineLocalImages(html, { strict: true, allowNetwork: false, ... });\n} catch (e) {\n  if (e instanceof StrictModeError && /remote image blocked/.test(e.message)) {\n    // downgrade to non-strict and accept placeholders\n    inlineLocalImages(html, { strict: false, allowNetwork: false, ... });\n  } else throw e;\n}","preventionTips":["Localize all images (download to ./assets) before a strict render.","Embed small images as data: URIs.","Only enable --allow-network in trusted, online environments.","Audit markdown for absolute URLs before CI (`grep -nE '!\\\\[[^]]*\\\\]\\\\(https?://'`)."],"tags":["make-pdf","image","offline","strict-mode","network"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}