Gate the build-path flip behind the same session key and origin checks

An unauthenticated POST /build-path wrote the flip event that makes --wait
instruct the agent to generate comps: same class as the /answer hole in #555.

Written with AI assistance under maintainer direction.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Abdul Wahab
2026-08-28 05:53:25 +05:00
committed by Abdul Wahab
co-authored by Cursor
parent eaaecbd1fe
commit 2e075dc58c
2 changed files with 24 additions and 1 deletions
+2 -1
View File
@@ -1306,7 +1306,7 @@ ${buildPath?.toggle ? `<div id="bp-confirm" role="dialog" aria-modal="true" aria
};
const apply = (value) => {
set(value);
fetch('/build-path', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ value }) });
fetch('/build-path' + keyQ, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ value }) });
if (value === 'comp') enterComp(); else exitComp();
};
// Flipping to comp starts real generation, so it confirms first; the
@@ -1659,6 +1659,7 @@ const server = http.createServer((req, res) => {
return;
}
if (req.method === 'POST' && pathname === '/build-path') {
if (rejectDetachedPost(req, res, url, port)) return;
let body = '';
req.on('data', (chunk) => { body += chunk; });
req.on('end', () => {
+22
View File
@@ -192,10 +192,32 @@ describe('serve-question', () => {
assert.equal(slashSlash.status, 400);
assert.equal((await fetch(url)).status, 200);
// The build-path flip commands the agent through --wait, so it takes the
// same key and Origin gate as /answer.
const flipPath = path.join(dir, '.impeccable', 'questions', `${key}.flip.json`);
const flipBody = JSON.stringify({ value: 'comp' });
const noKeyFlip = await fetch(`http://${goodHost}/build-path`, { method: 'POST', headers: jsonHeaders, body: flipBody });
assert.equal(noKeyFlip.status, 401);
assert.equal(existsSync(flipPath), false);
const evilOriginFlip = await rawRequest(port, {
method: 'POST',
path: `/build-path?key=${key}`,
headers: { ...jsonHeaders, Origin: 'https://evil.example' },
}, flipBody);
assert.equal(evilOriginFlip.status, 403);
assert.equal(existsSync(flipPath), false);
const okFlip = await fetch(`http://${goodHost}/build-path?key=${key}`, { method: 'POST', headers: jsonHeaders, body: flipBody });
assert.equal(okFlip.status, 200);
assert.equal(existsSync(flipPath), true);
const flipped = await run(['--wait', '--key', key, '--poll', '2']);
assert.equal(flipped.code, 0);
assert.match(flipped.out, /BUILD PATH FLIPPED/);
const html = await (await fetch(url)).text();
assert.match(html, /const KEY = "seckey"/);
assert.match(html, /\/answer' \+ keyQ/);
assert.match(html, /\/heartbeat' \+ keyQ/);
assert.match(html, /\/build-path' \+ keyQ/);
const ok = await fetch(`http://${goodHost}/answer?key=${key}`, { method: 'POST', headers: jsonHeaders, body });
assert.equal(ok.status, 200);