Skip to content

Commit 0cb1571

Browse files
Merge pull request #509 from corbitsdev/cl-6709-treat-missing-project-trust-repo-field-as-invalid
Treat missing project-trust repo field as invalid
2 parents 8b70ad0 + bbe129d commit 0cb1571

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

src/trust/project-trust.test.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { describe, test, expect } from "bun:test";
2-
import { mkdtemp, readFile, rm } from "node:fs/promises";
2+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
33
import { tmpdir } from "node:os";
4-
import { join } from "node:path";
4+
import { dirname, join } from "node:path";
55

66
import {
77
loadProjectTrust,
88
projectTrustPath,
9+
readProjectTrustStore,
910
trustMcpServer,
1011
trustPlugin,
1112
type ProjectTrustStore,
@@ -99,4 +100,32 @@ describe("project trust store", () => {
99100
expect(exists).toBe(false);
100101
});
101102
});
103+
104+
test("store without a repo field is invalid, not defaulted to empty-but-valid", async () => {
105+
await withTempHome(async (home, cwd) => {
106+
const path = projectTrustPath(cwd, home);
107+
await mkdir(dirname(path), { recursive: true });
108+
await writeFile(
109+
path,
110+
JSON.stringify({ trustedPluginPaths: ["/plugins/a"], trustedMcpFingerprints: [] }),
111+
);
112+
113+
const result = await readProjectTrustStore(cwd, home);
114+
expect(result.state).toBe("invalid");
115+
expect(result.store.trustedPluginPaths).toEqual([]);
116+
expect(result.store.trustedMcpFingerprints).toEqual([]);
117+
});
118+
});
119+
120+
test("store with a non-string repo field is invalid", async () => {
121+
await withTempHome(async (home, cwd) => {
122+
const path = projectTrustPath(cwd, home);
123+
await mkdir(dirname(path), { recursive: true });
124+
await writeFile(path, JSON.stringify({ repo: 12345, trustedPluginPaths: [] }));
125+
126+
const result = await readProjectTrustStore(cwd, home);
127+
expect(result.state).toBe("invalid");
128+
expect(result.store).toEqual({ trustedPluginPaths: [], trustedMcpFingerprints: [] });
129+
});
130+
});
102131
});

src/trust/project-trust.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,15 @@ export async function readProjectTrustStore(
130130
path,
131131
);
132132
// Guard against a stale/copied record keyed to a different repo path: the
133-
// file records the repo it was written for and must match this cwd.
134-
if (validated.repo !== undefined && resolve(validated.repo) !== resolve(cwd)) {
133+
// file records the repo it was written for and must match this cwd. A
134+
// missing or non-string `repo` is invalid too — without it, a hand-edited
135+
// or stripped store would apply its grants to whatever cwd happens to hash
136+
// to this filename, defeating the mismatch guard entirely.
137+
if (typeof validated.repo !== "string") {
138+
logger.warn`project trust store missing repo field at ${path}`;
139+
return { state: "invalid", store: emptyStore() };
140+
}
141+
if (resolve(validated.repo) !== resolve(cwd)) {
135142
logger.warn`project trust store repo mismatch at ${path}: recorded ${validated.repo}, expected ${resolve(cwd)}`;
136143
return { state: "invalid", store: emptyStore() };
137144
}

0 commit comments

Comments
 (0)