Keep owned Docker database credentials in server-only environment

This commit is contained in:
Dotta committed 2026-10-27 02:11:39 -05:00
1 parent 1454f54399
commit 96058c7e2f
6 files changed
+51 -9

No files matched your search

+38 -2
View File
@@ -9,7 +9,9 @@ import { assertRunnerDatabaseIsolation, assertEmbeddedDatabaseIsolation } from "
import { campaignDatabaseEvidenceEligible, type CampaignAttempt } from "./chat-campaign.js";
import { assertChatCatalogFingerprintLane, fingerprintChatDependencyClosure } from "./chat-catalog-fingerprint.js";
import { validateRetainedRunnerResult } from "./result-validation.js";
import { buildPaperclipServerEnvironment } from "./harness-env.js";
import { assertIsolatedServerEnvironment, buildPaperclipServerEnvironment } from "./harness-env.js";
import { findSecretLeakInDirectory } from "./redaction.js";
import { createSanitizedCodexEnvironment } from "../../packages/paperclip-runner/src/drivers/codex/app-server-transport.js";
const roots: string[] = [];
beforeEach(() => { vi.stubEnv("DOCKER_HOST", ""); vi.stubEnv("DOCKER_CONTEXT", ""); });
afterEach(async () => { vi.unstubAllEnvs(); for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true }); });
@@ -71,7 +73,10 @@ it("binds exact image/engine/resources, strips admission data and removes only o
expect(supervisor.source[DOCKER_DATABASE_ENV]).toBeUndefined(); expect(supervisor.source[DATABASE_LANE_ENV]).toBeUndefined();
const configPath = path.join(temp, "paperclip-home", "instances", "fixture", "config.json");
expect(await prepareRunnerE2EServerConfig({ temporaryRoot: temp, configPath, serverPort: 3100, dockerReceiptPath: owned.filename, dockerRun: run })).toBeNull();
const config = JSON.parse(await readFile(configPath, "utf8")); expect(config.database.mode).toBe("postgres"); expect(config.database.connectionString).toBe(owned.receipt.connectionString);
const config = JSON.parse(await readFile(configPath, "utf8")); expect(config.database.mode).toBe("postgres"); expect(config.database.connectionString).toBeUndefined();
expect(JSON.stringify(config)).not.toContain(secrets[0]);
expect(supervisor.serverOverrides.DATABASE_URL).toBe(owned.receipt.connectionString);
expect(supervisor.source.DATABASE_URL).toBeUndefined();
const key = config.secrets.localEncrypted.keyFilePath; await mkdir(path.dirname(key), { recursive: true }); await writeFile(key, "offline-key", { mode: 0o600 });
await expect(assertRunnerDatabaseIsolation(configPath, temp, owned.filename, run)).resolves.toBeUndefined();
const changed = structuredClone(config); changed.database.connectionString = "postgresql://foreign"; await writeFile(configPath, JSON.stringify(changed), { mode: 0o600 });
@@ -183,3 +188,34 @@ it("allows only exact Docker Desktop host translation with an owned canonical pr
await rm(password); await writeFile(password, contents, { mode: 0o600 });
await owned.close(); expect(state.container).toBeNull(); expect(state.volume).toBeNull();
});
it("keeps owned DB credentials server-only without any persisted scan exemption", async () => {
const temp = await root(), { run } = daemon(), secrets: string[] = [];
const owned = await startOwnedDockerDatabase({ temporaryRoot: temp, privateSecrets: secrets, run });
const admission = await dockerDatabaseSupervisorEnvironment({ temporaryRoot: temp, env: { [DATABASE_LANE_ENV]: "owned-docker-postgres", [DOCKER_DATABASE_ENV]: owned.filename, DATABASE_URL: "ambient-unrelated", DATABASE_MIGRATION_URL: "ambient-migration" }, run });
const home = path.join(temp, "paperclip-home"), configPath = path.join(home, "instances", "custody", "config.json");
await prepareRunnerE2EServerConfig({ temporaryRoot: temp, configPath, serverPort: 3100, dockerReceiptPath: owned.filename, dockerRun: run });
const expected = { temporaryRoot: temp, paperclipHome: home, configPath, ownedDatabaseUrl: admission.serverOverrides.DATABASE_URL };
const env = buildPaperclipServerEnvironment(admission.source, { ...admission.serverOverrides, PAPERCLIP_HOME: home, PAPERCLIP_CONFIG: configPath, XDG_CACHE_HOME: path.join(temp, "xdg-cache"), PAPERCLIP_AGENT_JWT_SECRET: "jwt", PAPERCLIP_DECISION_SIGNING_SECRET: "decision", PAPERCLIP_TOOL_ACTION_SIGNING_SECRET: "tool", BETTER_AUTH_SECRET: "auth" });
expect(env.DATABASE_URL).toBe(owned.receipt.connectionString);
expect(env.DATABASE_MIGRATION_URL).toBeUndefined();
expect(() => assertIsolatedServerEnvironment(env, expected)).not.toThrow();
expect(() => assertIsolatedServerEnvironment({ ...env, DATABASE_URL: "ambient-unrelated" }, expected)).toThrow();
expect(() => assertIsolatedServerEnvironment(env, { ...expected, ownedDatabaseUrl: undefined })).toThrow();
expect(createSanitizedCodexEnvironment(env).DATABASE_URL).toBeUndefined();
expect(createSanitizedCodexEnvironment(env).DATABASE_MIGRATION_URL).toBeUndefined();
expect(await findSecretLeakInDirectory(home, secrets, { includeShapes: false })).toBeNull();
const config = JSON.parse(await readFile(configPath, "utf8"));
config.database.connectionString = owned.receipt.connectionString;
await writeFile(configPath, JSON.stringify(config), { mode: 0o600 });
expect(await findSecretLeakInDirectory(home, secrets, { includeShapes: false })).not.toBeNull();
await expect(prepareRunnerE2EServerConfig({ temporaryRoot: temp, configPath, serverPort: 3100, dockerReceiptPath: owned.filename, dockerRun: run })).rejects.toThrow("differs");
delete config.database.connectionString;
config.unrelatedField = secrets[0];
await writeFile(configPath, JSON.stringify(config), { mode: 0o600 });
expect(await findSecretLeakInDirectory(home, secrets, { includeShapes: false })).not.toBeNull();
config.unrelatedField = "provider-secret-canary";
await writeFile(configPath, JSON.stringify(config), { mode: 0o600 });
expect(await findSecretLeakInDirectory(home, [...secrets, "provider-secret-canary"], { includeShapes: false })).not.toBeNull();
await owned.close();
});
+3 -3
View File
@@ -234,11 +234,11 @@ export async function startOwnedDockerDatabase(input: { temporaryRoot: string; p
export async function dockerDatabaseSupervisorEnvironment(input: { temporaryRoot: string; env: NodeJS.ProcessEnv; run?: DockerRun }) {
const lane = selectedDatabaseLane(input.env), source = { ...input.env }, filename = source[DOCKER_DATABASE_ENV];
delete source[DOCKER_DATABASE_ENV]; delete source[DATABASE_LANE_ENV];
if (lane === "embedded-postgres") { if (filename) throw new Error("Undeclared Docker database receipt"); return { source, filename: undefined }; }
if (lane === "embedded-postgres") { if (filename) throw new Error("Undeclared Docker database receipt"); return { source, filename: undefined, serverOverrides: {} as NodeJS.ProcessEnv }; }
if (!filename) throw new Error("Selected Docker database lane is missing its owned receipt");
await readDockerDatabaseReceipt(filename, input.temporaryRoot, input.run);
const receipt = await readDockerDatabaseReceipt(filename, input.temporaryRoot, input.run);
for (const key of ["DOCKER_CONFIG", "DOCKER_HOST", "DOCKER_CONTEXT"]) delete source[key];
return { source, filename };
return { source, filename, serverOverrides: { DATABASE_URL: receipt.connectionString } as NodeJS.ProcessEnv };
}
export function databaseFingerprintIdentity(env = process.env) {
+4 -1
View File
@@ -235,6 +235,8 @@ export function assertIsolatedServerEnvironment(
temporaryRoot: string;
paperclipHome: string;
configPath: string;
/** Supplied only after exact owned Docker receipt validation by the supervisor. */
ownedDatabaseUrl?: string;
},
) {
const home = env.PAPERCLIP_HOME;
@@ -263,11 +265,12 @@ export function assertIsolatedServerEnvironment(
...AMBIENT_PAPERCLIP_CREDENTIAL_KEYS,
...AMBIENT_EXTERNAL_STATE_KEYS,
]) {
if (env[key])
if (env[key] && !(key === "DATABASE_URL" && expected.ownedDatabaseUrl && env[key] === expected.ownedDatabaseUrl))
throw new Error(
`Paperclip server environment unexpectedly contains ${key}`,
);
}
if (expected.ownedDatabaseUrl && env.DATABASE_URL !== expected.ownedDatabaseUrl) throw new Error("Paperclip server database URL differs from the validated owned receipt");
for (const key of GENERATED_SERVER_SECRET_KEYS) {
if (!env[key])
throw new Error(`Paperclip server environment is missing ${key}`);
+1 -1
View File
@@ -28,7 +28,7 @@ export async function assertRunnerDatabaseIsolation(
};
};
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) {
if (docker ? config.database?.mode !== "postgres" || !!config.database.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 (
+3 -2
View File
@@ -23,7 +23,7 @@ export async function prepareRunnerE2EServerConfig(input: {
const docker = input.dockerReceiptPath ? await readDockerDatabaseReceipt(input.dockerReceiptPath, input.temporaryRoot, input.dockerRun) : undefined;
try {
const existing = JSON.parse(await readFile(input.configPath, "utf8"));
if (docker ? existing.database?.mode !== "postgres" || existing.database.connectionString !== docker.connectionString : existing.database?.mode !== "embedded-postgres" || existing.database.connectionString) throw new Error("Existing runner database config differs from its explicit owned lane");
if (docker ? existing.database?.mode !== "postgres" || !!existing.database.connectionString : existing.database?.mode !== "embedded-postgres" || existing.database.connectionString) throw new Error("Existing runner database config differs from its explicit owned lane");
return null;
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
@@ -37,9 +37,10 @@ export async function prepareRunnerE2EServerConfig(input: {
updatedAt: new Date().toISOString(),
source: "onboard",
},
// The owned Docker DSN belongs only to the validated server child env.
// Keep normal onboarding config private but entirely free of DB secrets.
database: {
mode: docker ? "postgres" : "embedded-postgres",
...(docker ? { connectionString: docker.connectionString } : {}),
embeddedPostgresDataDir: path.join(instanceRoot, "db"),
embeddedPostgresPort: databaseReservation?.port ?? 54329,
backup: {
+2
View File
@@ -108,11 +108,13 @@ const serverEnvironment = buildPaperclipServerEnvironment(installedRelease
// to Playwright in this harness, so never create a developer desktop tab.
PAPERCLIP_OPEN_ON_LISTEN: "false",
...vaultEnvironment,
...dockerEnvironment.serverOverrides,
});
assertIsolatedServerEnvironment(serverEnvironment, {
temporaryRoot,
paperclipHome,
configPath,
ownedDatabaseUrl: dockerEnvironment.serverOverrides.DATABASE_URL,
});
const definedServerEnvironment = Object.fromEntries(
Object.entries(serverEnvironment).filter(