feat(editor): work-item descriptions store sanitised HTML only; pages store the validated triple; hardened sanitiser

未關閉
#48 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

維護者通常 1 天內回覆

還沒有人認領這個 Issue。

評估

難度
5/5
預估耗時
一週以上
新手友好度
32/100
Issue 類型
功能
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
html, rust, sql, wasm

研究方向

Start by reading the sanitizer in crates/thrust-editor-doc/src/sanitize.rs, its tests in crates/thrust-editor-doc/tests/sanitize.rs, and the migration and contract references named in the issue. Then trace the work item write path and page fallback write path, keeping the blocked-by #44 dependency in view. Done means the acceptance tests pass, the page binary validation rejects invalid Yjs data, the migration stores only the specified work-item fields, and the listed deviations and questions are recorded.

由索引模型根據 Issue 內容生成。

描述

area:app prio:p0 review:fable-2026-10-19

Context

The slice stores a work item's description as unsanitised text (crates/thrust-adapters-postgres/migrations/0001_init.sql:33; crates/thrust-contracts/src/lib.rs:97, :214; crates/thrust-core/src/lib.rs:260). The sanitiser exists only in the spike crate thrust-editor-doc, behind its sanitize feature, and only thrust-editor-worker depends on it. It provides plane_builder(), Plane's nh3 allow-list (crates/thrust-editor-doc/src/sanitize.rs:100-115); hardened_builder(), which adds value filters (:167-177); validate_binary_data (:208-233); and strip_tags (:235-237). Sanitizer::default() and validate_html_content both use the Plane builder (:192, :197-200). ADR-0012 (accepted in round 27) stores client html, json and binary verbatim at P0 (adrs/ui/0012-editor-and-i18n-reuse.md:33), as decided under Q-F5.1 in round 11 (QUESTIONS.md:153).

Problem

  • FR-14, from spike 0006: work-item descriptions carry no binary (v1 returns it null in 64 of 64; research/0008 §2.1, line 42). The v1 serializer excludes description_json (apps/api/plane/api/serializers/issue.py:73), and the web editor initialises from description_html (apps/web/core/components/editor/rich-text/description-input/root.tsx:239). Only the page fallback path builds all three forms (apps/web/core/hooks/use-page-fallback.ts).
  • Storing client JSON next to sanitised HTML therefore creates two sources of truth, one of them unsanitised.
  • FR-23: the hardened config neutralises 125 of 125 executable payloads, where Plane's allow-list neutralises 116, and it leaves all 195 editor documents unchanged. Plane's allow-list changes 66 of the 195 for exactly two reasons: it strips issue-embed-component (5 documents) and rewrites rel (61) (research/0008 §7, lines 217-229).
  • At P1 the Rust converter derives the binary from HTML (§8).

Proposal

  • Work items store description_html (sanitised) and description_stripped (strip_tags of the sanitised HTML). There is no description_json or description_binary column at P0. Compat and v1 writes accept description_json and discard it. At P1, thrust-editor-doc derives JSON and the binary on first collab open.
  • Pages store the triple the fallback path sends. The HTML is sanitised. The binary is base64-decoded and checked with validate_binary_data, as Plane's PageBinaryUpdateSerializer does (apps/api/plane/app/serializers/page.py:180-198), and also checked to decode with yrs. Plane does not do that last check, so record it with the deviations below. Pages are an M3 screen, so this half lands with the pages work.
  • Sanitiser changes:
    • Make the hardened builder the default.
    • Allow issue-embed-component and its five attributes.
    • Preserve rel (turn off ammonia's link_rel and allow rel on a).
    • Accept that ammonia 4.x output is not byte-identical to nh3's 4.0.0.
    • Sanitise in the use-case layer, so compat and v1 share one path, and record the schema version (ADR-0012).
  • Record deviations D-016 to D-020 (handoff A4.4). Build no browser-flavour renderer (Q-0012.4).

Acceptance criteria

  • The migration gives work items description_html and description_stripped, and neither description_json nor description_binary.
  • A contract test sends one payload of each of the nine classes that survive Plane's allow-list (research/0008 §7, line 225) through create and update, and reads every one back neutralised. Control: with plane_builder() as the default, the test fails.
  • allow_list_preservation_of_editor_html (crates/thrust-editor-doc/tests/sanitize.rs:15-64) asserts 195 of 195 parse-equal under the new allow-list instead of only printing counts. Control: under plane_builder() it fails (129 of 195).
  • A page write with a truncated or non-Yjs binary gets a 400, and a valid fallback triple is stored.
  • DEVIATIONS.md has D-016 to D-020, and Q-0012.2, Q-0012.4, Q-F5.1 and Q-0061.4 are answered.

References

  • Review: .agents/plans/contrail/reviews/2026-10-19-fable-architecture-review.md (FR-14, FR-23)
  • research/0008-editor-conversions.md §2.1, §5 rows 7 and 8, §5.3, §7, §8, §11
  • ADR-0012 (:33; round-29 note), ADR-0061 (round-29 note); DEVIATIONS D-012, C-5, D-016 to D-020
  • Q-F5.1, Q-0012.2, Q-0012.4, Q-0061.4
  • crates/thrust-editor-doc/src/sanitize.rs:14, :100-115, :167-237; tests/sanitize.rs:15-64, :225-243
  • Plane oracle: apps/api/plane/api/serializers/issue.py:73; apps/api/plane/app/serializers/page.py:173-198; apps/web/core/components/editor/rich-text/description-input/root.tsx:239
  • Beads: plane-byd.6

Blocked by: #44

Finding: FR-14, FR-23

主要語言
Rust
星號
0
分支
0
平均合併
1 小時 35 分鐘
30 天內合併 PR
67

環境準備

  • 沒有 Dockerfile 或 Docker Compose 檔案
  • 沒有 Pull Request 範本
  • 閱讀貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

aRustyDev/thrust 的其他 Issue

查看 aRustyDev/thrust 的全部 Issue

相似的 Issue

更多 Rust Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。