fix: keep CLI prepare credentials private

This commit is contained in:
Shakker
2026-06-15 19:54:16 +01:00
committed by Shakker
parent d53e559ae7
commit 91f0767257
2 changed files with 79 additions and 7 deletions

View File

@@ -412,6 +412,70 @@ describe("shouldSkipLocalCliCredentialEpoch", () => {
}
});
it("does not expose auth profile credentials to non-Gemini CLI prepare hooks", async () => {
const { dir, sessionFile } = createSessionFile();
const agentDir = path.join(dir, "agents", "main", "agent");
const authProfileId = "test-cli:secret";
const prepareExecution = vi.fn(async () => undefined);
fs.mkdirSync(agentDir, { recursive: true });
saveAuthProfileStore(
{
version: 1,
profiles: {
[authProfileId]: {
type: "api_key",
provider: "test-cli",
key: "secret-key",
},
},
},
agentDir,
);
cliBackendsTesting.setDepsForTest({
resolvePluginSetupCliBackend: () => undefined,
resolveRuntimeCliBackends: () => [
{
id: "test-cli",
pluginId: "test-plugin",
bundleMcp: false,
prepareExecution,
config: {
command: "test-cli",
args: ["--prompt", "{prompt}"],
output: "json",
input: "arg",
sessionMode: "existing",
},
},
],
});
try {
await prepareCliRunContext({
sessionId: "session-test",
sessionKey: "agent:main:main",
sessionFile,
workspaceDir: dir,
prompt: "latest ask",
provider: "test-cli",
model: "test-model",
timeoutMs: 1_000,
runId: "run-test-non-gemini-credential-boundary",
authProfileId,
config: {},
});
expect(prepareExecution).toHaveBeenCalledWith(
expect.objectContaining({
authProfileId,
}),
);
expect(prepareExecution.mock.calls[0]?.[0]).not.toHaveProperty("authCredential");
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
it("prepares side questions without agent-turn context, tools, hooks, or reusable sessions", async () => {
const { dir, sessionFile } = createSessionFile();
appendTranscriptEntry(sessionFile, {

View File

@@ -463,20 +463,28 @@ export async function prepareCliRunContext(
: undefined,
warn: (message) => cliBackendLog.warn(message),
});
const preparedExecution = await backendResolved.prepareExecution?.({
const prepareExecutionContext = {
config: params.config,
workspaceDir,
agentDir,
provider: params.provider,
modelId,
authProfileId: effectiveAuthProfileId,
// Private bridge for bundled Gemini CLI. This is intentionally not part
// of the public Plugin SDK until a credential-forwarding contract exists.
authCredential,
executionMode,
} as Parameters<NonNullable<typeof backendResolved.prepareExecution>>[0] & {
authCredential?: AuthProfileCredential;
});
} as Parameters<NonNullable<typeof backendResolved.prepareExecution>>[0];
const preparedExecution = await backendResolved.prepareExecution?.(
(backendResolved.id === "google-gemini-cli"
? {
...prepareExecutionContext,
// Private bridge for bundled Gemini CLI. This is intentionally not
// part of the public Plugin SDK until a credential-forwarding
// contract exists.
authCredential,
}
: prepareExecutionContext) as typeof prepareExecutionContext & {
authCredential?: AuthProfileCredential;
},
);
const skipLocalCredentialEpoch = shouldSkipLocalCliCredentialEpoch({
authEpochMode: backendResolved.authEpochMode,
authProfileId: effectiveAuthProfileId,