fix: reject executable pi configuration

This commit is contained in:
2026-08-05 04:49:19 +02:00
parent 6b828288e3
commit 2703864572
9 changed files with 479 additions and 56 deletions
+87
View File
@@ -0,0 +1,87 @@
import {
closeSync, constants, fstatSync, lstatSync, openSync, readFileSync,
} from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
const MAX_AGENT_CONFIG_BYTES = 1024 * 1024;
export const PI_MANAGED_CONFIG_ERROR_CODE = "PI_MANAGED_CONFIG_INVALID";
export const PI_MANAGED_CONFIG_ERROR_MESSAGE = "Pi provider/model configuration is invalid";
export class PiManagedConfigError extends Error {
readonly code = PI_MANAGED_CONFIG_ERROR_CODE;
constructor() {
super(PI_MANAGED_CONFIG_ERROR_MESSAGE);
}
}
export function isPiManagedConfigError(error: unknown): boolean {
return Boolean(
error && typeof error === "object"
&& (error as { code?: unknown }).code === PI_MANAGED_CONFIG_ERROR_CODE,
);
}
export function parsePiConfigJson(raw: string): unknown {
try {
return JSON.parse(raw);
} catch {
throw new PiManagedConfigError();
}
}
/** Reject every Pi shell-backed configuration value, including unknown future nested fields. */
export function assertDeclarativePiConfig(value: unknown): void {
const pending: unknown[] = [value];
while (pending.length > 0) {
const current = pending.pop();
if (typeof current === "string") {
if (current.startsWith("!")) throw new PiManagedConfigError();
continue;
}
if (Array.isArray(current)) {
for (const nested of current) pending.push(nested);
continue;
}
if (current && typeof current === "object") {
for (const nested of Object.values(current as Record<string, unknown>)) pending.push(nested);
}
}
}
export function validateDeclarativePiConfig(raw: string): void {
assertDeclarativePiConfig(parsePiConfigJson(raw));
}
export function readConfiguredPiAgentFile(name: "auth.json"): string;
export function readConfiguredPiAgentFile(name: "models.json", optional: true): string | undefined;
export function readConfiguredPiAgentFile(
name: "auth.json" | "models.json",
optional = false,
): string | undefined {
const configuredAgentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
const path = join(configuredAgentDir, name);
let fd: number | undefined;
try {
const before = lstatSync(path);
if (!before.isFile() || before.isSymbolicLink() || before.size > MAX_AGENT_CONFIG_BYTES) {
throw new PiManagedConfigError();
}
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
const opened = fstatSync(fd);
if (!opened.isFile() || opened.size > MAX_AGENT_CONFIG_BYTES
|| before.dev !== opened.dev || before.ino !== opened.ino) {
throw new PiManagedConfigError();
}
return readFileSync(fd, "utf8");
} catch (error) {
if (optional && (error as NodeJS.ErrnoException)?.code === "ENOENT") return undefined;
throw new PiManagedConfigError();
} finally {
if (fd !== undefined) {
try { closeSync(fd); } catch { /* preserve the stable validation outcome */ }
}
}
}