mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
73 lines
2.8 KiB
TypeScript
73 lines
2.8 KiB
TypeScript
import { readDockerDatabaseReceipt, type DockerRun } from "./docker-postgres.js";
|
|
import path from "node:path";
|
|
import { readFile, stat } from "node:fs/promises";
|
|
|
|
export async function assertRunnerDatabaseIsolation(
|
|
configPath: string,
|
|
temporaryRoot: string,
|
|
dockerReceiptPath?: string,
|
|
dockerRun?: DockerRun,
|
|
) {
|
|
const configText = await readFile(configPath, "utf8");
|
|
const config = JSON.parse(configText) as {
|
|
database?: {
|
|
mode?: string;
|
|
connectionString?: string;
|
|
embeddedPostgresDataDir?: string;
|
|
backup?: { dir?: string };
|
|
};
|
|
logging?: { logDir?: string };
|
|
storage?: {
|
|
provider?: string;
|
|
localDisk?: { baseDir?: string };
|
|
};
|
|
secrets?: {
|
|
provider?: string;
|
|
strictMode?: boolean;
|
|
localEncrypted?: { keyFilePath?: string };
|
|
};
|
|
};
|
|
const docker = dockerReceiptPath ? await readDockerDatabaseReceipt(dockerReceiptPath, temporaryRoot, dockerRun) : undefined;
|
|
if (docker ? config.database?.mode !== "postgres" || config.database.connectionString !== docker.connectionString : config.database?.mode !== "embedded-postgres" || !!config.database.connectionString) {
|
|
throw new Error("Runner E2E Paperclip instance did not use its explicitly owned database lane");
|
|
}
|
|
if (
|
|
config.storage?.provider !== "local_disk" ||
|
|
config.secrets?.provider !== "local_encrypted" ||
|
|
config.secrets.strictMode !== true
|
|
) {
|
|
throw new Error(
|
|
"Runner E2E instance did not use isolated local storage and strict encrypted secrets",
|
|
);
|
|
}
|
|
const isolatedPaths = {
|
|
...(docker ? {} : { database: config.database!.embeddedPostgresDataDir }),
|
|
backup: config.database!.backup?.dir,
|
|
logs: config.logging?.logDir,
|
|
storage: config.storage.localDisk?.baseDir,
|
|
secretsKey: config.secrets.localEncrypted?.keyFilePath,
|
|
};
|
|
for (const [label, configuredPath] of Object.entries(isolatedPaths)) {
|
|
if (!configuredPath) {
|
|
throw new Error(`Runner E2E config omitted its ${label} path`);
|
|
}
|
|
const resolved = path.resolve(configuredPath);
|
|
if (!resolved.startsWith(`${temporaryRoot}${path.sep}`)) {
|
|
throw new Error(
|
|
`Runner E2E ${label} path escaped the isolated root: ${resolved}`,
|
|
);
|
|
}
|
|
}
|
|
if (!docker) {
|
|
const databasePath = path.resolve(isolatedPaths.database!);
|
|
const databaseStat = await stat(databasePath);
|
|
if (!databaseStat.isDirectory()) throw new Error("Embedded database path is not a directory");
|
|
}
|
|
const keyStat = await stat(path.resolve(isolatedPaths.secretsKey!));
|
|
if (!keyStat.isFile())
|
|
throw new Error("Encrypted-secrets master key path is not a file");
|
|
}
|
|
|
|
/** Compatibility entry point remains embedded-only, never ambient external DB. */
|
|
export async function assertEmbeddedDatabaseIsolation(configPath: string, temporaryRoot: string) { return assertRunnerDatabaseIsolation(configPath, temporaryRoot); }
|