schollz/croc · error · Error
Remote path is not allowed: ${value}
Error message
Remote path is not allowed: ${value} What it means
normalizeFolder() rejects any normalized folder whose joined path contains the substring '.ssh'. This blocks a malicious transfer from overwriting the receiver's SSH keys/authorized_keys, a classic remote-code-execution vector when downloads land in or near the user's home directory.
Source
Thrown at web/src/protocol/metadata.ts:31
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}`;
return { folder, name, path };
}
export function normalizeOutgoingFileName(value: string) {
// Go's unicode.IsPrint accepts ASCII space but rejects the other UnicodeView on GitHub (pinned to e25f1bdc04)
Solutions
- Exclude .ssh from any folder-sync source on the sender before offering.
- As a receiver, refuse the transfer when this fires — the guard is intentional and must not be bypassed.
- If you truly need to transfer SSH configs, use a renamed folder and restore manually after the transfer.
Defensive patterns
Strategy: validation
Validate before calling
function mentionsSsh(normalizedFolder: string): boolean {
return normalizedFolder.includes(".ssh");
}
// after your own normalization, before accepting:
if (mentionsSsh(folder)) rejectOffer(".ssh destinations are blocked"); Try / catch
try {
const folder = normalizeFolder(value);
} catch (error) {
if (error instanceof Error && error.message.startsWith("Remote path is not allowed")) {
rejectOffer("path touches .ssh"); // intentional guard — never bypass
return;
}
throw error;
} Prevention
- Never bypass or special-case this guard to 'make a transfer work' — it blocks SSH key overwrite attacks.
- Exclude .ssh from sender-side folder selections before offering.
- Transfer SSH configs via a renamed container folder and restore manually.
When it happens
Trigger: An offer folder that normalizes to something containing '.ssh' anywhere — e.g. '.ssh', 'backup/.ssh', or 'x.ssh.y'. The check is substring-based on the joined normalized path, so it is intentionally broad.
Common situations: Hostile sender attempting SSH key overwrite; a user legitimately syncing a dotfiles repo that includes .ssh (must be excluded); security scanners asserting the guard fires.
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 must be relative: ${value}
- Remote filename must be a basename: ${nameValue}
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/a9e7d5aabb11a7bd.
Report an issue: GitHub.