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:
+8
-5
@@ -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
|
* RELATIVE IS NORMAL, and requiring a leading slash was simply wrong. `scp file host:db-backups/fofos/`
|
||||||
* characters a shell would act on are refused rather than escaped. Refusing is checkable by reading it;
|
* lands under the remote account's home, which is what every ssh tool does and what anybody typing a
|
||||||
* escaping is checkable by being right about every shell.
|
* 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 {
|
export function checkRemotePath(path: string): string {
|
||||||
const trimmed = path.trim();
|
const trimmed = path.trim();
|
||||||
if (!trimmed) throw new Error('A remote directory is required.');
|
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)) {
|
if (/["'`$;&|<>()\\\s]/.test(trimmed)) {
|
||||||
throw new Error('The remote directory may not contain spaces, quotes or shell characters.');
|
throw new Error('The remote directory may not contain spaces, quotes or shell characters.');
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-3
@@ -175,7 +175,7 @@ export const BackupsRow = () => {
|
|||||||
{configured && settings.sshHost ? (
|
{configured && settings.sshHost ? (
|
||||||
<span className="text-muted-foreground">
|
<span className="text-muted-foreground">
|
||||||
{' '}
|
{' '}
|
||||||
→ {settings.sshHost}:{settings.sshPath}
|
→ {settings.sshHost}:{settings.sshPath?.startsWith('/') ? settings.sshPath : `~/${settings.sshPath}`}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
</p>
|
</p>
|
||||||
@@ -278,7 +278,7 @@ export const BackupsRow = () => {
|
|||||||
<Input
|
<Input
|
||||||
value={sshPath}
|
value={sshPath}
|
||||||
onChange={(event) => setSshPath(event.target.value)}
|
onChange={(event) => setSshPath(event.target.value)}
|
||||||
placeholder="/srv/backups/fofos"
|
placeholder="db-backups/fofos"
|
||||||
className="w-full font-mono text-xs"
|
className="w-full font-mono text-xs"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
@@ -296,7 +296,10 @@ export const BackupsRow = () => {
|
|||||||
</div>
|
</div>
|
||||||
{/* No password field, and there will not be one: this runs with nobody at a terminal, so it
|
{/* No password field, and there will not be one: this runs with nobody at a terminal, so it
|
||||||
authenticates with the owner's SSH key and stores no credential of its own. */}
|
authenticates with the owner's SSH key and stores no credential of its own. */}
|
||||||
<p className="text-xs text-muted-foreground/70">Authenticated with this machine’s SSH key.</p>
|
<p className="text-xs text-muted-foreground/70">
|
||||||
|
Authenticated with this machine’s SSH key. A path without a leading slash is relative to
|
||||||
|
the remote home.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user