Files
oh-my-pi/packages/coding-agent/test/config-cli-credentials.test.ts
T
Wolfgang Schoenberger 914afc0d6c fix(cli): redact credential settings in config list
omp config list printed every configured value, including auth.broker.token,
searxng.token, searxng.basicPassword and dev.autoqaPush.token, in both the
human and --json output. Nobody asked for those specific credentials; the
command dumps everything.

Credentials are marked with a top-level credential flag rather than ui.secret,
because four of them have no settings-panel entry and so have nowhere to put a
UI-level flag. isCredential is the single accessor both the CLI and the panel
consult, so the two spellings cannot produce different behaviour on different
surfaces.

Human output shows dots. JSON omits value and marks the entry redacted instead
of substituting a placeholder, which a consumer could not distinguish from a
real value and might write back.

config get <path> is deliberately unchanged: that is an explicit request for a
single value, and masking it would break a retrieval API with no way to read
your own token back.
2026-07-26 02:58:38 -07:00

66 lines
2.3 KiB
TypeScript

import { describe, expect, it } from "bun:test";
import { isCredential, SETTINGS_SCHEMA, type SettingPath } from "../src/config/settings-schema";
import { getSettingDef } from "../src/modes/components/settings-defs";
const paths = Object.keys(SETTINGS_SCHEMA) as SettingPath[];
describe("credential settings", () => {
it("marks every known credential, including those with no settings panel entry", () => {
for (const path of [
"auth.broker.token",
"searxng.token",
"searxng.basicPassword",
"dev.autoqaPush.token",
"hindsight.apiToken",
] as const) {
expect(isCredential(path)).toBe(true);
}
});
it("classifies UI-visible credentials through the same marker", () => {
// One field, not two: there is no separate UI-only masking flag that could
// drift away from this classification.
for (const path of ["mnemopi.embeddingApiKey", "mnemopi.llmApiKey"] as const) {
expect(isCredential(path)).toBe(true);
}
});
it("does not sweep ordinary settings into the credential set", () => {
// Token-budget settings read like credentials by name but are plain numbers.
for (const path of ["compaction.thresholdTokens", "display.showTokenUsage", "autoResume"] as const) {
expect(isCredential(path)).toBe(false);
}
});
it("only marks string settings as credentials", () => {
for (const path of paths) {
if (!isCredential(path)) continue;
expect(SETTINGS_SCHEMA[path].type).toBe("string");
}
});
});
describe("credential masking reaches every surface", () => {
it("masks a UI-visible credential in the settings panel", () => {
// The panel derives masking from the same classification the CLI uses, so
// a credential cannot render as plain text on one surface and dots on the
// other.
for (const path of ["hindsight.apiToken", "mnemopi.embeddingApiKey", "mnemopi.llmApiKey"] as const) {
const def = getSettingDef(path);
expect(def?.type).toBe("text");
expect(def && "secret" in def ? def.secret : undefined).toBe(true);
}
});
it("keeps credentials with no panel entry out of the panel entirely", () => {
for (const path of ["auth.broker.token", "searxng.token", "dev.autoqaPush.token"] as const) {
expect(getSettingDef(path)).toBeUndefined();
}
});
it("leaves ordinary text settings unmasked", () => {
const def = getSettingDef("shellPath");
if (def?.type === "text") expect(def.secret).toBe(false);
});
});