schollz/croc · error · Error

Stored-transfer origin must contain only an HTTPS scheme and

Error message

Stored-transfer origin must contain only an HTTPS scheme and host

What it means

normalizeOrigin() enforces that a stored share's origin is a bare HTTPS origin (or HTTP on localhost/127.0.0.1/[::1]) with no username/password, path, query, or fragment. Anything else — a path suffix, credentials, ftp/file scheme, or plain HTTP on a non-loopback host — throws, because the origin is embedded in share URLs/CLI tokens and used as the trust anchor for later fetches.

Source

Thrown at web/src/protocol/stored.ts:142

  }
}

function normalizeOrigin(value: string) {
  const parsed = new URL(value);
  const loopback =
    parsed.hostname === "localhost" ||
    parsed.hostname === "127.0.0.1" ||
    parsed.hostname === "[::1]" ||
    parsed.hostname === "::1";
  if (
    (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && loopback)) ||
    parsed.username ||
    parsed.password ||
    (parsed.pathname !== "/" && parsed.pathname !== "") ||
    parsed.search ||
    parsed.hash
  ) {
    throw new Error("Stored-transfer origin must contain only an HTTPS scheme and host");
  }
  return parsed.origin;
}

function validateShare(share: StoredShare) {
  if (!/^[A-Za-z0-9_-]{22}$/.test(share.id)) {
    throw new Error("Invalid stored-transfer id");
  }
  if (share.key.byteLength !== storedKeyBytes) {
    throw new Error("Invalid stored-transfer key");
  }
  share.origin = normalizeOrigin(share.origin);
  return share;
}

export function formatStoredBrowserURL(share: StoredShare) {
  validateShare(share);
  return `${share.origin}/s/${share.id}#v1.${base64URL(share.key)}`;

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Serve the web client over HTTPS (or on localhost/127.0.0.1) so window.location.origin passes
  2. Strip any path/query from the origin embedded in tokens — only scheme://host[:port] is allowed
  3. Re-copy the share token from the sender unedited; do not paste it through URL-shorteners or re-host it under a subpath
Defensive patterns

Strategy: validation

Validate before calling

const isValidOrigin = (value: string): boolean => { try { const u = new URL(value); const loopback = ["localhost","127.0.0.1","[::1]","::1"].includes(u.hostname); return (u.protocol === "https:" || (u.protocol === "http:" && loopback)) && !u.username && !u.password && (u.pathname === "/" || u.pathname === "") && !u.search && !u.hash; } catch { return false; } };

Prevention

When it happens

Trigger: validateShare() runs on every parseStoredShare/formatStored* call and on createStoredUpload's return (origin: window.location.origin). A share token whose encoded origin is e.g. "https://host/croc" or "http://192.168.1.5" throws; likewise running the web UI itself over plain HTTP on a LAN IP, since window.location.origin then fails the check.

Common situations: Self-hosting the web client on an internal network over HTTP (not localhost) — every created share fails validation; a reverse proxy that adds a path prefix; hand-edited or truncated share tokens where the origin segment decodes to garbage.

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/43a5c0e1a42f7695. Report an issue: GitHub.