reviews: Q195 S0 amendments A1/A2 from Filbert, D8 ruling (#1798)
M1 adds WOFF1 summed-table and WOFF2 declared-size refusals. F2 is corrected: ctrl+click is already limited to http, https and mailto, and the exposure is DOCX save writing targets back; M2 now targets host-saved DOCX output. CSP report nits N1-N3 fixed. D8 recorded as ruled by Mos (mos-1363): style-src 'self', rfd hash not approved.
This commit is contained in:
1 parent
85ae2be773
commit
74ec320135
5 files changed
+48
-38
No files matched your search
@@ -5360,3 +5360,5 @@ Gander: after Duckworth-0100 design approval with C1-C5, prepared the two host-s
|
|||||||
2026-10-22T23:13:48Z | Gyro mos-1359 / r3 push guard | D13 records save-group timing as Mos's accepted reading of Q226 F41, not Jason's ruling; both defaults remain human-only settings. Push guard identity gyro/purpose push: exit 0 documented degraded-pass, CANNOT_ASSERT credential-unresolvable; audit gyro-q195-ci-queue-audit.jsonl, no credential fallback or CI-green claim.
|
2026-10-22T23:13:48Z | Gyro mos-1359 / r3 push guard | D13 records save-group timing as Mos's accepted reading of Q226 F41, not Jason's ruling; both defaults remain human-only settings. Push guard identity gyro/purpose push: exit 0 documented degraded-pass, CANNOT_ASSERT credential-unresolvable; audit gyro-q195-ci-queue-audit.jsonl, no credential fallback or CI-green claim.
|
||||||
|
|
||||||
2026-10-22T23:58:36Z | Fenton Q195 O2 S0 (#1798, sage-3058 under mos-1360) | Before: partial inventory b7daf5198, no verdict. After: CONDITIONAL PASS on WordCraft e05b7fe8. Inventory 15 local / 199 normal / 10 build-only / 25 build scripts / 10 proc-macros, 3 git deps by commit. Licenses resolved by a notice bundle (L1: epaint fonts missing from WordCraft NOTICE; self_cell and ryu elect Apache-2.0). Findings F1-F15 (F1 wuff WOFF1 orig_length allocation trap reachable from DOCX open, source only), mandatory mitigations M1-M14 with rejection tests. Browser on the copied artifact: style-src self 1 violation (rfd inline style, removed by host-owned Open), style-hash 0 violations, all functions run; Print blob iframe and cross-origin frame navigation blocked. D8 hash fallback returned to Mos, not recommended. Corrections: style-hash attempt 1 had a harness-made connect-src violation (rerun clean); Print evidence copied from the transcript. No compile, no Console/policy/live change, loopback-only fixtures. Filbert reviews.
|
2026-10-22T23:58:36Z | Fenton Q195 O2 S0 (#1798, sage-3058 under mos-1360) | Before: partial inventory b7daf5198, no verdict. After: CONDITIONAL PASS on WordCraft e05b7fe8. Inventory 15 local / 199 normal / 10 build-only / 25 build scripts / 10 proc-macros, 3 git deps by commit. Licenses resolved by a notice bundle (L1: epaint fonts missing from WordCraft NOTICE; self_cell and ryu elect Apache-2.0). Findings F1-F15 (F1 wuff WOFF1 orig_length allocation trap reachable from DOCX open, source only), mandatory mitigations M1-M14 with rejection tests. Browser on the copied artifact: style-src self 1 violation (rfd inline style, removed by host-owned Open), style-hash 0 violations, all functions run; Print blob iframe and cross-origin frame navigation blocked. D8 hash fallback returned to Mos, not recommended. Corrections: style-hash attempt 1 had a harness-made connect-src violation (rerun clean); Print evidence copied from the transcript. No compile, no Console/policy/live change, loopback-only fixtures. Filbert reviews.
|
||||||
|
|
||||||
|
2026-10-23T00:06:03Z | Fenton Q195 O2 S0 amendments (#1798, sage-3085, mos-1363) | Before: 85ae2be77 CONDITIONAL PASS; Filbert concurred with A1/A2 required (comment 30914). After: A1 M1 refuses WOFF1 per-table and summed origLength and WOFF2 totalSfntSize/brotli uncompressed_size before decode (WOFF2 a regression case, wuff bounds it at 128 MiB). A2 corrected F2: ctrl+click is already limited to http/https/mailto (canvas.rs:2490-2501, link.rs:7); exposure is DOCX save writing targets back (write/story.rs:335-340); F2 now low, M2 retargeted at host-saved DOCX output plus https under M7. N1-N3 fixed in the CSP report (resize receipt, harness/ prefix, loopback claim). D8 recorded as ruled by Mos mos-1363: style-src self, hash not approved. Docs only, no slot.
|
||||||
@@ -1553,3 +1553,4 @@ are never rewritten or removed; corrections are new entries.
|
|||||||
2026-10-22T23:09:38Z | Gyro (Codex gpt-6.1-sol, fenton-0006) | Q195 O2 design corrections on gyro/editor-o2 | F1 requirement boundary sent to Mos, adoption gated; F2-F6 source-checked docs fixes prepared. Ten briefs and two open owner items checked statically. No implementation, build/browser/worker run, queue or live action; Fenton re-check after push.
|
2026-10-22T23:09:38Z | Gyro (Codex gpt-6.1-sol, fenton-0006) | Q195 O2 design corrections on gyro/editor-o2 | F1 requirement boundary sent to Mos, adoption gated; F2-F6 source-checked docs fixes prepared. Ten briefs and two open owner items checked statically. No implementation, build/browser/worker run, queue or live action; Fenton re-check after push.
|
||||||
2026-10-22T23:13:27Z | Gyro (Codex gpt-6.1-sol, fenton-0007) | Q195 O2 r3 F7/F8 | Save-group keys/defaults/validation and W3b refusal/W4b lift ownership explicit. Static docs checks only. Q36 remains open; no implementation or gate lift. Direct Fenton/Mos handoff after push.
|
2026-10-22T23:13:27Z | Gyro (Codex gpt-6.1-sol, fenton-0007) | Q195 O2 r3 F7/F8 | Save-group keys/defaults/validation and W3b refusal/W4b lift ownership explicit. Static docs checks only. Q36 remains open; no implementation or gate lift. Direct Fenton/Mos handoff after push.
|
||||||
2026-10-22T23:58:36Z | Fenton (Claude Opus 5.5, sage-3058) | Q195 O2 S0 WordCraft source security and CSP review, fenton/q195-s0 | CONDITIONAL PASS, M1-M14 mandatory, D8 style-src hash held for Mos. Reviews docs only; Filbert review requested.
|
2026-10-22T23:58:36Z | Fenton (Claude Opus 5.5, sage-3058) | Q195 O2 S0 WordCraft source security and CSP review, fenton/q195-s0 | CONDITIONAL PASS, M1-M14 mandatory, D8 style-src hash held for Mos. Reviews docs only; Filbert review requested.
|
||||||
|
2026-10-23T00:06:03Z | Fenton (Claude Opus 5.5, sage-3085) | Q195 S0 amendments A1/A2 and N1-N3 on fenton/q195-s0, D8 ruling recorded | Docs only; Filbert re-check requested.
|
||||||
@@ -30,14 +30,14 @@ Copied from Louie's build (origin/louie/wordcraft-eval 880ef19e0); each hash mat
|
|||||||
|
|
||||||
| File | SHA-256 |
|
| File | SHA-256 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| www/frame.html | `6a8d141fa8b073cff73fa666fdf030a9f91030924e74a6aa0a441d76a5583f82` |
|
| harness/www/frame.html | `6a8d141fa8b073cff73fa666fdf030a9f91030924e74a6aa0a441d76a5583f82` |
|
||||||
| www/frame-bootstrap.js | `32c8f71acfe14f3bdc4d31534ed3a84609fc4fb1fb8a1a4a96f56eb28f39d3d4` |
|
| harness/www/frame-bootstrap.js | `32c8f71acfe14f3bdc4d31534ed3a84609fc4fb1fb8a1a4a96f56eb28f39d3d4` |
|
||||||
| www/frame.css | `629eb32e9cdf6b97f0ce79f841419ab4a80d42f322c10948ec5d23198799111e` |
|
| harness/www/frame.css | `629eb32e9cdf6b97f0ce79f841419ab4a80d42f322c10948ec5d23198799111e` |
|
||||||
| www/shell.html | `cf94f1ce359e8b90f86d39cdef98894d7874e97da9cf7eb0808b0f6e038b3bac` |
|
| harness/www/shell.html | `cf94f1ce359e8b90f86d39cdef98894d7874e97da9cf7eb0808b0f6e038b3bac` |
|
||||||
| www/shell.css | `372716168beb22dd9fbc7e3d45a76bc06c1b3b1d8f59807a187700689d9ff38c` |
|
| harness/www/shell.css | `372716168beb22dd9fbc7e3d45a76bc06c1b3b1d8f59807a187700689d9ff38c` |
|
||||||
| harness/serve.py | `ad34aed5cdf49a77128778f5f6523698d35f5a6eca771e1dbe53941f09fabe80` |
|
| harness/serve.py | `ad34aed5cdf49a77128778f5f6523698d35f5a6eca771e1dbe53941f09fabe80` |
|
||||||
| harness/instrument.js | `028e914a994a518043dab0b834a9917fc67bc35730f9e3725b59ebd8fa8fb0fa` |
|
| harness/instrument.js | `028e914a994a518043dab0b834a9917fc67bc35730f9e3725b59ebd8fa8fb0fa` |
|
||||||
| fixtures/s0-full-feature.docx (9985 B) | `b40eb5875518701d10de1a5419931f02e10c378560fbd5ab6a2ed68d19be53f4` |
|
| harness/fixtures/s0-full-feature.docx (9985 B) | `b40eb5875518701d10de1a5419931f02e10c378560fbd5ab6a2ed68d19be53f4` |
|
||||||
|
|
||||||
The frame wrapper loads the wasm-bindgen module from `frame-bootstrap.js`; it has no inline
|
The frame wrapper loads the wasm-bindgen module from `frame-bootstrap.js`; it has no inline
|
||||||
script or inline style. The fixture is generated by `harness/make_fixture.py`. It carries
|
script or inline style. The fixture is generated by `harness/make_fixture.py`. It carries
|
||||||
@@ -88,8 +88,10 @@ measurement. The real adapter's frame route sends the frame header above.
|
|||||||
| frame navigation test | 7392 | 1 in the shell, intended: `frame-src`, enforce | n/a | `browser-console-network.txt` |
|
| frame navigation test | 7392 | 1 in the shell, intended: `frame-src`, enforce | n/a | `browser-console-network.txt` |
|
||||||
| Print probe | 7392 | 1, intended: `frame-src`, blocked `blob` | Print blocked | `print-probe.json` |
|
| Print probe | 7392 | 1, intended: `frame-src`, blocked `blob` | Print blocked | `print-probe.json` |
|
||||||
|
|
||||||
Each full pass made exactly 8 requests: the 8 routes, all from 127.0.0.1, all 200. The fixture
|
Each full pass made exactly 8 requests: the 8 routes, all 200, and the fixture fetch is one of
|
||||||
fetch is one of them. No other origin was contacted.
|
them. That the client was 127.0.0.1 rests on the server binding 127.0.0.1 only (`serve.py`); the
|
||||||
|
request log does not record the peer address. The browser's network list showed no request to
|
||||||
|
any other origin.
|
||||||
|
|
||||||
The one style-self violation fired between the file-picker click and the fixture feed. Source:
|
The one style-self violation fired between the file-picker click and the fixture feed. Source:
|
||||||
rfd 0.17.2 `src/backend/wasm.rs:127-129` appends its `wasm/style.css` as a `<style>` element. Its
|
rfd 0.17.2 `src/backend/wasm.rs:127-129` appends its `wasm/style.css` as a `<style>` element. Its
|
||||||
@@ -107,7 +109,7 @@ Every function in both full passes ran on the same fixture. Event logs record ea
|
|||||||
| Load and start | wgpu GL backend, event handlers installed |
|
| Load and start | wgpu GL backend, event handlers installed |
|
||||||
| Open (Browse) | `input[type=file]` click, accept `.docx,.docm,.dotx,.dotm,.doc,.dot,.odt,.rtf,.txt,.md,.html,.htm,.tex,.json,.xml`; fixture fed, title changes to `s0-full-feature` |
|
| Open (Browse) | `input[type=file]` click, accept `.docx,.docm,.dotx,.dotm,.doc,.dot,.odt,.rtf,.txt,.md,.html,.htm,.tex,.json,.xml`; fixture fed, title changes to `s0-full-feature` |
|
||||||
| Edit | text inserted and the title gains the dirty mark (screenshots `clean-02`, `selffull-03`) |
|
| Edit | text inserted and the title gains the dirty mark (screenshots `clean-02`, `selffull-03`) |
|
||||||
| Resize | 1024x700 (style-self also 1280x800 then back) |
|
| Resize | 1024x700 (style-self also 1280x800 then back). style-self has a `step` event; the style-hash resize has no event record, only screenshot `clean-03-resized.png` and the session transcript |
|
||||||
| Hyperlink ctrl+click | `window.open("https://example.invalid/", "_blank")` |
|
| Hyperlink ctrl+click | `window.open("https://example.invalid/", "_blank")` |
|
||||||
| Discord title button | `window.open("https://discord.gg/artcraft", "_blank")` |
|
| Discord title button | `window.open("https://discord.gg/artcraft", "_blank")` |
|
||||||
| Save As (.docx) | `showSaveFilePicker`, then `createObjectURL` + anchor download, 10382 / 10386 B |
|
| Save As (.docx) | `showSaveFilePicker`, then `createObjectURL` + anchor download, 10382 / 10386 B |
|
||||||
@@ -175,7 +177,8 @@ surface the scratch harness cannot provide.
|
|||||||
3. W2 re-measures the adapter build under `style-src 'self'` and must record 0 violations with
|
3. W2 re-measures the adapter build under `style-src 'self'` and must record 0 violations with
|
||||||
full function coverage, including host-owned Print.
|
full function coverage, including host-owned Print.
|
||||||
|
|
||||||
**Held for Mos.** On the unmodified artifact, `style-src 'self'` is not violation-free. The
|
**Ruled by Mos (mos-1363, 2026-10-23T00:05Z).** On the unmodified artifact, `style-src 'self'`
|
||||||
measured fallback is the rfd hash above. D8 says any extra source is a new allowance, so I am
|
is not violation-free, and the measured fallback was the rfd hash above. Mos ruled that
|
||||||
not recommending it. It is returned to Mos as the delta. If the adapter cannot remove rfd from
|
`style-src` stays `'self'`; the hash is not approved and not kept open. W2 must re-measure 0
|
||||||
the web path, the choice between the hash and stopping the change is Mos's to make.
|
violations with full function coverage, including host-owned Print. If W2 cannot remove rfd
|
||||||
|
from the web path, the change goes back to Mos.
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"buildScripts": 25,
|
"buildScripts": 25,
|
||||||
"procMacros": 10
|
"procMacros": 10
|
||||||
},
|
},
|
||||||
"verdict": "conditional-pass: baseline source acceptable only with mandatory mitigations M1-M14 adopted as S1/W gates; D8 style-src question returned to Mos (style-src 'self' measured 1 violation on the unmodified artifact, attributed to rfd and removed by M5)",
|
"verdict": "conditional-pass: baseline source acceptable only with mandatory mitigations M1-M14 adopted as S1/W gates (amended per Filbert A1/A2); D8 ruled by Mos mos-1363: style-src stays 'self', rfd hash not approved, M5 removes rfd, W2 re-measures 0 violations including host-owned Print",
|
||||||
"findings": [
|
"findings": [
|
||||||
{
|
{
|
||||||
"id": "F1",
|
"id": "F1",
|
||||||
@@ -31,15 +31,15 @@
|
|||||||
"blocking": "mitigated-by M1",
|
"blocking": "mitigated-by M1",
|
||||||
"evidence": "source",
|
"evidence": "source",
|
||||||
"title": "WOFF1 table decompression trusts orig_length (allocation trap)",
|
"title": "WOFF1 table decompression trusts orig_length (allocation trap)",
|
||||||
"detail": "wuff 0.2.9 decompress_woff1.rs:96 passes table.orig_length (u32 from file) to decompress_z, which calls Vec::with_capacity(size_hint) at :15. Only check is woff_length < orig_length. Reached on document open: engine/io.rs:164-182 open_bytes_in -> load_embedded_fonts -> docx fonts.rs:30 embedded_fonts (no fontKey: stored bytes as is) -> fonts user.rs:143-163 decode_font_file. MAX_FONT_FILE_BYTES 50 MiB caps input; MAX_DECODED_BYTES 128 MiB is checked after decode. user.rs:166 catch_unwind does not help on wasm32-unknown-unknown (panic=abort, no profile override) and allocation failure aborts regardless. Effect: wasm instance trap, unsaved edits lost. Also reachable from Add Font. Not reproduced in the browser."
|
"detail": "wuff 0.2.9 decompress_woff1.rs:96 passes table.orig_length (u32 from file) to decompress_z, which calls Vec::with_capacity(size_hint) at :15. Only check is woff_length < orig_length. Reached on document open: engine/io.rs:164-182 open_bytes_in -> load_embedded_fonts -> docx fonts.rs:30 embedded_fonts (no fontKey: stored bytes as is) -> fonts user.rs:143-163 decode_font_file. MAX_FONT_FILE_BYTES 50 MiB caps input; MAX_DECODED_BYTES 128 MiB is checked after decode. user.rs:166 catch_unwind does not help on wasm32-unknown-unknown (panic=abort, no profile override) and allocation failure aborts regardless. Effect: wasm instance trap, unsaved edits lost. Also reachable from Add Font. Not reproduced in the browser. WOFF2 has its own bounds in wuff (uncompressed_size <= 128 MiB bail, compression ratio <= 100, capacity_hint clamped; decompress_woff2.rs:23,27,85-128), but brotli.rs:60 still allocates vec![0u8; expected_size] up to that bound before the post-decode cap."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "F2",
|
"id": "F2",
|
||||||
"severity": "medium",
|
"severity": "low",
|
||||||
"blocking": "mitigated-by M2",
|
"blocking": "mitigated-by M2",
|
||||||
"evidence": "source",
|
"evidence": "source",
|
||||||
"title": "DOCX hyperlink targets are not scheme-filtered on import",
|
"title": "DOCX hyperlink targets are not scheme-filtered on import or DOCX save",
|
||||||
"detail": "docx read/story.rs:485-496 keeps the relationship target as is (javascript:, data:, file:, custom schemes). is_safe_href exists but is used only by the latex/markdown/html exporters. Ctrl+click passes the target to window.open (browser: https://example.invalid/ _blank recorded). DOCX re-export writes it back."
|
"detail": "docx read/story.rs:485-496 keeps any target (javascript:, data:, file:, custom schemes) and docx write/story.rs:335-340 writes it back as an external relationship unfiltered. Activation is already gated: ctrl+click (canvas.rs:2490-2501) calls is_followable_link -> link::is_web_or_mail_link (doc link.rs:7), which allows only http, https and mailto (corrected per Filbert A2; the review first said activation was ungated). is_safe_href filters the markdown, latex, html, odt (odt.rs:399) and rtf (rtf.rs:261) exporters, not DOCX. Exposure: a saved DOCX carries the target to a desktop Office user."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "F3",
|
"id": "F3",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"blocking": "mitigated-by M7",
|
"blocking": "mitigated-by M7",
|
||||||
"evidence": "browser+source",
|
"evidence": "browser+source",
|
||||||
"title": "Outbound navigation via window.open",
|
"title": "Outbound navigation via window.open",
|
||||||
"detail": "Measured: hyperlink ctrl+click https://example.invalid/ _blank; Discord title button https://discord.gg/artcraft _blank (lib.rs:1683-1686, show_discord defaults true lib.rs:376); About links getartcraft.com, discord.gg, github.com _self; sidebar community _blank. Path: ui-egui lib.rs:2244-2245 ctx.open_url -> eframe web open_url. The frame navigating itself to a cross-origin shell URL was blocked by frame-src (shell CSP default-src fallback); nothing reached the target server."
|
"detail": "Measured: hyperlink ctrl+click https://example.invalid/ _blank; Discord title button https://discord.gg/artcraft _blank (lib.rs:1683-1686, show_discord defaults true lib.rs:376); About links getartcraft.com, discord.gg, github.com _self; sidebar community _blank. Path: ui-egui lib.rs:2244-2245 ctx.open_url -> eframe web open_url. The frame navigating itself to a cross-origin shell URL was blocked by frame-src (shell CSP default-src fallback); nothing reached the target server. Document hyperlinks reach open_url only for http, https and mailto (see F2); the Discord and About targets are hard-coded."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "F7",
|
"id": "F7",
|
||||||
@@ -192,17 +192,16 @@
|
|||||||
"for": [
|
"for": [
|
||||||
"F1"
|
"F1"
|
||||||
],
|
],
|
||||||
"requirement": "Reject WOFF/WOFF2 whose declared decoded size (header totalSfntSize and each table origLength) sum exceeds MAX_DECODED_BYTES before any decode, in the adapter or a reviewed WordCraft patch; embedded fonts are subject to the same gate.",
|
"requirement": "Before any decode, refuse a WOFF whose declared sizes exceed MAX_DECODED_BYTES: each WOFF1 table origLength, the WOFF1 sum of origLength, and the WOFF2 totalSfntSize and brotli uncompressed_size. Applies to embedded fonts and Add Font alike.",
|
||||||
"rejectionTest": "DOCX with an embedded WOFF1 whose table origLength is 0xFFFFFFFF opens with the font refused and the editor still responsive; same file through Add Font is refused."
|
"rejectionTest": "Each of these, embedded in a DOCX and via Add Font, is refused before decode with the editor responsive: (a) WOFF1 table origLength 0xFFFFFFFF; (b) WOFF1 tables each under the cap whose origLength sum exceeds it; (c) WOFF2 with huge totalSfntSize and brotli uncompressed_size. (a) and (b) fail on the baseline; (c) is a regression case, since wuff bounds WOFF2 at 128 MiB (Filbert A1)."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "M2",
|
"id": "M2",
|
||||||
"for": [
|
"for": [
|
||||||
"F2",
|
"F2"
|
||||||
"F6"
|
|
||||||
],
|
],
|
||||||
"requirement": "Hyperlink targets pass a host allowlist (https, http, mailto) at import and at activation; anything else is kept as inert text.",
|
"requirement": "Host-saved DOCX output carries only http, https and mailto external hyperlink targets (others dropped or kept as inert text). Activation keeps WordCraft's existing is_web_or_mail_link gate, and an https activation stays inside the M7 sandbox.",
|
||||||
"rejectionTest": "DOCX with javascript:, data:, file: and vbscript: hyperlinks: ctrl+click opens nothing and no window.open call is made."
|
"rejectionTest": "DOCX with javascript:, data:, file: and vbscript: hyperlinks, saved through host Save: the output has no such relationship target (fails on the baseline). Regression: ctrl+click on each makes no window.open call; ctrl+click on an https link opens no browsing context under the M7 sandbox."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "M3",
|
"id": "M3",
|
||||||
@@ -240,7 +239,8 @@
|
|||||||
{
|
{
|
||||||
"id": "M7",
|
"id": "M7",
|
||||||
"for": [
|
"for": [
|
||||||
"F6"
|
"F6",
|
||||||
|
"F2"
|
||||||
],
|
],
|
||||||
"requirement": "Frame sandbox without allow-popups and allow-top-navigation; show_discord forced false; About/community links removed or routed through the host link gate.",
|
"requirement": "Frame sandbox without allow-popups and allow-top-navigation; show_discord forced false; About/community links removed or routed through the host link gate.",
|
||||||
"rejectionTest": "Each measured window.open trigger (hyperlink, Discord, About x3, sidebar community) produces no new browsing context and no request off-origin."
|
"rejectionTest": "Each measured window.open trigger (hyperlink, Discord, About x3, sidebar community) produces no new browsing context and no request off-origin."
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Q195 O2 S0: WordCraft source security review
|
# Q195 O2 S0: WordCraft source security review
|
||||||
|
|
||||||
Status: COMPLETE. Verdict: **CONDITIONAL PASS**. The baseline source is acceptable for the
|
Status: COMPLETE. Verdict: **CONDITIONAL PASS**. The baseline source is acceptable for the
|
||||||
O2 adapter only with mandatory mitigations M1-M14 adopted as gates. One CSP question goes back
|
O2 adapter only with mandatory mitigations M1-M14 adopted as gates. Amended after Filbert's
|
||||||
to Mos under D8 (see "Verdict").
|
review (#1798 comment 30914, A1 and A2). Mos ruled the D8 question in mos-1363 (see "Verdict").
|
||||||
|
|
||||||
- Review id: `s0-20261022`. Record: Gitea #1798. Owner: Fenton. Reviewer: Filbert.
|
- Review id: `s0-20261022`. Record: Gitea #1798. Owner: Fenton. Reviewer: Filbert.
|
||||||
- Spec: `docs/plans/2026-10-21_q195-writing-editor.md` blob `322ec50f` at e36bce352: D6-D9 and
|
- Spec: `docs/plans/2026-10-21_q195-writing-editor.md` blob `322ec50f` at e36bce352: D6-D9 and
|
||||||
@@ -142,7 +142,7 @@ The license findings are resolved by M13, the notice bundle. None blocks.
|
|||||||
| Storage | localStorage `wordcraft.ui` (prefs.rs) holds the recent list, author, dictionary, autocorrect and UI keys. No cookies, and sessionStorage is empty. | [source][browser] | F9; M9 |
|
| Storage | localStorage `wordcraft.ui` (prefs.rs) holds the recent list, author, dictionary, autocorrect and UI keys. No cookies, and sessionStorage is empty. | [source][browser] | F9; M9 |
|
||||||
| Clipboard | Document-level paste listener, added once and never removed (web.rs ~203). | [source] | F11; M11 |
|
| Clipboard | Document-level paste listener, added once and never removed (web.rs ~203). | [source] | F11; M11 |
|
||||||
| Objects | vbaProject, attachedTemplate and OLE external relationships survive a round trip. Nothing executes them. | [source][agent-reported] | F5; M6 |
|
| Objects | vbaProject, attachedTemplate and OLE external relationships survive a round trip. Nothing executes them. | [source][agent-reported] | F5; M6 |
|
||||||
| Hyperlinks | Targets are imported unfiltered (docx read/story.rs:485-496). Ctrl+click, the About links and the Discord buttons all call `window.open`. `show_discord` defaults to true (ui-egui lib.rs:376). | [source][browser] | F2, F6; M2, M7 |
|
| Hyperlinks | Targets are imported unfiltered (docx read/story.rs:485-496) and DOCX save writes them back unfiltered (docx write/story.rs:335-340). Ctrl+click is already gated: canvas.rs:2490-2501 follows a link only if `is_web_or_mail_link` (doc link.rs:7) allows it (http, https, mailto). The About links and Discord buttons call `window.open` with hard-coded targets. `show_discord` defaults to true (ui-egui lib.rs:376). | [source][browser] | F2, F6; M2, M7 |
|
||||||
| Print/PDF | Hidden `iframe#wordcraft-print-frame` with a `blob:` PDF (web.rs:541-604). `frame-src 'none'` blocks it. | [source][browser] | F7; M8 |
|
| Print/PDF | Hidden `iframe#wordcraft-print-frame` with a `blob:` PDF (web.rs:541-604). `frame-src 'none'` blocks it. | [source][browser] | F7; M8 |
|
||||||
| MCP | The `mcp` crate is not in the web closure. | [source] | none |
|
| MCP | The `mcp` crate is not in the web closure. | [source] | none |
|
||||||
| Zotero | A protocol driver that takes a caller-supplied answer callback and has no transport of its own. Its UI actions are disabled on wasm32 (widgets.rs:183). | [source] | none |
|
| Zotero | A protocol driver that takes a caller-supplied answer callback and has no transport of its own. Its UI actions are disabled on wasm32 (widgets.rs:183). | [source] | none |
|
||||||
@@ -158,7 +158,7 @@ choose. The full text of each finding is in the companion `.json`.
|
|||||||
| Id | Severity | Evidence | Finding |
|
| Id | Severity | Evidence | Finding |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| F1 | medium | source | `wuff` 0.2.9 trusts the WOFF1 `orig_length`: `decompress_woff1.rs:96` passes it to `decompress_z`, which runs `Vec::with_capacity` at `:15`. The only check is `woff_length < orig_length`. A DOCX with an embedded WOFF reaches this on open: io.rs:164-182 → docx fonts.rs:30 → fonts user.rs:143-163 → wuff. The input cap is 50 MiB; the 128 MiB decoded cap is checked only after decoding. `catch_unwind` (user.rs:166) does nothing on wasm32-unknown-unknown (panic=abort), and allocation failure aborts anyway. Result: the wasm instance traps and unsaved edits are lost. Not reproduced in the browser. |
|
| F1 | medium | source | `wuff` 0.2.9 trusts the WOFF1 `orig_length`: `decompress_woff1.rs:96` passes it to `decompress_z`, which runs `Vec::with_capacity` at `:15`. The only check is `woff_length < orig_length`. A DOCX with an embedded WOFF reaches this on open: io.rs:164-182 → docx fonts.rs:30 → fonts user.rs:143-163 → wuff. The input cap is 50 MiB; the 128 MiB decoded cap is checked only after decoding. `catch_unwind` (user.rs:166) does nothing on wasm32-unknown-unknown (panic=abort), and allocation failure aborts anyway. Result: the wasm instance traps and unsaved edits are lost. Not reproduced in the browser. |
|
||||||
| F2 | medium | source | DOCX hyperlink targets are kept unfiltered, including `javascript:`, `data:`, `file:` and custom schemes. `is_safe_href` exists but only the latex, markdown and html exporters use it. |
|
| F2 | low | source | DOCX hyperlink targets (`javascript:`, `data:`, `file:`, custom schemes) are kept on import and written back unfiltered on DOCX save (write/story.rs:335-340), so a saved file carries them to a desktop Office user. Activation inside WordCraft is already limited to http, https and mailto (canvas.rs:2490-2501, link.rs:7). `is_safe_href` filters the markdown, latex, html, ODT (odt.rs:399) and RTF (rtf.rs:261) exporters, not DOCX. Corrected after Filbert's A2: this row first said activation was ungated and rated it medium. |
|
||||||
| F3 | medium | source + browser | `.json` opens as a native serde `Document` and skips the package caps. The web picker accepts it. |
|
| F3 | medium | source + browser | `.json` opens as a native serde `Document` and skips the package caps. The web picker accepts it. |
|
||||||
| F4 | medium | agent-reported, spin cap source-verified | Import exhausts resources on the main thread: encrypted DOCX spin count up to 10,000,000 (crypt.rs:31); no ODT package total; CFB up to 1 GiB; image decode limits 512 MiB; zip has no ratio limit of its own. The frame shares the shell's renderer process. |
|
| F4 | medium | agent-reported, spin cap source-verified | Import exhausts resources on the main thread: encrypted DOCX spin count up to 10,000,000 (crypt.rs:31); no ODT package total; CFB up to 1 GiB; image decode limits 512 MiB; zip has no ratio limit of its own. The frame shares the shell's renderer process. |
|
||||||
| F5 | low | source + agent-reported | vbaProject (package.rs:40/83/87), attachedTemplate (:27) and OLE external relationships are passed through on save. |
|
| F5 | low | source + agent-reported | vbaProject (package.rs:40/83/87), attachedTemplate (:27) and OLE external relationships are passed through on save. |
|
||||||
@@ -174,6 +174,8 @@ choose. The full text of each finding is in the companion `.json`.
|
|||||||
| F15 | low | agent-reported | Non-hermetic build inputs; no build script or proc-macro uses the network. |
|
| F15 | low | agent-reported | Non-hermetic build inputs; no build script or proc-macro uses the network. |
|
||||||
| L1-L5 | license | source + artifact | See "Licenses and notices". |
|
| L1-L5 | license | source + artifact | See "Licenses and notices". |
|
||||||
|
|
||||||
|
Filbert's A1 and A2 (#1798 comment 30914) amended F2, M1 and M2; the rows above are the amended text.
|
||||||
|
|
||||||
No finding blocks on its own. Each medium is closed by a mandatory mitigation with a rejection
|
No finding blocks on its own. Each medium is closed by a mandatory mitigation with a rejection
|
||||||
test. Two things would turn F1 into a blocker: the adapter failing to gate font decode size, or
|
test. Two things would turn F1 into a blocker: the adapter failing to gate font decode size, or
|
||||||
the corpus in M14 finding a trap that M1 does not cover.
|
the corpus in M14 finding a trap that M1 does not cover.
|
||||||
@@ -185,13 +187,13 @@ unmodified baseline before the mitigation lands.
|
|||||||
|
|
||||||
| Id | Closes | Requirement | Rejection test |
|
| Id | Closes | Requirement | Rejection test |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| M1 | F1 | Refuse a WOFF or WOFF2 whose declared decoded sizes exceed `MAX_DECODED_BYTES` before decoding, for embedded fonts and Add Font alike. | A DOCX embedding a WOFF1 with `origLength` 0xFFFFFFFF opens with the font refused and the editor responsive. Add Font refuses the same font. |
|
| M1 | F1 | Before any decode, refuse a font whose declared sizes exceed `MAX_DECODED_BYTES`: each WOFF1 table `origLength`, the WOFF1 sum of `origLength`, and the WOFF2 `totalSfntSize` and brotli `uncompressed_size`. Applies to embedded fonts and Add Font alike. | Each case, embedded in a DOCX and through Add Font, is refused before decode with the editor responsive: (a) WOFF1 table `origLength` 0xFFFFFFFF; (b) WOFF1 tables each under the cap whose `origLength` sum exceeds it; (c) WOFF2 with a huge `totalSfntSize` and brotli `uncompressed_size`. (a) and (b) fail on the baseline. (c) is a regression case: wuff already bails above 128 MiB and a ratio of 100 (decompress_woff2.rs:23,27,85-92), though brotli.rs:60 still allocates up to that bound. |
|
||||||
| M2 | F2, F6 | Hyperlink targets pass a host allowlist (`https`, `http`, `mailto`) on import and on activation. Anything else stays as inert text. | `javascript:`, `data:`, `file:` and `vbscript:` links: ctrl+click makes no `window.open` call. |
|
| M2 | F2 | Host-saved DOCX output carries only http, https and mailto external hyperlink targets; any other target is dropped or kept as inert text. Activation keeps WordCraft's existing `is_web_or_mail_link` gate, and an https activation stays inside the M7 sandbox. | A DOCX with `javascript:`, `data:`, `file:` and `vbscript:` hyperlinks, saved through host Save, has none of those targets in its relationships; this fails on the baseline. Regression: ctrl+click on each makes no `window.open` call, and ctrl+click on an https link opens no browsing context under the M7 sandbox. |
|
||||||
| M3 | F3 | The host import allowlist excludes `.json` and any other native format. | A `.json` file sent through host Open is refused before WordCraft sees it. |
|
| M3 | F3 | The host import allowlist excludes `.json` and any other native format. | A `.json` file sent through host Open is refused before WordCraft sees it. |
|
||||||
| M4 | F4 | Host caps before handoff: file size, zip entry count and uncompressed total. Encrypted documents are refused, or their spin count is capped low. An import watchdog tears down the frame on a stall. | A zip bomb, an oversize ODT, an encrypted DOCX with spinCount 10,000,000 and an oversize PNG are each refused or aborted, and the shell stays responsive. |
|
| M4 | F4 | Host caps before handoff: file size, zip entry count and uncompressed total. Encrypted documents are refused, or their spin count is capped low. An import watchdog tears down the frame on a stall. | A zip bomb, an oversize ODT, an encrypted DOCX with spinCount 10,000,000 and an oversize PNG are each refused or aborted, and the shell stays responsive. |
|
||||||
| M5 | F8, F13 | Host-owned Open, Save and Export replace `Services.open_async`, `showSaveFilePicker` and the anchor download. rfd is never called on web. | A full pass under `style-src 'self'` records 0 violations, no file-input click, no `showSaveFilePicker` call and no `a.download`. |
|
| M5 | F8, F13 | Host-owned Open, Save and Export replace `Services.open_async`, `showSaveFilePicker` and the anchor download. rfd is never called on web. | A full pass under `style-src 'self'` records 0 violations, no file-input click, no `showSaveFilePicker` call and no `a.download`. |
|
||||||
| M6 | F5 | Strip vbaProject, attachedTemplate and OLE external relationships on save, or refuse `.docm` and `.dotm`. | A `.docm` round-trips with neither part present. |
|
| M6 | F5 | Strip vbaProject, attachedTemplate and OLE external relationships on save, or refuse `.docm` and `.dotm`. | A `.docm` round-trips with neither part present. |
|
||||||
| M7 | F6 | Frame sandbox without `allow-popups` or `allow-top-navigation`. `show_discord` forced false. The About and community links are removed or go through the host link gate. | None of the six measured `window.open` triggers creates a browsing context or sends an off-origin request. |
|
| M7 | F6, F2 | Frame sandbox without `allow-popups` or `allow-top-navigation`. `show_discord` forced false. The About and community links are removed or go through the host link gate. | None of the six measured `window.open` triggers creates a browsing context or sends an off-origin request. |
|
||||||
| M8 | F7 | Remove the blob print iframe. Print goes through the host, and `frame-src 'none'` stays. | Print creates no iframe and records no `frame-src` violation. |
|
| M8 | F7 | Remove the blob print iframe. Print goes through the host, and `frame-src 'none'` stays. | Print creates no iframe and records no `frame-src` violation. |
|
||||||
| M9 | F9 | No document-derived data goes into origin storage. The dictionary, autocorrect list and fonts are host-owned, or kept in a partition the host clears. | After open, edit and close, no fixture file name, author or user word remains in localStorage or IndexedDB. |
|
| M9 | F9 | No document-derived data goes into origin storage. The dictionary, autocorrect list and fonts are host-owned, or kept in a partition the host clears. | After open, edit and close, no fixture file name, author or user word remains in localStorage or IndexedDB. |
|
||||||
| M10 | F10 | Disable Local Font Access: `Permissions-Policy: local-fonts=()` on the frame response, or remove the call. | `queryLocalFonts` is missing or rejects, and no prompt appears. |
|
| M10 | F10 | Disable Local Font Access: `Permissions-Policy: local-fonts=()` on the frame response, or remove the call. | `queryLocalFonts` is missing or rejects, and no prompt appears. |
|
||||||
@@ -231,11 +233,13 @@ unmodified baseline before the mitigation lands.
|
|||||||
- Baseline meets the restrictions: yes. The measured frame header holds with every function
|
- Baseline meets the restrictions: yes. The measured frame header holds with every function
|
||||||
working, and the only `style-src 'self'` violation sits on a path that D7 already removes.
|
working, and the only `style-src 'self'` violation sits on a path that D7 already removes.
|
||||||
|
|
||||||
**Held for Mos (D8).** On the unmodified artifact, `style-src 'self'` records one violation:
|
**D8, ruled by Mos (mos-1363, 2026-10-23T00:05Z).** On the unmodified artifact, `style-src
|
||||||
rfd's inline `<style>`. My recommendation is the final header with `style-src 'self'` and no
|
'self'` records one violation: rfd's inline `<style>`. I recommended the final header with
|
||||||
extra source, with M5 removing rfd from the web path and W2 re-measuring to 0. The measured
|
`style-src 'self'` and no extra source, and returned rfd's hash
|
||||||
fallback, rfd's hash `'sha256-9tSqG2Th9gNn6sbn1ZYMSqakJLznxnsamRDeigez2Mo='`, is a new allowance
|
(`'sha256-9tSqG2Th9gNn6sbn1ZYMSqakJLznxnsamRDeigez2Mo='`) as the D8 delta. Mos ruled:
|
||||||
under D8. I am returning it to Mos as the delta and not recommending it.
|
`style-src` stays `'self'`; the hash is not approved and not kept open. M5 gates removing rfd
|
||||||
|
from the web path. W2 must re-measure 0 violations with full function coverage, including
|
||||||
|
host-owned Print. If W2 cannot remove rfd, the change goes back to Mos.
|
||||||
|
|
||||||
## Process disclosures
|
## Process disclosures
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user