schollz/croc · error · Error
Remote path must be relative: ${value}
Error message
Remote path must be relative: ${value} What it means
normalizeFolder() requires every remote folder to be a relative path: the regex /^(?:[a-zA-Z]:|\/)/ rejects POSIX absolute paths (leading '/') and Windows drive letters ('C:', 'D:\...'). Destination folders are always interpreted inside the receiver-chosen download root, so absolute destinations are meaningless and dangerous.
Source
Thrown at web/src/protocol/metadata.ts:26
function cleanSegments(value: string) {
const replaced = value.replaceAll("\\", "/");
if (replaced.includes("\0")) throw new Error("A remote path contains a null byte");
const segments: string[] = [];
for (const segment of replaced.split("/")) {
if (segment === "" || segment === ".") continue;
if (segment === "..") throw new Error(`Remote path escapes the destination: ${value}`);
if ([...segment].some((character) => !/\P{C}/u.test(character))) {
throw new Error(`Remote path contains a non-printable character: ${value}`);
}
segments.push(segment);
}
return segments;
}
export function normalizeFolder(value = ".") {
if (/^(?:[a-zA-Z]:|\/)/.test(value)) {
throw new Error(`Remote path must be relative: ${value}`);
}
const segments = cleanSegments(value);
const normalized = segments.join("/") || ".";
if (normalized.includes(".ssh")) {
throw new Error(`Remote path is not allowed: ${value}`);
}
return normalized;
}
export function normalizeFilePath(folderValue: string, nameValue: string) {
const folder = normalizeFolder(folderValue);
const nameSegments = cleanSegments(nameValue);
if (nameSegments.length !== 1 || nameSegments[0] !== nameValue.replaceAll("\\", "/")) {
throw new Error(`Remote filename must be a basename: ${nameValue}`);
}
const name = nameSegments[0];
if (!name) throw new Error("Remote filename is empty");
const path = folder === "." ? name : `${folder}/${name}`;View on GitHub (pinned to e25f1bdc04)
Solutions
- Pass only relative folder strings ('sub/dir' or '.') into normalizeFolder; keep the absolute download root as a separate config value applied at write time.
- Strip or reject leading slashes and drive letters on the sender before building the offer.
- Refuse transfers whose offers carry absolute destinations — treat them as malformed or hostile metadata.
Example fix
// before
const folder = normalizeFolder(config.downloadDir); // '/home/me/downloads'
// after
const folder = normalizeFolder("."); // offer-relative folder; apply downloadDir at save time Defensive patterns
Strategy: validation
Validate before calling
function isRelativeRemotePath(value: string): boolean {
return !/^(?:[a-zA-Z]:|\/)/.test(value);
}
if (!isRelativeRemotePath(folderValue)) throw new TypeError("absolute destination not allowed"); Try / catch
try {
const { folder } = normalizeFolder(candidate);
} catch (error) {
if (error instanceof Error && error.message.startsWith("Remote path must be relative")) {
// strip the root and retry with the relative remainder, or reject the offer
rejectOffer(`absolute folder not allowed: ${candidate}`);
return;
}
throw error;
} Prevention
- Keep the absolute download root in receiver config; never pass it through normalizeFolder.
- Send only relative folder strings in offers ('sub/dir', '.').
- Reject offers with drive-letter or leading-slash destinations.
When it happens
Trigger: validateSenderInfo() receives an offer with fr='/tmp/evil' or fr='C:\Users\x' (backslashes are checked after replacement too — 'C:/' still matches the drive-letter branch), or your own code calls normalizeFolder with an absolute path.
Common situations: A hostile peer trying to pin an absolute destination; a sender on Windows passing drive-qualified folders through unnormalized; receiver-side code that forwards user config (download dir) into normalizeFolder instead of using it only as a root.
Related errors
- A remote path contains a null byte
- Remote path escapes the destination: ${value}
- Remote path contains a non-printable character: ${value}
- Remote path is not allowed: ${value}
- Remote filename must be a basename: ${nameValue}
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/c9c54f532bef651c.
Report an issue: GitHub.