a remote path without a leading slash is relative to the remote home

Requiring an absolute one was simply wrong. `scp file host:db-backups/fofos/`
lands under the remote account's home — that is what every ssh tool does, and
what anybody typing a destination expects, since the home directory is the one
place they are certain to be able to write.

What is still refused is unchanged and is the part that matters: the characters
a shell would act on. This value IS interpreted remotely, since `ssh host
mkdir -p <path>` runs through a shell, so they are rejected rather than escaped
— refusing is checkable by reading the function, escaping is checkable only by
being right about every shell.

The summary shows a relative destination as ~/… because that is where it lands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 13:07:50 +01:00
co-authored by Claude Opus 5
parent 5fc710f072
commit c429dc3455
2 changed files with 14 additions and 8 deletions
+8 -5
View File
@@ -113,16 +113,19 @@ export function checkHost(host: string): string {
}
/**
* An absolute path on the far side.
* A directory on the far side, absolute or relative.
*
* This one IS interpreted by the remote shell — `ssh host mkdir -p /path` runs through it — so the
* characters a shell would act on are refused rather than escaped. Refusing is checkable by reading it;
* escaping is checkable by being right about every shell.
* RELATIVE IS NORMAL, and requiring a leading slash was simply wrong. `scp file host:db-backups/fofos/`
* lands under the remote account's home, which is what every ssh tool does and what anybody typing a
* destination expects — the home directory is the one place they are certain to be able to write.
*
* What is still refused: the characters a shell would act on. This value IS interpreted remotely —
* `ssh host mkdir -p <path>` runs through a shell — so they are rejected rather than escaped. Refusing
* is checkable by reading this function; escaping is checkable only by being right about every shell.
*/
export function checkRemotePath(path: string): string {
const trimmed = path.trim();
if (!trimmed) throw new Error('A remote directory is required.');
if (!trimmed.startsWith('/')) throw new Error('The remote directory must be an absolute path.');
if (/["'`$;&|<>()\\\s]/.test(trimmed)) {
throw new Error('The remote directory may not contain spaces, quotes or shell characters.');
}