Merge PR #1836 R5: fetch chat assets through the SSO public Host (#385)

This commit is contained in:
jason.woltje committed 2026-11-02 12:58:25 -06:00
commit c1e9841d36
3 files changed
+11 -4

No files matched your search

+1
View File
@@ -5842,3 +5842,4 @@ Q284 / #470 source-only security fix 2026-10-29T03:33:00.640624+00:00: pin worke
2026-11-02T18:19:04Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R4 on d0ddfa8de | R3-F1 fixed fail-closed, no migration: `scripts/stack-upgrade.sh` adds `legacy_console`, called right after `read_args` in `do_upgrade` and `do_rollback`. For the library profile (same default and last-wins rule as R3), an installed Console unit under the driver's UNIT_DIR whose ExecStart has a `--send` token after `serve` refuses new code `console-unit-legacy` (exit 3, nothing stopped). A missing unit is left to `read_old` (`units-unreadable`). The R3 args refusal stays. `case2-unit.sh`, factory, tester, stack-release.sh and OIDC/#412 untouched. Tests in stack-upgrade.test.mjs run the real base renderer (`git show 09a847eda` of case2-unit.sh and both templates) in the module fixture with fake getent/systemctl to make the legacy unit, prove the current case2-unit.sh refuses it, then drive upgrade and rollback with the real current renderer as A4's RENDER. Controls: read-only library unit, factory host, missing Console unit. RED on d0ddfa8de code 24/25 (the main case exits 2 rollback-failed, both units stopped), GREEN 25/25 (70.1 s). host-run-sheet section 7 now gives a read-only grep of the unit and says stop and report when it carries `--send`. TOOLS.md lists the code. No hash pins on changed files. stack-release.test.mjs and case2-unit.test.mjs read no changed file and were not rerun. One local commit, no push.
2026-11-02T18:58:01Z | Q284 lead: FIX-156 release bump to 0.0.13 | The lead gate on lead/q284-land 418f00bce failed scripts/test-task.sh "mission and private prompt paths delivered to adapter" twice (94 pass, 1 fail). Cause: the local worker image mosaic-poc-agent:0.85.1-r0.0.12 was built 2026-10-21 from pre-#156 source; its baked src/run-agent.sh:39 exports MOSAIC_SYSTEM_PROMPT_FILE=/var/lib/mosaic/system-prompt.md over the launcher value. The test is right. #156 changes the image contract both ways: its run-agent.sh refuses when the launcher does not set MOSAIC_SYSTEM_PROMPT_FILE, which no pre-#156 launcher does, and its launcher with an old image drops the user context directory and the private prompt path. Rebuilding the shared 0.0.12 tag would break the live pinned copy and every rollback target, so RELEASE moves to 0.0.13 and the new image gets its own tag (compose builds it from source on first use; release.sh ensure aligns an instance). No code change. Live activation of 0.0.13 belongs to the next re-pin. |
2026-11-02T18:55:26Z | Claude Sonnet 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R5 on 484aef7e2 | Test-only fix; no product file changed. The asset test in packages/webui/tests/chat.test.mjs still fetched `http://127.0.0.1:PORT/chat.js` bare, but since 9936a1be1 its `setup()` runs `sso: auth.sso`, and `serve.mjs:726` answers any non-public Host with 403 `public Host required` (the lead found it in the landing gate on lead/q284-land). The server is right, the test was stale. The test now fetches chat.js and chat.css through `c.get` on the public Host with the signed-in session (new `asText` option on the test `http()` so an asset body is not JSON-parsed), asserts 200, the content type and a non-empty body, asserts the same asset on the loopback Host gets 403 `public Host required`, and adds one line pinning that an unauthenticated public-Host request gets 303 to `/auth/login?return=%2Fchat.js` (`serve.mjs` 736-739), so the asset is not public. RED on 484aef7e2 29/30 (403 !== 200 at chat.test.mjs:350), GREEN 30/30. Whole packages/webui run 495/495, 0 fail (65 s). A first whole run gave 487/8, all 8 in pinned-copy.test.mjs: `fixtureRepo` copies `git ls-files -co --exclude-standard`, and a symlink named `packages/webui/node_modules` is untracked and not ignored (`node_modules/` matches directories only), so cpSync refuses it. That is an artifact of the symlink; the rerun used a real ignored directory of links. Observation, not changed: in `c.get` the `host` option is overridden by `baseHeaders.host`, so the existing POST assertion passing `host: 127.0.0.1:PORT` sends the public Host and exercises the Origin fence. No hash pins on changed files. One local commit, no push.
+1
View File
@@ -1894,3 +1894,4 @@ are never rewritten or removed; corrections are new entries.
2026-11-02T18:03:53Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R3 on work/vikunja-19-385-q284-complete at ab2bc23c7 | R2-F1 fixed: stack-upgrade refuses a library args file holding `--send` after `--` before any unit stops, for upgrade and rollback; R2-I1 component case and R2-I2 TOOLS.md precision added, R2-I3 noted. RED 0/1 then GREEN 2/2; stack-upgrade 23/23, stack-release 225/225, case2-unit 48/48. One local commit; no push, PR, Vikunja, Gitea or live action.
2026-11-02T18:19:04Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R4 on work/vikunja-19-385-q284-complete at d0ddfa8de | R3-F1 fixed: stack-upgrade refuses `console-unit-legacy` before any unit stops when a library host's installed Console unit runs serve with `--send`, for upgrade and rollback; host-run-sheet section 7 and TOOLS.md updated. RED 24/25 then GREEN 25/25 in stack-upgrade.test.mjs; other suites not rerun (no changed file read). One local commit; no push, PR, Vikunja, Gitea or live action.
2026-11-02 | Mosaic lead (Claude Opus 5.5, T3 c5c1e8ba) | PR 1835 (#156) RELEASE 0.0.12 to 0.0.13 so the changed worker image gets its own tag | Committed on work/vikunja-19-156-q284-worker-mounts; GPT review and lead gate follow.
2026-11-02T18:55:26Z | Claude Sonnet 5.5 implementation sub-agent (lead T3 c5c1e8ba) | #385 PR 1836 repair R5 on work/vikunja-19-385-q284-complete at 484aef7e2 | chat.test.mjs asset test now fetches chat.js and chat.css on the SSO public Host with a signed-in session, pins 403 `public Host required` on the loopback Host and 303 to sign-in without a session; test-only, serve.mjs correct. RED 29/30, GREEN 30/30 in chat.test.mjs; whole packages/webui 495/495. One local commit; no push, PR, Vikunja, Gitea or live action.
+9 -4
View File
@@ -25,7 +25,7 @@ const posts = [
[`/api/chat/threads/${T}/messages`, { body: ' hi\nthere ' }, 'thread.send', { thread: T, body: ' hi\nthere ', class: 'REQUEST' }, { id: M }],
[`/api/chat/threads/${T}/open`, {}, 'thread.open', { thread: T }, { opened: T }],
];
function http(port, path, { method = 'GET', value, raw, extra = {}, host = preview.host, baseHeaders = headers } = {}) {
function http(port, path, { method = 'GET', value, raw, asText = false, extra = {}, host = preview.host, baseHeaders = headers } = {}) {
const data = raw ?? (value === undefined ? undefined : JSON.stringify(value));
return new Promise((resolve, reject) => {
const req = request({ host: '127.0.0.1', port, method, path, headers: {
@@ -33,7 +33,7 @@ function http(port, path, { method = 'GET', value, raw, extra = {}, host = previ
} }, res => {
let text = '';
res.on('data', c => { text += c; });
res.on('end', () => resolve({ status: res.statusCode, data: text ? JSON.parse(text) : null, cache: res.headers['cache-control'], headers: res.headers }));
res.on('end', () => resolve({ status: res.statusCode, data: text ? (asText ? text : JSON.parse(text)) : null, cache: res.headers['cache-control'], headers: res.headers }));
});
req.on('error', reject); req.end(data);
});
@@ -345,11 +345,16 @@ test('configured Console process injects chat through its existing human transpo
test('chat assets, GET purity, method and unknown-route refusals are explicit', async t => {
const c = await setup(t);
// Under --sso the human listener answers only the public Host, and the page assets sit behind the session.
for (const name of ['chat.js', 'chat.css']) {
const result = await fetch(`http://127.0.0.1:${c.port}/${name}`);
const result = await c.get(`/${name}`, { asText: true });
assert.equal(result.status, 200);
assert.match(result.headers.get('content-type'), name.endsWith('.js') ? /javascript/ : /css/);
assert.match(result.headers['content-type'], name.endsWith('.js') ? /javascript/ : /css/);
assert.ok(result.data.length > 0, name);
const loopback = await c.get(`/${name}`, { extra: { host: `127.0.0.1:${c.port}` } });
assert.equal(loopback.status, 403); assert.equal(loopback.data.error, 'public Host required');
}
assert.equal((await c.get('/chat.js', { extra: { cookie: '' } })).status, 303);
assert.equal((await c.get(`/api/chat/threads/${T}/open`)).status, 404);
assert.equal((await c.get('/api/chat/private')).status, 404);
assert.equal((await c.get('/api/chat/projects', { method:'DELETE' })).status, 405);