jackwener/OpenCLI · error · CommandExecutionError

${label} returned an untrusted Pixiv image URL

Error message

${label} returned an untrusted Pixiv image URL

What it means

This CommandExecutionError is thrown by parsePixivImageUrl in clis/pixiv/bookmark-download.js:44-46 as a security check: the URL parses but is not a trusted Pixiv CDN image. It must satisfy ALL of: https: protocol, hostname i.pximg.net, no embedded username/password, no explicit port, and a file extension mapped in IMAGE_CONTENT_TYPES (.jpg/.jpeg/.png/.gif/.webp). Any violation rejects the URL to prevent SSRF or downloading unexpected content.

Source

Thrown at clis/pixiv/bookmark-download.js:45

  if (value !== true) {
    throw new ArgumentError('Refusing to write local Pixiv downloads: pass --execute');
  }
}

function parsePixivImageUrl(value, label) {
  if (typeof value !== 'string' || !value) {
    throw new CommandExecutionError(`${label} returned a missing image URL`);
  }
  let url;
  try {
    url = new URL(value);
  } catch {
    throw new CommandExecutionError(`${label} returned a malformed image URL`);
  }
  const extension = path.extname(url.pathname).toLowerCase();
  const contentType = IMAGE_CONTENT_TYPES.get(extension);
  if (url.protocol !== 'https:' || url.hostname !== 'i.pximg.net' || url.username || url.password || url.port || !contentType) {
    throw new CommandExecutionError(`${label} returned an untrusted Pixiv image URL`);
  }
  return { url: url.href, extension, contentType };
}

async function prepareIllustPlan(page, row, outputRoot) {
  const pages = await pixivFetch(page, `/ajax/illust/${row.illust_id}/pages`, {
    notFoundMsg: `Illustration not found: ${row.illust_id}`,
  });
  if (!Array.isArray(pages)) {
    throw new CommandExecutionError('Pixiv pages API returned malformed payload');
  }
  if (pages.length === 0) {
    throw new EmptyResultError('pixiv bookmark-download', `No images found for illustration ${row.illust_id}.`);
  }
  const files = pages.map((entry, index) => {
    if (!entry || Array.isArray(entry) || typeof entry !== 'object' || !entry.urls || Array.isArray(entry.urls) || typeof entry.urls !== 'object') {
      throw new CommandExecutionError(`Pixiv illustration ${row.illust_id} returned malformed page ${index + 1}`);
    }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the offending URL and check which condition failed (scheme, host, credentials, port, or extension)
  2. Confirm the content type is actually an image — ugoira/zip or non-image assets are intentionally rejected by this command
  3. Re-authenticate and retry: restricted sessions can make Pixiv return alternate hosts
  4. If Pixiv legitimately changed its CDN host or added extensions, update the hostname allowlist or IMAGE_CONTENT_TYPES map accordingly
Defensive patterns

Strategy: validation

Validate before calling

function isTrustedPixivImageUrl(value) {
  if (typeof value !== 'string' || !value) return false;
  let url;
  try { url = new URL(value); } catch { return false; }
  const okExt = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].includes(
    require('node:path').extname(url.pathname).toLowerCase());
  return url.protocol === 'https:' &&
    url.hostname === 'i.pximg.net' &&
    !url.username && !url.password && !url.port && okExt;
}
if (!isTrustedPixivImageUrl(raw)) console.warn('Untrusted image URL:', raw);

Type guard

const TRUSTED_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp']);
function isPximgImageUrl(value) {
  let url;
  try { url = new URL(value); } catch { return false; }
  return url.protocol === 'https:' &&
    url.hostname === 'i.pximg.net' &&
    !url.username && !url.password && !url.port &&
    TRUSTED_EXTENSIONS.has(path.extname(url.pathname).toLowerCase());
}

Try / catch

try {
  const parsed = parsePixivImageUrl(value, label);
} catch (e) {
  if (e instanceof CommandExecutionError && e.message.includes('untrusted Pixiv image URL')) {
    console.warn(`${label}: URL failed the pximg allowlist — skipping (possible ugoira/non-image asset)`);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: The pages API returns an image URL that is http:, points at a different host (e.g. embed.pixiv.net or a mirrored host), embeds credentials, specifies a port, or has an unmapped extension (e.g. .zip, no extension), failing the guard at clis/pixiv/bookmark-download.js:44.

Common situations: Pixiv serving ugoira/animation entries whose sources are not plain images; novels or other asset types leaking into the illust download path; a changed CDN hostname after a Pixiv update; URLs from a third-party mirror or proxy injected into the response; extension-less URLs for certain content types.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/4aaeb95f285c4a21. Report an issue: GitHub.