fix(models): redact synthetic auth credentials from models status --json (#104734)

The provider auth overview passed the caller's synthetic-auth object
through to the JSON payload. Status hands it the full runtime shape,
which also carries the raw credential, so structural typing let live
API keys/tokens ship in a diagnostic surface (#104713). Re-project to
the declared value/source fields at the overview boundary.

Fixes #104713
This commit is contained in:
RickLin
2026-07-12 13:20:28 +08:00
committed by GitHub
parent 3d9b912b88
commit 4cfe0077ff
3 changed files with 43 additions and 1 deletions

View File

@@ -99,6 +99,31 @@ describe("resolveProviderAuthOverview", () => {
vi.mocked(resolveEnvApiKey).mockClear();
});
it("projects synthetic auth to value/source and drops runtime credential fields", () => {
// #104713: status callers pass their richer runtime object (credential,
// mode, expiresAt); the overview must not let those reach JSON output.
const runtimeSyntheticAuth = {
value: "plugin-owned",
source: "xAI plugin config",
credential: "xai-raw-credential-material",
mode: "api-key",
expiresAt: Date.now() + 60_000,
};
const overview = resolveProviderAuthOverview({
provider: "xai",
cfg: {},
store: { version: 1, profiles: {} } as never,
modelsPath: "/tmp/models.json",
syntheticAuth: runtimeSyntheticAuth,
});
expect(overview.syntheticAuth).toStrictEqual({
value: "plugin-owned",
source: "xAI plugin config",
});
expect(JSON.stringify(overview)).not.toContain("xai-raw-credential-material");
});
it("labels token profiles that only have tokenRef", () => {
const overview = resolveProviderAuthOverview({
provider: "github-copilot",

View File

@@ -218,6 +218,16 @@ export function resolveProviderAuthOverview(params: {
},
}
: {}),
...(params.syntheticAuth ? { syntheticAuth: params.syntheticAuth } : {}),
// Re-project instead of passing the caller's object through: status callers
// hand richer runtime shapes that also carry the raw synthetic credential,
// and structural typing would let it leak into `--json` output verbatim.
...(params.syntheticAuth
? {
syntheticAuth: {
value: params.syntheticAuth.value,
source: params.syntheticAuth.source,
},
}
: {}),
};
}

View File

@@ -1221,6 +1221,13 @@ describe("modelsStatusCommand auth overview", () => {
value: "plugin-owned",
source: "codex-app-server",
});
// #104713: the summary must ship only the projected fields; the runtime
// synthetic-auth object also carries the raw credential and must never
// reach the JSON payload.
expect(
Object.keys(requireRecord(codexProvider.syntheticAuth, "codex synthetic auth")),
).toStrictEqual(["value", "source"]);
expect(JSON.stringify(payload)).not.toContain("codex-runtime-token");
expectRecordFields(requireRecord(codexProvider.effective, "codex effective auth"), {
kind: "synthetic",
detail: "codex-app-server",