fix(backend): reject compound provider credentials
This commit is contained in:
@@ -141,3 +141,25 @@ test("model-list spawn loads only the selected canonical provider credential", a
|
||||
rmSync(path.dirname(script), { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test.each(["amazon-bedrock", "azure-openai-responses", "cloudflare-workers-ai", "cloudflare-ai-gateway"])(
|
||||
"model listing rejects compound provider %s before spawning Pi", async (provider) => {
|
||||
const script = scriptWith([]);
|
||||
const secret = join(path.dirname(script), "model-key");
|
||||
writeFileSync(secret, "selected-secret", { mode: 0o600 });
|
||||
let spawns = 0;
|
||||
const lister = createPiModelLister(loadConfig({
|
||||
PI_PROVIDER: provider, THT_MODEL_API_KEY_FILE: secret,
|
||||
}), {
|
||||
spawnFn: () => { spawns += 1; throw new Error("must not spawn"); },
|
||||
});
|
||||
try {
|
||||
await expect(lister()).rejects.toThrow(
|
||||
"compound credential bundles are unsupported by THT_MODEL_API_KEY_FILE; dedicated provider configuration is required",
|
||||
);
|
||||
expect(spawns).toBe(0);
|
||||
} finally {
|
||||
rmSync(path.dirname(script), { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -262,6 +262,25 @@ test("local providers spawn without a model key and scrub ambient generic creden
|
||||
}
|
||||
});
|
||||
|
||||
test.each(["amazon-bedrock", "azure-openai-responses", "cloudflare-workers-ai", "cloudflare-ai-gateway"])(
|
||||
"session spawn rejects compound provider %s before spawning Pi", async (provider) => {
|
||||
const secret = path.resolve(__dirname, `.compound-key-${process.pid}-${provider}`);
|
||||
writeFileSync(secret, "provider-secret", { mode: 0o600 });
|
||||
let spawns = 0;
|
||||
const mgr = new PiProcessManager(loadConfig({ THT_MODEL_API_KEY_FILE: secret }), {
|
||||
spawnFn: () => { spawns += 1; throw new Error("must not spawn"); },
|
||||
});
|
||||
try {
|
||||
await expect(mgr.spawnFor("compound-provider", { provider })).rejects.toThrow(
|
||||
"compound credential bundles are unsupported by THT_MODEL_API_KEY_FILE; dedicated provider configuration is required",
|
||||
);
|
||||
expect(spawns).toBe(0);
|
||||
} finally {
|
||||
await import("node:fs/promises").then((fs) => fs.unlink(secret));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test.each(["missing", "permissive", "unreadable", "directory", "symlink", "unsupported"])(
|
||||
"hosted provider credential failure is sanitized: %s", async (kind) => {
|
||||
const target = path.resolve(__dirname, `.bad-model-key-${process.pid}-${kind}`);
|
||||
|
||||
@@ -74,3 +74,44 @@ test("Docker secrets require a secure root-owned /run/secrets parent and 0444 fi
|
||||
},
|
||||
})).toThrow("model provider credential is unavailable");
|
||||
});
|
||||
|
||||
test.each(["amazon-bedrock", "azure-openai-responses", "cloudflare-workers-ai", "cloudflare-ai-gateway"])(
|
||||
"compound provider %s fails closed before opening the generic secret", (provider) => {
|
||||
let opens = 0;
|
||||
expect(() => buildPiChildEnv({
|
||||
ambient: {}, provider, credentialFile: "/unused",
|
||||
fsOps: {
|
||||
lstat: () => { throw new Error("must not inspect file"); },
|
||||
open: () => { opens += 1; return 9; },
|
||||
fstat: () => { throw new Error("must not inspect file"); },
|
||||
read: () => "secret", close: () => undefined,
|
||||
},
|
||||
})).toThrow(
|
||||
"compound credential bundles are unsupported by THT_MODEL_API_KEY_FILE; dedicated provider configuration is required",
|
||||
);
|
||||
expect(opens).toBe(0);
|
||||
},
|
||||
);
|
||||
|
||||
test("single-key providers scrub ambient compound companions before injecting their key", () => {
|
||||
const stat = {
|
||||
dev: 7, ino: 1, uid: process.getuid?.() ?? 0, mode: 0o100600, nlink: 1, size: 6,
|
||||
isFile: () => true, isDirectory: () => false, isSymbolicLink: () => false,
|
||||
};
|
||||
const env = buildPiChildEnv({
|
||||
ambient: {
|
||||
AWS_ACCESS_KEY_ID: "ambient", AWS_SECRET_ACCESS_KEY: "ambient",
|
||||
CLOUDFLARE_ACCOUNT_ID: "ambient", CLOUDFLARE_GATEWAY_ID: "ambient",
|
||||
},
|
||||
provider: "openai", credentialFile: "/safe/key",
|
||||
fsOps: {
|
||||
lstat: () => stat as any, open: () => 9, fstat: () => stat as any,
|
||||
read: () => "secret", close: () => undefined,
|
||||
},
|
||||
});
|
||||
expect(env.OPENAI_API_KEY).toBe("secret");
|
||||
expect(env).not.toHaveProperty("AWS_ACCESS_KEY_ID");
|
||||
expect(env).not.toHaveProperty("AWS_SECRET_ACCESS_KEY");
|
||||
expect(env).not.toHaveProperty("CLOUDFLARE_ACCOUNT_ID");
|
||||
expect(env).not.toHaveProperty("CLOUDFLARE_GATEWAY_ID");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user