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:
@@ -1,10 +1,12 @@
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import type { PermissionsService } from "@gotgenes/pi-permission-system";
|
||||
import { resolveRemoteCwd } from "./src/remote-cwd.ts";
|
||||
|
||||
export interface SshPermissionConnection {
|
||||
remote: string;
|
||||
port?: number;
|
||||
remoteCwd: string;
|
||||
remoteHome?: string;
|
||||
}
|
||||
|
||||
export interface SshPermissionIntegrationDependencies {
|
||||
@@ -17,8 +19,8 @@ export interface SshPermissionIntegrationDependencies {
|
||||
type PermissionIntegrationApi = Pick<ExtensionAPI, "events" | "on">;
|
||||
type ToolInput = Record<string, unknown>;
|
||||
|
||||
const REMOTE_FILE_TOOLS = ["ssh_read", "ssh_write", "ssh_edit", "ssh_find", "ssh_grep"] as const;
|
||||
const REMOTE_TOOLS = ["ssh_connect", ...REMOTE_FILE_TOOLS, "ssh_bash"] as const;
|
||||
const REMOTE_PATH_TOOLS = ["ssh_cd", "ssh_read", "ssh_write", "ssh_edit", "ssh_find", "ssh_grep"] as const;
|
||||
const REMOTE_TOOLS = ["ssh_connect", ...REMOTE_PATH_TOOLS, "ssh_bash"] as const;
|
||||
|
||||
function inline(value: string, limit = 240): string {
|
||||
const normalized = value.replace(/\s+/g, " ").trim();
|
||||
@@ -40,6 +42,23 @@ function formatTarget(connection: SshPermissionConnection | null): string {
|
||||
return `SSH target '${inline(connection.remote)}${port}' in remote cwd '${inline(connection.remoteCwd)}'`;
|
||||
}
|
||||
|
||||
function remotePathDetail(
|
||||
requested: string,
|
||||
connection: SshPermissionConnection | null,
|
||||
label = "remote path",
|
||||
): string {
|
||||
let resolved: string | undefined;
|
||||
if (connection?.remoteHome) {
|
||||
try {
|
||||
resolved = resolveRemoteCwd(requested, connection.remoteCwd, connection.remoteHome);
|
||||
} catch {
|
||||
// Execution performs authoritative validation; preserve the raw request.
|
||||
}
|
||||
}
|
||||
if (!resolved || resolved === requested) return `${label} '${inline(requested)}'`;
|
||||
return `${label} '${inline(resolved)}' (requested '${inline(requested)}')`;
|
||||
}
|
||||
|
||||
export function formatSshPermissionInput(
|
||||
toolName: string,
|
||||
input: ToolInput,
|
||||
@@ -55,8 +74,12 @@ export function formatSshPermissionInput(
|
||||
return `requested imported SSH host '${hostId}'${remotePath ? ` in remote cwd '${inline(remotePath)}'` : ""}; establish a persistent SSH2 connection`;
|
||||
}
|
||||
|
||||
if (toolName === "ssh_cd") {
|
||||
return `${target}; change the active ${path ? remotePathDetail(path, connection, "remote cwd to") : "remote cwd to '<unspecified>'"}`;
|
||||
}
|
||||
|
||||
if (toolName === "ssh_read") {
|
||||
const details = path ? [`remote path '${inline(path)}'`] : ["an unspecified remote path"];
|
||||
const details = path ? [remotePathDetail(path, connection)] : ["an unspecified remote path"];
|
||||
if (typeof input.offset === "number") details.push(`offset ${input.offset}`);
|
||||
if (typeof input.limit === "number") details.push(`limit ${input.limit}`);
|
||||
return `${target}; read ${details.join(", ")}`;
|
||||
@@ -64,20 +87,20 @@ export function formatSshPermissionInput(
|
||||
|
||||
if (toolName === "ssh_write") {
|
||||
const content = stringField(input, "content") ?? "";
|
||||
return `${target}; write remote path '${inline(path ?? "<unspecified>")}' (${countLines(content)} lines, ${content.length} characters)`;
|
||||
return `${target}; write ${path ? remotePathDetail(path, connection) : "remote path '<unspecified>'"} (${countLines(content)} lines, ${content.length} characters)`;
|
||||
}
|
||||
|
||||
if (toolName === "ssh_edit") {
|
||||
const oldText = stringField(input, "oldText") ?? "";
|
||||
const newText = stringField(input, "newText") ?? "";
|
||||
return `${target}; edit remote path '${inline(path ?? "<unspecified>")}' (replace ${countLines(oldText)} lines with ${countLines(newText)} lines)`;
|
||||
return `${target}; edit ${path ? remotePathDetail(path, connection) : "remote path '<unspecified>'"} (replace ${countLines(oldText)} lines with ${countLines(newText)} lines)`;
|
||||
}
|
||||
|
||||
if (toolName === "ssh_find" || toolName === "ssh_grep") {
|
||||
const pattern = inline(stringField(input, "pattern") ?? "<unspecified>");
|
||||
const operation = toolName === "ssh_find" ? "find remote files" : "search remote file contents";
|
||||
const details = [
|
||||
`under '${inline(path ?? ".")}'`,
|
||||
remotePathDetail(path ?? ".", connection, "under"),
|
||||
`for '${pattern}'`,
|
||||
`limit ${typeof input.limit === "number" ? input.limit : 50}`,
|
||||
];
|
||||
@@ -140,7 +163,7 @@ export function installSshPermissionIntegration(
|
||||
}),
|
||||
);
|
||||
}
|
||||
for (const toolName of REMOTE_FILE_TOOLS) {
|
||||
for (const toolName of REMOTE_PATH_TOOLS) {
|
||||
pending.push(service.registerToolAccessExtractor(toolName, () => undefined));
|
||||
}
|
||||
disposers = pending;
|
||||
|
||||
Reference in New Issue
Block a user