garrytan/gstack · warning · StrictModeError

remote image blocked (offline posture): ${src} — re-run with

Error message

remote image blocked (offline posture): ${src} — re-run without --strict or pass --allow-network

What it means

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.

Source

Thrown at make-pdf/src/diagram-prepass.ts:607

  const memo = new Map<string, { dataUri: string; attrs: string }>();

  return html.replace(IMG_TAG_RE, (tag) => {
    const srcMatch = tag.match(SRC_RE);
    if (!srcMatch) return tag;
    const src = srcMatch[2] ?? srcMatch[3] ?? "";

    if (src.startsWith("data:")) return annotateFromDataUri(tag, src);

    // Windows drive-letter paths (C:/x.png, C:\x.png) look like single-letter
    // URL schemes — they are local paths, not URLs.
    const isDrivePath = /^[a-zA-Z]:[\\/]/.test(src);

    if (!isDrivePath && /^[a-z][a-z0-9+.-]*:/i.test(src)) {
      // Absolute URL with a scheme (http, https, file, …)
      if (opts.allowNetwork && /^https?:/i.test(src)) return tag;
      if (/^https?:/i.test(src)) {
        const msg = `remote image blocked (offline posture): ${src}`;
        if (opts.strict) throw new StrictModeError(msg + " — re-run without --strict or pass --allow-network");
        opts.warn(msg);
        // Leaving the tag would make Chromium fetch it at print time anyway —
        // the warn would be a lie. Replace with a visible placeholder.
        return buildBlockedRemotePlaceholder(src);
      }
      // file:// and friends fall through to the local path branch
      if (!src.startsWith("file:")) return tag;
    }

    // decodeURIComponent throws on malformed escapes (foo%zz.png) — a broken
    // URL must degrade to the missing-image path, not crash the run.
    let decodedSrc = src;
    try {
      decodedSrc = decodeURIComponent(src);
    } catch { /* keep raw src */ }

    const filePath = src.startsWith("file:")
      ? fileURLToPath(src)

View on GitHub (pinned to 94993f7401)

Solutions

  1. Download the remote image locally and rewrite the markdown src to a relative path.
  2. Re-run without --strict (it becomes a warn + placeholder) if network independence is not required.
  3. Pass --allow-network to permit http(s) fetches (Chromium fetches at print time).
  4. Convert the remote image to a data: URI embedded in the markdown.

Example fix

<!-- before -->
![logo](https://cdn.example.com/logo.png)

<!-- after: download and reference locally -->
![logo](./assets/logo.png)
Defensive patterns

Strategy: validation

Validate before calling

// Scan markdown for remote images before invoking strict mode.
function findRemoteImages(markdown: string): string[] {
  const re = /!\[[^\]]*\]\((https?:\/\/[^)]+)\)/g;
  const out: string[] = [];
  let m: RegExpExecArray | null;
  while ((m = re.exec(markdown))) out.push(m[1]);
  return out;
}

const remotes = findRemoteImages(markdown);
if (opts.strict && !opts.allowNetwork && remotes.length) {
  throw new Error(`strict mode blocks ${remotes.length} remote image(s): ${remotes.join(', ')}`);
}

Type guard

import { StrictModeError } from './diagram-prepass';
function isStrictModeError(e: unknown): e is StrictModeError {
  return e instanceof StrictModeError;
}

Try / catch

try {
  inlineLocalImages(html, { strict: true, allowNetwork: false, ... });
} catch (e) {
  if (e instanceof StrictModeError && /remote image blocked/.test(e.message)) {
    // downgrade to non-strict and accept placeholders
    inlineLocalImages(html, { strict: false, allowNetwork: false, ... });
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/7b781d4480bd5822. Report an issue: GitHub.