This commit is contained in:
commit
7024e35f59
3 files changed
+35
-14
No files matched your search
@@ -5972,3 +5972,4 @@ Owner directed static-only fallback after actual nativepriority drift: no new pr
|
||||
2026-11-03T03:41:28Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | Vikunja #774 R2 for row 332 (GPT review P2 on round 1) on t3/proxy-launch-774-r2-fix, product commit b8f01a189 on a605e44d1 | Correction to the round 1 entry: its first proxy check refused only when the primary and the fallback both refused, so a healthy primary at capacity zero passed it and was refused only after modelPreflight. Both checks now pick the route through one helper, pickRoute in check(): the primary when its harness preflight passes, else the fallback through its own preflight. The first check calls it with a stand-in session proxy (PENDING_ADMISSION_KEY "pending-admission", a fixed non-secret marker, on the configured proxy port) that reaches only the preflights, then gates the picked route only, gate() still given null; no union and no retry. The final check calls it with proxyFor; its body is the round 1 code moved into the helper (proxyFor renamed proxyOf, two spaces less indent). Nothing reads the first check's route: startLocked takes model and account from the final check. Fixture options extra, real and hostEnv load the real claude-code harness with a pi fallback. Tests: 7 new in proxy-launch-order.test.mjs (healthy primary at capacity zero, fallback launch, failed primary at capacity zero, fallback at capacity zero, both preflights fail, no fallback, stand-in key absent from the launch log, bus events, session.json and the data root); on a605e44d1 the four refusal tests fail on modelPreflight 1 !== 0 and the three live tests pass. Runs: cli 1120/1121 with 1 skip (FED R7), 768 s; bus 1397/1397, 52 s; launch-limit-checks 5/5; neither known flake appeared. Mutants M4 (fall back on any primary gate refusal, test 1), M5 (first check never falls back, tests 2, 3, 4a, 4b, 6), M6 (no predicted preflight, tests 3, 4a, 4b, 5), the reviewer's unconditional throw at the helper's fallback decision (tests 2, 3, 4a, 4b, 6) and M7 (stand-in key written to the launch log, test 6) each killed. No push, queue, Vikunja, Gitea or PR action.
|
||||
2026-11-03T04:07:40Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | editor-browser.test.mjs:154 under Chromium 156 (Vikunja #784) on t3/editor-chromium-156, test commit 0c6aa1a1e on 1ff45c4ad | Case 2, no product defect: Chromium 156 checks script-src-attr when an inline handler attribute is created, also in a document with no window (WPT PR 62762; PR 64290 later skips such documents and 156 lacks it). All four reports come from prosemirror-view readHTML's innerHTML parse into a new createHTMLDocument (editor.js:8212:19, target the page document, one per pasted handler: img onerror, a onclick, a onmouseover, svg onload); no on* attribute reaches the live page and injected stays null; Chromium 153, 151 and Brave 154 report none for the same paste. Fix is test-only: the page's CSP record sets aside a script-src-attr report whose sourceFile is /editor.js, whose line is readHTML's parse (computed from the bundle, with a guard pinning those lines and detachedDoc) and whose target is the document; quiet(), inert(), injected, errors, rejections and the Console CSP are unchanged. New test: after a hostile paste, a handler set on a live editor paragraph, one parsed into a page-owned element never attached, and one set through prosemirror-view's attributes prop each still fail quiet(). editor-browser 17/17, editor plus editor-provenance 11/11, no skips; 4 mutants killed (all script-src-attr, target only, target only without the paragraph case, editor.js at any line). Residual: an element from that parse attached to the page after its report was sent would not be caught by the CSP record; on 156 its listener is never set. Full webui suite not run (bundle not rebuilt); Chromium 152 not on the host. No push, queue or PR action.
|
||||
2026-11-03T04:12:57Z | Claude Opus 5.5 implementation sub-agent (lead T3 c5c1e8ba) | Vikunja #415 and #416, .53 and .54 Console writes, on t3/console-writes-53-54, fix commit bd79daffd on 1ff45c4ad | Factory: scripts/case2-unit.sh takes the paired bare --sso --send (the lane b00019548 serve_flags rule), refuses either alone, still refuses the preview set and the proof pair, and before any unit write a bounded query run as the stack user, through the existing config-query supervisor, checks for console.sso. Library: unchanged and still read-only. Library --send with forward-auth and proof is not done: since #385 the tip's Console refuses --send without --sso (packages/webui/src/cli.mjs:63, serve.mjs:681), and reverting that is more than about 40 lines across the webui, service, release and upgrade scripts and reopens a reviewed security boundary, so it goes back to the lead. docs/guides/case1-business.mosaic-stack.json launches claude-opus-5-5 with provider proxy, route anthropic, under account factory with credential proxy. Tests: 5 new in case2-unit.test.mjs (4 RED on base) and 1 updated; case1-business-file.test.mjs 1 updated (RED on the base document) and 1 new. Runs: cli case2-unit 88/88, stack-upgrade 25/25, case1-accept 53/53; business case1-business-file 8/8, case1-project-key 3/3, chief-library-credentials 3/3; no skips. Mutants: factory --send without --sso and library without --proof-header required, each killed. Not done: scripts/case1-accept.sh still refuses --sso and --send in the args file (out of scope). No push, queue or PR action.
|
||||
2026-11-03T04:36:00Z | Claude Sonnet 5.5 implementation sub-agent (lead T3 c5c1e8ba) | editor-browser.test.mjs paste test, round 2 for Vikunja #784 on t3/784-r2, test commit e71222d1a on 0c6aa1a1e | GPT review of 0c6aa1a1e found that the CSP filter (directive, editor.js, readHTML line, target document) also sets aside a handler-bearing element that is attached to the live page and removed before its report is sent, because Chromium then targets the document too. The reviewer's insertion after editor.js:8212 (append reviewNode to body, put it back) passed the paste test and the live-handler test; reproduced here, editor-browser 17/17 with it. Fix is test-only: the page record now runs a MutationObserver on the live document (subtree, childList, attributes) that notes an added element, or a descendant of one, carrying an on* attribute and any on* attribute set in the page, without reading CSP events; h.quiet() asserts window.__live is empty; the live-handler test gains a case for a handler attached and removed in one turn and checks per case that the observer saw the handler (not for the page-owned element never attached). Runs: the reviewer's insertion now fails the paste test and the live-handler test with 'added a with onmouseover'; a variant that attaches a handler-bearing parent fails the same two; the four earlier weakened-filter mutants (all script-src-attr, target only, target only without the paragraph case, editor.js at any line) each fail the live-handler test, as does the observer pointed at a detached node; mutants in editor.js at another line and in app.js fail; one appended to the readHTML line fails the pin guard at load. Unmutated: editor-browser 17/17 (13.3 s), editor 5/5, editor-provenance 6/6, no skips; every mutant restored, git status empty. The earlier #784 BUILD-LOG and SESSIONS lines are on 1bf83078b, not on this base, so a merge keeps both appends. No push, queue or PR action.
|
||||
@@ -1978,3 +1978,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-11-03 | Claude (sub-agent of Mosaic Stack lead c5c1e8ba) | Vikunja #774 R2 for row 332 | b8f01a189 on t3/proxy-launch-774-r2-fix (base a605e44d1): one pickRoute helper picks the route in both checks; the first check predicts it on a stand-in key and gates only that route; 7 new tests; cli 1120 + 1 skip, bus 1397, launch-limit-checks 5; mutants M4-M7 and the reviewer's throw killed; no push, queue or PR action
|
||||
2026-11-03 | Claude (sub-agent of Mosaic Stack lead c5c1e8ba) | editor-browser :154 under Chromium 156 (Vikunja #784) | 0c6aa1a1e on t3/editor-chromium-156 (base 1ff45c4ad): test-only; the page's CSP record sets aside the paste's inert-parse script-src-attr reports, keyed on editor.js readHTML's parse line and target document; a new test shows live inline handlers still fail quiet(); editor-browser 17/17, editor plus editor-provenance 11/11, no skips; 4 mutants killed; no push, queue or PR action.
|
||||
2026-11-03 | Claude (sub-agent of Mosaic Stack lead c5c1e8ba) | Vikunja #415 and #416, .53 and .54 Console writes | bd79daffd on t3/console-writes-53-54 (base 1ff45c4ad): factory takes the paired --sso --send with a console.sso check before any unit write; .53 business document on provider proxy, route anthropic, account factory/proxy; library --send not done (needs the #385 refusal reverted, returned to the lead); cli case2-unit 88, stack-upgrade 25, case1-accept 53, business 8, 3 and 3, no skips; 2 mutants killed; no push, queue or PR action.
|
||||
2026-11-03 | Claude (sub-agent of Mosaic Stack lead c5c1e8ba) | editor-browser paste test round 2 (Vikunja #784) | e71222d1a on t3/784-r2 (base 0c6aa1a1e): test-only; a MutationObserver on the live document records handler-bearing elements entering the page and h.quiet() asserts none, so the CSP filter cannot hide an attach-then-remove; the reviewer's mutation now fails the paste and live-handler tests; the four earlier mutants still killed; editor-browser 17/17, editor 5/5, editor-provenance 6/6, no skips; no push, queue or PR action.
|
||||
@@ -31,11 +31,15 @@ const files = { 'docs/hostile.md': HOSTILE, 'docs/every.md': EVERY, 'docs/mix.md
|
||||
// so in a document with no window as well (WPT PR 62762; PR 64290 later skips a windowless document, and 156 lacks it). So
|
||||
// prosemirror-view's parse of pasted HTML into a fresh createHTMLDocument raises one report per handler against the page's
|
||||
// policy, though no handler there can run. The page record sets aside only reports whose event names, as the place the
|
||||
// attribute was created, the one statement below in editor.js, and whose target is the document. Why that cannot hide a
|
||||
// live handler: a handler put on a live element by any other code reports another file or line; an element from this parse
|
||||
// attached to the page before its report is sent makes the report's target that element; and a release that checks only
|
||||
// when a handler runs reports where it runs, never at this parse. INERT_PARSE pins the lines that make `elt` the body of a
|
||||
// document from detachedDoc(), and DETACHED pins that detachedDoc() makes a new createHTMLDocument each call.
|
||||
// attribute was created, the one statement below in editor.js, and whose target is the document. That filter alone cannot
|
||||
// tell a handler that stayed in the detached document from one that was attached to the live page and taken off again
|
||||
// before the report was sent: Chromium then also targets the document. So the page also records, with a MutationObserver
|
||||
// on the live document that does not look at CSP events, every element that enters the page carrying an on* attribute and
|
||||
// every on* attribute set on an element in the page (STUB, window.__live; quiet() asserts it stays empty). Mutation records
|
||||
// outlast an attach and a removal in one turn. A handler put on a live element by other code also reports another file or
|
||||
// line, and a release that checks only when a handler runs reports where it runs, never at this parse. INERT_PARSE pins the
|
||||
// lines that make `elt` the body of a document from detachedDoc(), and DETACHED pins that detachedDoc() makes a new
|
||||
// createHTMLDocument each call.
|
||||
const BUNDLE = readFileSync(new URL('../src/public/editor.js', import.meta.url), 'utf8').split('\n');
|
||||
const INERT_PARSE = [
|
||||
' function readHTML(html) {',
|
||||
@@ -60,6 +64,12 @@ const STUB = `(() => {
|
||||
addEventListener('securitypolicyviolation', e => (e.violatedDirective === 'script-src-attr' && e.sourceFile === location.origin + '/editor.js'
|
||||
&& e.lineNumber === ${INERT_PARSE_LINE} && e.target === document ? window.__cspInert : window.__csp).push(e.violatedDirective + ' ' + e.blockedURI));
|
||||
addEventListener('error', e => window.__errors.push(String(e.message)));
|
||||
window.__live = [];
|
||||
const handlers = el => [...el.attributes].map(a => a.name).filter(n => /^on/i.test(n));
|
||||
new MutationObserver(records => { for (const r of records) {
|
||||
if (r.type === 'attributes' && /^on/i.test(r.attributeName)) window.__live.push('set ' + r.attributeName + ' on ' + r.target.localName);
|
||||
for (const n of r.addedNodes) if (n.nodeType === 1) for (const e of [n, ...n.querySelectorAll('*')]) if (handlers(e).length) window.__live.push('added ' + e.localName + ' with ' + handlers(e));
|
||||
} }).observe(document, { subtree: true, childList: true, attributes: true });
|
||||
window.fetch = async (url, init = {}) => {
|
||||
const u = String(url);
|
||||
if (u === '/api/config') {
|
||||
@@ -97,8 +107,12 @@ async function session(run) {
|
||||
pm: () => wait('document.querySelector("#doc-visual .ProseMirror")'),
|
||||
answer: a => b.evaluate(`window.__answers.push(${JSON.stringify(a)}), true`),
|
||||
// What the page holds that must stay empty, whatever else a test does.
|
||||
quiet: async () => assert.deepEqual(await b.evaluate('({ csp: window.__csp, errors: window.__errors, rejections: window.__rejections, injected: window.injected ?? null })'),
|
||||
{ csp: [], errors: [], rejections: [], injected: null }),
|
||||
quiet: async () => {
|
||||
assert.deepEqual(await b.evaluate('({ csp: window.__csp, errors: window.__errors, rejections: window.__rejections, injected: window.injected ?? null })'),
|
||||
{ csp: [], errors: [], rejections: [], injected: null });
|
||||
// read apart from the CSP record: the filter on it cannot tell a handler that entered the page and left again
|
||||
assert.deepEqual(await b.evaluate('window.__live'), [], 'no element with an inline handler entered the live page');
|
||||
},
|
||||
// The editor's inert-ness: nothing in it can run or load.
|
||||
inert: () => b.evaluate(`(() => { const p = document.querySelector('#doc-visual .ProseMirror'); return {
|
||||
// prosemirror-view's own src-less <img class="ProseMirror-separator"> (a cursor hack after a non-editable node) is not content
|
||||
@@ -271,8 +285,11 @@ test('hostile content pasted into the editor runs and loads nothing, and a bad l
|
||||
|
||||
// Under Chromium 156 the paste raises one script-src-attr report per handler from the parse into prosemirror-view's
|
||||
// detached document, and the page record sets those aside (STUB). A handler on the live page must still fail quiet(): one
|
||||
// set on a paragraph in the editor and clicked, one parsed into an element the page owns but never attaches, and one that
|
||||
// prosemirror-view itself sets on an editor it draws, so that report names editor.js at another line.
|
||||
// set on a paragraph in the editor and clicked, one parsed into an element the page owns but never attaches, one that
|
||||
// prosemirror-view itself sets on an editor it draws, so that report names editor.js at another line, and one parsed into
|
||||
// an element the page attaches and takes off again in the same turn. Each also says whether the page's MutationObserver
|
||||
// saw the handler enter the page (window.__live): it must for all but the element never attached, or quiet() could not
|
||||
// catch a pasted handler that was attached and removed before its CSP report was sent.
|
||||
test('after a hostile paste, an inline handler on the live page still fails the quiet check', { timeout: 120000 }, async () => {
|
||||
await session(async h => {
|
||||
await h.open('docs/mix.md');
|
||||
@@ -292,19 +309,21 @@ test('after a hostile paste, an inline handler on the live page still fails the
|
||||
assert.deepEqual([view.bad, view.handlers, view.fetched, view.injected], [[], 0, [], null]);
|
||||
await h.quiet();
|
||||
const LIVE = [
|
||||
['a handler set on a paragraph in the editor, then clicked', `(() => { const p = document.querySelector('#doc-visual .ProseMirror p'); p.setAttribute('onclick', 'window.injected=64'); p.click(); return true; })()`],
|
||||
['a handler parsed into an element the page owns and never attaches', `(() => { document.createElement('div').innerHTML = '<img src="/nothing.png" onerror="window.injected=65">'; return true; })()`],
|
||||
['a handler set on a paragraph in the editor, then clicked', `(() => { const p = document.querySelector('#doc-visual .ProseMirror p'); p.setAttribute('onclick', 'window.injected=64'); p.click(); return true; })()`, true],
|
||||
['a handler parsed into an element the page owns and never attaches', `(() => { document.createElement('div').innerHTML = '<img src="/nothing.png" onerror="window.injected=65">'; return true; })()`, false],
|
||||
['a handler prosemirror-view sets through its attributes prop on an editor it draws, then clicked', `(() => { const host = document.body.appendChild(document.createElement('div'));
|
||||
const ed = window.MosaicEditor.mount(host, { text: 'x', editable: false }); ed.view.setProps({ attributes: { onclick: 'window.injected=66' } }); ed.view.dom.click(); return true; })()`],
|
||||
const ed = window.MosaicEditor.mount(host, { text: 'x', editable: false }); ed.view.setProps({ attributes: { onclick: 'window.injected=66' } }); ed.view.dom.click(); return true; })()`, true],
|
||||
['a handler parsed into an element the page attaches and takes off again in the same turn', `(() => { const d = document.createElement('div'); d.innerHTML = '<span onmouseover="window.injected=67">x</span>'; document.body.appendChild(d); d.remove(); return true; })()`, true],
|
||||
];
|
||||
for (const [what, expr] of LIVE) {
|
||||
for (const [what, expr, entered] of LIVE) {
|
||||
const aside = await h.q('window.__cspInert.length');
|
||||
await h.q('window.__csp = [], true');
|
||||
await h.q('window.__csp = [], window.__live = [], true');
|
||||
await h.q(expr);
|
||||
// the one report comes when the attribute is set (Chromium 156) or when the handler would fire (earlier releases)
|
||||
await h.wait(`window.__csp.length + window.__cspInert.length > ${aside}`);
|
||||
await assert.rejects(h.quiet(), e => { assert.deepEqual(e.actual, { csp: ['script-src-attr inline'], errors: [], rejections: [], injected: null }, what); return true; }, what);
|
||||
assert.equal(await h.q('window.__cspInert.length'), aside, `${what}: nothing set aside`);
|
||||
assert.equal(await h.q('window.__live.length > 0'), entered, `${what}: ${entered ? 'seen' : 'not seen'} entering the page`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user