refactor(runtime): prefer env-backed secrets at runtime

This commit is contained in:
2026-03-30 19:17:32 +02:00
parent 4f5d410b94
commit fed7aa5b61
13 changed files with 532 additions and 71 deletions
@@ -0,0 +1,153 @@
import { SystemRole } from "@capakraken/shared";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { parseAiError } from "../ai-client.js";
import { logger } from "../lib/logger.js";
import { settingsRouter } from "../router/settings.js";
import { createCallerFactory } from "../trpc.js";
vi.mock("../lib/logger.js", () => ({
logger: {
error: vi.fn(),
warn: vi.fn(),
info: vi.fn(),
debug: vi.fn(),
},
}));
const createCaller = createCallerFactory(settingsRouter);
function createAdminCaller(db: Record<string, unknown>) {
return createCaller({
session: {
user: { email: "admin@example.com", name: "Admin", image: null },
expires: "2099-01-01T00:00:00.000Z",
},
db: db as never,
dbUser: {
id: "admin_1",
systemRole: SystemRole.ADMIN,
permissionOverrides: null,
},
});
}
describe("runtime config hardening", () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
});
it("sanitizes AI fallback diagnostics", () => {
const error = parseAiError(
new Error(
"Provider failed at https://example.openai.azure.com/path?api-key=topsecret with Bearer sk-super-secret",
),
);
expect(error).toContain("<redacted-url>");
expect(error).toContain("<redacted-secret>");
expect(error).not.toContain("https://example.openai.azure.com");
expect(error).not.toContain("sk-super-secret");
expect(error).not.toContain("topsecret");
});
it("does not expose raw diagnostics from AI connection tests", async () => {
const findUnique = vi.fn().mockResolvedValue({
aiProvider: "openai",
azureOpenAiDeployment: "gpt-4o-mini",
azureOpenAiApiKey: "sk-live-secret",
});
vi.stubGlobal("fetch", vi.fn().mockResolvedValue({
ok: false,
status: 500,
text: vi.fn().mockResolvedValue(
JSON.stringify({
error: {
message: "upstream failure at https://api.openai.com/v1/chat/completions?api-key=hidden",
},
}),
),
}));
const caller = createAdminCaller({
systemSettings: {
findUnique,
},
});
const result = await caller.testAiConnection();
expect(result.ok).toBe(false);
expect(result).not.toHaveProperty("raw");
expect(result.error).toContain("<redacted-url>");
expect(result.error).not.toContain("https://api.openai.com");
expect(result.error).not.toContain("hidden");
expect(logger.warn).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
diagnostic: expect.stringContaining("<redacted-url>"),
}),
"AI connection test failed",
);
});
it("reports secret presence flags when secrets come from environment overrides", async () => {
vi.stubEnv("OPENAI_API_KEY", "env-openai-key");
vi.stubEnv("SMTP_PASSWORD", "env-smtp-password");
vi.stubEnv("GEMINI_API_KEY", "env-gemini-key");
const caller = createAdminCaller({
systemSettings: {
findUnique: vi.fn().mockResolvedValue({
aiProvider: "openai",
azureOpenAiApiKey: null,
smtpPassword: null,
geminiApiKey: null,
}),
},
});
const result = await caller.getSystemSettings();
expect(result.hasApiKey).toBe(true);
expect(result.hasSmtpPassword).toBe(true);
expect(result.hasGeminiApiKey).toBe(true);
});
it("prefers environment API keys during AI connection tests", async () => {
vi.stubEnv("OPENAI_API_KEY", "env-openai-key");
const fetch = vi.fn().mockResolvedValue({
ok: true,
text: vi.fn().mockResolvedValue("ok"),
});
vi.stubGlobal("fetch", fetch);
const caller = createAdminCaller({
systemSettings: {
findUnique: vi.fn().mockResolvedValue({
aiProvider: "openai",
azureOpenAiDeployment: "gpt-4o-mini",
azureOpenAiApiKey: null,
}),
},
});
const result = await caller.testAiConnection();
expect(result).toEqual({ ok: true });
expect(fetch).toHaveBeenCalledWith(
"https://api.openai.com/v1/chat/completions",
expect.objectContaining({
headers: expect.objectContaining({
Authorization: "Bearer env-openai-key",
}),
}),
);
});
});