mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-08-28 08:35:57 +00:00
fix(pi-ssh): harden remote execution and search
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { parseRemotePathProbe, probeRemotePath } from "../src/remote-probe.ts";
|
||||
import type { RemoteExecOptions, RemoteTransport } from "../src/ssh2-transport.ts";
|
||||
|
||||
class ProbeTransport implements RemoteTransport {
|
||||
command = "";
|
||||
cwd = "";
|
||||
connect(): Promise<void> { return Promise.resolve(); }
|
||||
dispose(): Promise<void> { return Promise.resolve(); }
|
||||
exec(command: string, cwd: string, options: RemoteExecOptions): Promise<{ exitCode: number | null }> {
|
||||
this.command = command;
|
||||
this.cwd = cwd;
|
||||
const start = command.match(/'(__PI_SSH_PROBE_[a-f0-9]+_START__)'/)?.[1];
|
||||
const end = command.match(/'(__PI_SSH_PROBE_[a-f0-9]+_END__)'/)?.[1];
|
||||
if (!start || !end) throw new Error("probe markers missing");
|
||||
options.onData(Buffer.from(`login banner\n${start}/srv/project${end}\nlogout banner\n`));
|
||||
return Promise.resolve({ exitCode: 0 });
|
||||
}
|
||||
capture(): Promise<{ exitCode: number | null; output: Buffer }> { throw new Error("not used"); }
|
||||
readFile(): Promise<Buffer> { throw new Error("not used"); }
|
||||
ensureReadable(): Promise<void> { throw new Error("not used"); }
|
||||
ensureReadableWritable(): Promise<void> { throw new Error("not used"); }
|
||||
detectImageMimeType(): Promise<string | null> { throw new Error("not used"); }
|
||||
mkdir(): Promise<void> { throw new Error("not used"); }
|
||||
writeFile(): Promise<void> { throw new Error("not used"); }
|
||||
}
|
||||
|
||||
test("extracts one framed absolute path while ignoring shell startup output", async () => {
|
||||
const transport = new ProbeTransport();
|
||||
assert.equal(await probeRemotePath(transport, "cwd", "/srv"), "/srv/project");
|
||||
assert.equal(transport.cwd, "/srv");
|
||||
assert.match(transport.command, /pwd -P/);
|
||||
});
|
||||
|
||||
test("rejects unframed, relative, multiline, and oversized path probes", () => {
|
||||
assert.throws(() => parseRemotePathProbe(Buffer.from("/srv"), "START", "END", "cwd"), /invalid framed/);
|
||||
assert.throws(() => parseRemotePathProbe(Buffer.from("STARTrelativeEND"), "START", "END", "cwd"), /absolute POSIX path/);
|
||||
assert.throws(() => parseRemotePathProbe(Buffer.from("START/srv\notherEND"), "START", "END", "cwd"), /absolute POSIX path/);
|
||||
assert.throws(
|
||||
() => parseRemotePathProbe(Buffer.alloc(64 * 1024 + 1), "START", "END", "cwd"),
|
||||
/exceeded 65536 bytes/,
|
||||
);
|
||||
});
|
||||
|
||||
test("forwards cancellation to the probe exec call", async () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
const transport = new ProbeTransport();
|
||||
const original = transport.exec.bind(transport);
|
||||
transport.exec = (command, cwd, options) => {
|
||||
assert.equal(options.signal, controller.signal);
|
||||
if (options.signal?.aborted) return Promise.reject(new Error("SSH command aborted"));
|
||||
return original(command, cwd, options);
|
||||
};
|
||||
await assert.rejects(probeRemotePath(transport, "home", ".", controller.signal), /SSH command aborted/);
|
||||
});
|
||||
Reference in New Issue
Block a user