Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions src/trust/project-trust.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { describe, test, expect } from "bun:test";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { dirname, join } from "node:path";

import {
loadProjectTrust,
projectTrustPath,
readProjectTrustStore,
trustMcpServer,
trustPlugin,
type ProjectTrustStore,
Expand Down Expand Up @@ -99,4 +100,32 @@ describe("project trust store", () => {
expect(exists).toBe(false);
});
});

test("store without a repo field is invalid, not defaulted to empty-but-valid", async () => {
await withTempHome(async (home, cwd) => {
const path = projectTrustPath(cwd, home);
await mkdir(dirname(path), { recursive: true });
await writeFile(
path,
JSON.stringify({ trustedPluginPaths: ["/plugins/a"], trustedMcpFingerprints: [] }),
);

const result = await readProjectTrustStore(cwd, home);
expect(result.state).toBe("invalid");
expect(result.store.trustedPluginPaths).toEqual([]);
expect(result.store.trustedMcpFingerprints).toEqual([]);
});
});

test("store with a non-string repo field is invalid", async () => {
await withTempHome(async (home, cwd) => {
const path = projectTrustPath(cwd, home);
await mkdir(dirname(path), { recursive: true });
await writeFile(path, JSON.stringify({ repo: 12345, trustedPluginPaths: [] }));

const result = await readProjectTrustStore(cwd, home);
expect(result.state).toBe("invalid");
expect(result.store).toEqual({ trustedPluginPaths: [], trustedMcpFingerprints: [] });
});
});
});
11 changes: 9 additions & 2 deletions src/trust/project-trust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,15 @@ export async function readProjectTrustStore(
path,
);
// Guard against a stale/copied record keyed to a different repo path: the
// file records the repo it was written for and must match this cwd.
if (validated.repo !== undefined && resolve(validated.repo) !== resolve(cwd)) {
// file records the repo it was written for and must match this cwd. A
// missing or non-string `repo` is invalid too — without it, a hand-edited
// or stripped store would apply its grants to whatever cwd happens to hash
// to this filename, defeating the mismatch guard entirely.
if (typeof validated.repo !== "string") {
logger.warn`project trust store missing repo field at ${path}`;
return { state: "invalid", store: emptyStore() };
}
if (resolve(validated.repo) !== resolve(cwd)) {
logger.warn`project trust store repo mismatch at ${path}: recorded ${validated.repo}, expected ${resolve(cwd)}`;
return { state: "invalid", store: emptyStore() };
}
Expand Down
Loading