From 4378d30c490dee6f2648559571b9c749ea4f40a8 Mon Sep 17 00:00:00 2001 From: Mathis Blanc Date: Sun, 16 Aug 2026 21:57:57 +0200 Subject: [PATCH 1/2] fix: open protected startup context reviews --- src/context_room.mjs | 14 +++++---- test/context_room.test.mjs | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/context_room.mjs b/src/context_room.mjs index 9226443..5c1548e 100644 --- a/src/context_room.mjs +++ b/src/context_room.mjs @@ -22425,7 +22425,8 @@ async function routeRequest(req, res, root, globalPreferencesPath = null, { return; } if (req.method === "GET" && url.pathname === "/api/startup-context") { - sendJson(res, 200, { files: listStartupContextFiles(root).map(publicStartupContextFile), root }); + const settings = readResolvedContextRoomSettings(root, { expectedRootIdentity }); + sendJson(res, 200, { files: listStartupContextFiles(root, settings).map(publicStartupContextFile), root }); return; } if (req.method === "GET" && url.pathname === "/api/startup-skills") { @@ -22465,14 +22466,16 @@ async function routeRequest(req, res, root, globalPreferencesPath = null, { } if (req.method === "GET" && url.pathname === "/api/startup-context/file") { const order = url.searchParams.get("order") || ""; - sendJson(res, 200, readStartupContextFile(root, order)); + const settings = readResolvedContextRoomSettings(root, { expectedRootIdentity }); + sendJson(res, 200, readStartupContextFile(root, order, settings)); return; } if (req.method === "POST" && url.pathname === "/api/startup-context/file") { const body = await readJsonBody(req); - const current = readStartupContextFile(root, body.order); + const settings = readResolvedContextRoomSettings(root, { expectedRootIdentity }); + const current = readStartupContextFile(root, body.order, settings); assertExpectedContentHash(current, body.expectedContentHash, current.path); - sendJson(res, 200, writeStartupContextFile(root, body.order, body.content, null, { + sendJson(res, 200, writeStartupContextFile(root, body.order, body.content, settings, { expectedRootIdentity, expectedContentHash: current.contentHash, expectedDisplayPath: current.path, @@ -22481,7 +22484,8 @@ async function routeRequest(req, res, root, globalPreferencesPath = null, { } if (req.method === "POST" && url.pathname === "/api/startup-context/delete") { const body = await readJsonBody(req); - sendJson(res, 200, deleteStartupContextFile(root, body.order, null, { expectedRootIdentity })); + const settings = readResolvedContextRoomSettings(root, { expectedRootIdentity }); + sendJson(res, 200, deleteStartupContextFile(root, body.order, settings, { expectedRootIdentity })); return; } if (req.method === "GET" && url.pathname === "/api/startup-hooks/file") { diff --git a/test/context_room.test.mjs b/test/context_room.test.mjs index 8d140d0..b295f84 100644 --- a/test/context_room.test.mjs +++ b/test/context_room.test.mjs @@ -1994,6 +1994,66 @@ test("raw config narrowing fails closed until the human owner explicitly saves t assert.equal(buildContextRoomDoctorReport(root).issues.some((issue) => issue.type === "review_authority_tamper"), false); }); +test("startup context API opens files from the last owner-authorized scope after raw narrowing", async (t) => { + const originalHome = process.env.HOME; + const home = makeRoot(); + const root = path.join(home, "work", "project"); + const hubHome = makeRoot(); + const previousHubHome = process.env.CONTEXT_ROOM_HUB_HOME; + process.env.HOME = home; + process.env.CONTEXT_ROOM_HUB_HOME = hubHome; + t.after(() => { + if (originalHome === undefined) delete process.env.HOME; + else process.env.HOME = originalHome; + if (previousHubHome === undefined) delete process.env.CONTEXT_ROOM_HUB_HOME; + else process.env.CONTEXT_ROOM_HUB_HOME = previousHubHome; + }); + + fs.mkdirSync(path.join(home, ".codex"), { recursive: true }); + fs.mkdirSync(root, { recursive: true }); + fs.writeFileSync(path.join(home, ".codex", "AGENTS.md"), "# Global agent instructions\n"); + initializeContextRoomProject(root, { allowedPaths: ["docs/"], watchAllow: [] }); + + const room = createMemoryServer({ root }); + await new Promise((resolve) => room.server.listen(0, "127.0.0.1", resolve)); + t.after(() => new Promise((resolve) => room.server.close(resolve))); + const baseUrl = `http://127.0.0.1:${room.server.address().port}`; + const authorized = readMemoryWebappSettings(root); + authorized.startupContext = { + enabled: true, + projectOnly: false, + fileNames: ["AGENTS.md"], + globalPaths: ["~/.codex/AGENTS.md"], + }; + const authorizeResponse = await fetch(`${baseUrl}/api/settings`, { + method: "POST", + headers: { "content-type": "application/json", "x-context-room-owner-nonce": room.ownerMutationNonce }, + body: JSON.stringify({ settings: authorized }), + }); + assert.equal(authorizeResponse.status, 200); + + const configPath = path.join(root, CONFIG_FILE); + const narrowed = JSON.parse(fs.readFileSync(configPath, "utf8")); + narrowed.startupContext.projectOnly = true; + narrowed.startupContext.globalPaths = []; + fs.writeFileSync(configPath, JSON.stringify(narrowed, null, 2) + "\n"); + + const queue = buildDocQaReport(root).queue; + assert.equal(queue.some((item) => item.path === "~/.codex/AGENTS.md"), true); + assert.ok(buildContextRoomDoctorReport(root).issues.some((issue) => issue.type === "review_authority_tamper")); + + const listResponse = await fetch(`${baseUrl}/api/startup-context`); + assert.equal(listResponse.status, 200); + const listed = await listResponse.json(); + assert.deepEqual(listed.files.map((file) => file.startupContext.displayPath), ["~/.codex/AGENTS.md"]); + + const openResponse = await fetch(`${baseUrl}/api/startup-context/file?order=1`); + assert.equal(openResponse.status, 200); + const opened = await openResponse.json(); + assert.equal(opened.path, "~/.codex/AGENTS.md"); + assert.equal(opened.content, "# Global agent instructions\n"); +}); + test("direct review-state and ledger forgery is ignored and reported as critical", () => { const root = makeRoot(); fs.mkdirSync(path.join(root, "docs"), { recursive: true }); From 8925dcdba8684edb30db225a98b73c045a93d09c Mon Sep 17 00:00:00 2001 From: Mathis Blanc Date: Sun, 16 Aug 2026 22:28:53 +0200 Subject: [PATCH 2/2] release: 0.6.15 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f1a63b..00f7fcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "context-room", - "version": "0.6.14", + "version": "0.6.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "context-room", - "version": "0.6.14", + "version": "0.6.15", "license": "MIT", "dependencies": { "ajv": "^8.20.0", diff --git a/package.json b/package.json index 4191bdf..54da6ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "context-room", - "version": "0.6.14", + "version": "0.6.15", "description": "Local-first documentation control room for AI-assisted projects.", "type": "module", "homepage": "https://www.npmjs.com/package/context-room",