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
- Serve the web client over HTTPS (or on localhost/127.0.0.1) so window.location.origin passes
- Strip any path/query from the origin embedded in tokens — only scheme://host[:port] is allowed
- 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
- Serve the croc web client over HTTPS (or localhost) so window.location.origin always validates
- Never embed paths or credentials in share origins; the origin is scheme://host[:port] only
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
- Invalid stored-transfer URL
- Stored transfers can allow at most ${settings.maxDownloads}
- Stored transfers can expire after at most ${settings.maxExpi
- Code must be at least 6 characters
- Custom codes must use printable ASCII characters
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/43a5c0e1a42f7695.
Report an issue: GitHub.