gitroomhq/postiz-app · error · Error

Unsafe URL

Error message

Unsafe URL

What it means

Thrown by CloudflareStorage.uploadSimple when the input path is a remote URL (not a data: URL) that fails the isSafePublicHttpsUrl SSRF check. This is a deliberate security guard: only public HTTPS URLs are fetched, blocking private/reserved IP ranges, non-HTTPS schemes, and hosts that resolve to internal addresses.

Source

Thrown at libraries/nestjs-libraries/src/upload/cloudflare.storage.ts:88

              (request.headers as Record<string, string>)[key] = value;
            }
          );

          return next(args);
        },
      { step: 'build', name: 'customHeaders' }
    );
  }

  async uploadSimple(path: string) {
    const dataUrl = path.startsWith('data:') ? parseDataUrl(path) : null;

    let body: Buffer;
    if (dataUrl) {
      body = dataUrl.buffer;
    } else {
      if (!(await isSafePublicHttpsUrl(path))) {
        throw new Error('Unsafe URL');
      }
      const loadImage = await fetch(path, {
        // @ts-ignore — undici option, not in lib.dom fetch types
        dispatcher: ssrfSafeDispatcher,
      });
      body = Buffer.from(await loadImage.arrayBuffer());
    }
    const detected = await fileTypeFromBuffer(body);
    if (!detected || !ALLOWED_MIME_TYPES.has(detected.mime)) {
      throw new Error('Unsupported file type.');
    }
    const extension = detected.ext;
    const safeContentType = detected.mime;
    const id = makeId(10);

    const params = {
      Bucket: this._bucketName,
      Key: `${id}.${extension}`,

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Use an https:// URL — http:// is always rejected
  2. Ensure the host resolves to a public IP (no split-horizon DNS pointing to internal ranges)
  3. If developing locally with service names/localhost, upload a data: URL or a real public URL instead
  4. Do not bypass the check; it exists to prevent SSRF against internal services

Example fix

// before
await storage.uploadSimple('http://cdn.example.com/pic.png'); // throws Unsafe URL

// after
await storage.uploadSimple('https://cdn.example.com/pic.png');
Defensive patterns

Strategy: validation

Validate before calling

import { isSafePublicHttpsUrl } from '@gitroom/nestjs-libraries/dtos/webhooks/webhook.url.validator';

const safe = path.startsWith('data:') || (await isSafePublicHttpsUrl(path));
if (!safe) {
  throw new BadRequestException('Only public https URLs or data URLs can be uploaded');
}

Type guard

function isUploadableUrl(u: string): boolean {
  if (u.startsWith('data:')) return true;
  try { return new URL(u).protocol === 'https:'; } catch { return false; }
}

Try / catch

try {
  url = await storage.uploadSimple(path);
} catch (e) {
  if (e instanceof Error && e.message === 'Unsafe URL') {
    throw new BadRequestException('URL must be a public https URL');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling uploadSimple('http://example.com/img.png') (plain http), a URL whose DNS resolves to a private IP (10.x/127.x/169.254.x/etc.), a URL with credentials or unusual scheme, or an invalid URL string.

Common situations: User-supplied media URLs from social providers that still use http://; localhost/internal URLs passed during local dev; URLs behind DNS that intermittently resolves to internal ranges; test fixtures using http://test-host.

Related errors


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/58966d393da13e40. Report an issue: GitHub.