Files
rea/tests/acceptance/setup/geminiWorkspaceTrust.test.ts
T
morlutoandhaoran20100322 aade9bbcb7 fix(setup): evaluate Gemini workspace trust before settings content
Treat only ENOENT as absence. Preserve unreadable metadata errors and ignore untrusted workspace content before parsing it.

Refs: #3206, #3319

Co-authored-by: haoran20100322 <haoran20100322@outlook.com>
2026-10-14 17:49:19 +08:00

221 lines
7.8 KiB
TypeScript

import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { expect } from "vitest";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest, type TestCli } from "../../support/cli/cliFixture.js";
const setupArguments = ["setup", "--client", "gemini_cli", "--yes", "--json"];
const doctorArguments = ["doctor", "--client", "gemini_cli", "--json"];
const geminiWorkspace = async (cli: TestCli) => {
const home = await createTestTempDirectory("rea-gemini-workspace-trust-");
const project = join(home, "project");
const workspace = join(project, ".gemini/settings.json");
const user = join(home, ".gemini/settings.json");
await mkdir(dirname(workspace), { recursive: true });
const environment: NodeJS.ProcessEnv = {
HOME: home,
USERPROFILE: home,
GEMINI_CLI_HOME: home,
GEMINI_CLI_SYSTEM_SETTINGS_PATH: join(home, "system.json"),
GEMINI_CLI_SYSTEM_DEFAULTS_PATH: join(home, "defaults.json"),
GEMINI_CLI_TRUSTED_FOLDERS_PATH: join(home, "trust.json"),
};
const initial = await cli.run({
arguments: setupArguments,
cwd: home,
environment,
});
expect(initial.exitCode, initial.stdout).toBe(0);
const originalUser = await readFile(user);
const run = (arguments_: readonly string[]) =>
cli.run({ arguments: arguments_, cwd: project, environment });
return { workspace, user, originalUser, environment, run };
};
cliTest(
"Gemini does not require workspace trust until workspace settings exist",
async ({ cli }) => {
const fixture = await geminiWorkspace(cli);
const trust = join(dirname(fixture.user), "trustedFolders.json");
fixture.environment.GEMINI_CLI_TRUSTED_FOLDERS_PATH = trust;
for (const text of ["{}", "{ not json"]) {
await writeFile(trust, text);
const setup = await fixture.run(setupArguments);
expect(setup.exitCode, setup.stdout).toBe(0);
expect(setup.json).toMatchObject({ status: "ready", appliedActions: [] });
const doctor = await fixture.run(doctorArguments);
expect(doctor.exitCode, doctor.stdout).toBe(0);
expect(doctor.json).toMatchObject({
healthy: true,
identity: {
registrations: expect.arrayContaining([
expect.objectContaining({
client: "gemini_cli",
state: "aligned",
remediation: null,
}),
]),
},
});
await expect(access(fixture.workspace)).rejects.toMatchObject({
code: "ENOENT",
});
expect(await readFile(trust, "utf8")).toBe(text);
expect(await readFile(fixture.user)).toEqual(fixture.originalUser);
}
const workspaceText = "{ not json";
await writeFile(fixture.workspace, workspaceText);
const setup = await fixture.run(setupArguments);
expect(setup.exitCode, setup.stdout).toBe(1);
expect(setup.json).toMatchObject({
status: "needs_human",
plannedActions: [],
appliedActions: [],
remediation: expect.stringContaining(trust),
});
const doctor = await fixture.run(doctorArguments);
expect(doctor.exitCode, doctor.stdout).toBe(1);
expect(doctor.json).toMatchObject({
healthy: false,
identity: {
registrations: expect.arrayContaining([
expect.objectContaining({
client: "gemini_cli",
state: "invalid",
remediation: expect.stringContaining(trust),
}),
]),
},
});
expect(await readFile(fixture.workspace, "utf8")).toBe(workspaceText);
expect(await readFile(trust, "utf8")).toBe("{ not json");
expect(await readFile(fixture.user)).toEqual(fixture.originalUser);
},
);
for (const scenario of [
{ name: "an unlisted workspace", environment: {} },
{
name: "an explicitly untrusted workspace",
environment: { GEMINI_CLI_TRUST_WORKSPACE: "false" },
},
{
name: "a restricted workspace with an explicit trust opt-in",
environment: {
GEMINI_CLI_TRUST_WORKSPACE: "true",
GEMINI_RESTRICTED_MODE: "true",
},
},
])
cliTest(
`Gemini setup and doctor ignore malformed settings in ${scenario.name}`,
async ({ cli }) => {
const fixture = await geminiWorkspace(cli);
Object.assign(fixture.environment, scenario.environment);
const malformed = Buffer.from("{ not json");
await writeFile(fixture.workspace, malformed);
const setup = await fixture.run(setupArguments);
expect(setup.exitCode, setup.stdout).toBe(0);
expect(setup.json).toMatchObject({ status: "ready", appliedActions: [] });
const doctor = await fixture.run(doctorArguments);
expect(doctor.exitCode, doctor.stdout).toBe(0);
expect(doctor.json).toMatchObject({
healthy: true,
identity: {
registrations: expect.arrayContaining([
expect.objectContaining({
client: "gemini_cli",
state: "aligned",
remediation: null,
}),
]),
},
});
expect(await readFile(fixture.workspace)).toEqual(malformed);
expect(await readFile(fixture.user)).toEqual(fixture.originalUser);
await expect(
access(`${fixture.workspace}.rea.backup`),
).rejects.toMatchObject({
code: "ENOENT",
});
if (scenario.name === "an explicitly untrusted workspace") {
const excluded = Buffer.from(
JSON.stringify({ mcp: { excluded: ["rea"] } }),
);
await writeFile(fixture.workspace, excluded);
const excludedSetup = await fixture.run(setupArguments);
expect(excludedSetup.exitCode, excludedSetup.stdout).toBe(0);
const excludedDoctor = await fixture.run(doctorArguments);
expect(excludedDoctor.exitCode, excludedDoctor.stdout).toBe(0);
expect(excludedDoctor.json).toMatchObject({
identity: {
registrations: expect.arrayContaining([
expect.objectContaining({
client: "gemini_cli",
state: "aligned",
}),
]),
},
});
expect(await readFile(fixture.workspace)).toEqual(excluded);
}
},
);
cliTest(
"Gemini still rejects malformed settings and exclusions in a trusted workspace",
async ({ cli }) => {
const fixture = await geminiWorkspace(cli);
fixture.environment.GEMINI_CLI_TRUST_WORKSPACE = "true";
for (const scenario of [
{
text: "{ not json",
state: "invalid",
diagnostic: "Invalid JSON/JSONC",
},
{
text: JSON.stringify({ mcp: { excluded: ["rea"] } }),
state: "stale",
diagnostic: "mcp.excluded",
},
]) {
const original = Buffer.from(scenario.text);
await writeFile(fixture.workspace, original);
const setup = await fixture.run(setupArguments);
expect(setup.exitCode, setup.stdout).toBe(1);
expect(setup.json).toMatchObject({
status: "needs_human",
plannedActions: [],
appliedActions: [],
remediation: expect.stringContaining(scenario.diagnostic),
});
const doctor = await fixture.run(doctorArguments);
expect(doctor.exitCode, doctor.stdout).toBe(1);
expect(doctor.json).toMatchObject({
healthy: false,
identity: {
registrations: expect.arrayContaining([
expect.objectContaining({
client: "gemini_cli",
state: scenario.state,
remediation: expect.stringContaining(scenario.diagnostic),
}),
]),
},
});
expect(await readFile(fixture.workspace)).toEqual(original);
expect(await readFile(fixture.user)).toEqual(fixture.originalUser);
await expect(
access(`${fixture.workspace}.rea.backup`),
).rejects.toMatchObject({
code: "ENOENT",
});
}
},
);