torb doc: escape raw HTML of doc comments when rendering an untrusted package #234
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up of #17.
Context
std/markdownpasses raw HTML through: an HTML block (BlockContent.HtmlBlock) and an inline HTML span(
MarkdownInline.Html) are written as they are (std/markdown/src/html.trb), andcompiler/src/reference/render.trb(
markdownHtml) feeds every doc comment oftorb doc's site through that step. The registry's documentation worker(
tools/registry/src/documentation.trb, RELEASE.md section 7.7) runstorb doc --outputfor a stranger's package andserves the result from
packages.torb.dev. The Content-Security-Policy on/docs/(tools/deploy/nginx.conf) stopsscripts that are not the host's, but not
<meta http-equiv="refresh">redirects or full-page overlays made of inlinestyles. A link destination is not filtered either:
[x](javascript:...)is written as anhref.Design record: RELEASE.md section 7.6 (Security) and 7.7 (documentation per package: "their doc comments are a
stranger's text"). Seed: not touched (no syntax, no native, no driver flag the build uses, no name looked up by
string; the compiler's own build never passes the new flag).
Design (decided by the loop, see the comment below)
std/markdown: a second HTML step besidehtml(),MarkdownDocument.safeHtml(). Raw HTML blocks and inline rawHTML are written as escaped text (so they are visible in the page, not dropped), and a link or image destination
whose scheme is
javascript:,vbscript:ordata:(compared case-insensitively after trimming and removingASCII control characters and whitespace) is written as an empty
href/src.html()is unchanged: the CommonMarkexamples stay green.
markdownHtml(and everything incompiler/src/reference/site.trbthat calls it) takes the choice from a newtorb docflag--escape-html; without the flag the output is byte-for-byte what it is today (a package's owndocumentation is trusted).
--escape-htmltotorb doc --output. A toolchain that does not know the flag makestorb docfail with "not a flag of this command", which marks the versionfailed: it fails closed instead ofserving unescaped pages.
Acceptance criteria
std/markdowntests: withsafeHtml(),<meta http-equiv="refresh" content="0;url=x">as a block and as aninline span,
<div style="position:fixed">x</div>,<script>, and<img onerror=...>each come out asescaped text (
<meta ...);[a](javascript:alert(1)),[a](JaVaScRiPt:x),[a]( javascript:x),and[a](vbscript:x)leave an empty destination;[a](https://x),[a](#top),[a](relative.html)andare unchanged.html()stillpasses all 652 CommonMark examples.
compiler/tests/reference.test.trb(orsite.test.trb): a doc comment with a raw HTML block and an inline spanrenders as text with
--escape-htmland as HTML without it.torb doc ... --escape-htmlis parsed, listed in the usage text and indocs/tooling/torb-doc.md(synopsis anda sentence under "The site");
docs/standard-library/markdown.mddocumentssafeHtml; the skills underskills/are regenerated (torb docs skill docs skills).torb doc <package> --output <site> --escape-html, covered by its existing test(
tools/registry/tests/documentation.test.trb) asserting the argument list; RELEASE.md section 7.7 names theflag and the fail-closed behaviour.
sh tools/gates.sh ais green in forge CI.Size
One PR, two Conventional Commits:
feat(markdown): safeHtml escapes raw HTML and unsafe link schemesandfeat(doc): --escape-html for stranger's doc comments, passed by the registry worker.Not in this issue
Serving
/docsfrom its own cookieless origin (the second half of the proposal) is a deployment change intools/deploy, independent of this one; it is filed as its own issue when this has landed, if still wanted.torb docfor every version #17## Declarationssections of the standard library pages #32[loop:torbscript] intake: in scope (author orion, label loop), follow-up of #17, not a duplicate. Security-relevant (raw HTML from stranger doc comments in std/markdown rendering). next: waiting (refine queued, ready buffer full) until 2026-10-10T04:00:00Z
[loop:torbscript] claim refine by session:loop-torbscript-20261025-0542 until 2026-10-25T08:50:00Z (no ready issue is free to implement: all five are in review/CI or blocked, intake since 2026-10-09 not refined yet)
[loop:torbscript] decided by the loop: (1) A separate
safeHtml()step plus atorb doc --escape-htmlflag, off by default, instead of changinghtml()or making escaping the default:html()is the CommonMark reference output (652 spec examples), and a package's own localtorb docis trusted; only the registry renders strangers' text, so it opts in. (2) Raw HTML is shown as escaped text, not dropped (the issue's acceptance says "renders as text"; an author sees what was ignored). (3)javascript:,vbscript:anddata:link/image destinations are blanked in the same step: same threat, same function, a few lines, and the CSP does not cover a clickedjavascript:URL in every browser. (4) The worker passes the flag unconditionally, so an older toolchain fails the version (failed) rather than serving unescaped pages: fail closed. (5) The cookieless-origin half is deployment work and stays out; no owner question: internal security hardening, no public statement, cost or published-contract break (the flag is additive). Seed: untouched. Size: one PR, two commits.[loop:torbscript] refine: rewrote the issue to goal, design (std/markdown
safeHtml,torb doc --escape-html, registry worker passes it; link schemes blanked), testable acceptance criteria, RELEASE.md 7.6/7.7, seed untouched, one PR / two commits; decisions recorded above, no owner question. Worker tokens ~120k. next: ready[loop:torbscript] claim implement by session:loop-torbscript-20261025-0542 until 2026-10-25T13:05:00Z
[loop:torbscript] implement checkpoint (context guard, ~85k worker tokens): nothing committed or pushed yet; worktree
ws wt new torbscript/language 234 safe-html --branch loop/234-safe-htmlexists, clean, from main44a35916. Read so far: CLAUDE.md, std/markdown/src/html.trb, compiler/src/reference/{command,render,site}.trb, docs/tooling/torb-doc.md. Plan: (1) commit 1feat(markdown): safeHtml escapes raw HTML and unsafe link schemes: add asafe: Boolfield toHtmlRenderer,MarkdownDocument.safeHtml()besidehtml();.HtmlBlockand.Htmlwritten throughescapedHtml(block wrapped in<p>), link/image destination blanked when, after trim and removal of ASCII control chars and whitespace and lowercasing, it starts withjavascript:,vbscript:ordata:; tests in std/markdown/tests/markdown.test.trb per the acceptance list;docs/standard-library/markdown.md. (2) commit 2feat(doc): --escape-html ...:escapesHtml: Bool = falsedefault parameter (defaults exist) onmarkdownHtml(render.trb),siteFiles/writeSiteand the page helpers in site.trb (rootPage, packagePage, modulePage, itemHtml, deprecationHtml, sectionsHtml, inlineSummary; leave headScript/pageOf alone for #235),DocOptions.escapesHtml+ flag + usage line in command.trb, test in compiler/tests/reference.test.trb, docs/tooling/torb-doc.md (synopsis and a sentence under "The site"), registry workertools/registry/src/documentation.trbline ~411 add--escape-htmlplus its testtools/registry/tests/documentation.test.trb, RELEASE.md 7.7; thentorb docs skill docs skillsregenerate (needs a built torb: use CI or a quick local one, no bootstrap). Next: push, open PRCloses #234, request Argus, ci-wait. next: waiting for a continuation worker (implement not yet started in code).[loop:torbscript] claim implement by session:loop-torbscript-20261025-0542 until 2026-10-25T13:15:00Z (continuation worker 1 after the context-guard checkpoint)
[loop:torbscript] implement checkpoint 2 (context guard, ~85k tokens of continuation worker 1): branch
loop/234-safe-htmlpushed with two commits, worktreews wt new torbscript/language 234 safe-html --branch loop/234-safe-html(reuse). No PR yet.Done: commit 1
feat(markdown): safeHtml ...complete (html.trbsafefield +safeHtml(), text.trbhasScriptScheme, tests in std/markdown/tests/markdown.test.trb group "safe HTML", docs/standard-library/markdown.md and its copy in skills/torbscript-standard-library/references/standard-library/markdown.md by hand). Commit 2feat(doc): --escape-html ...is PARTIAL (pushed as a WIP of the final title; finish with a new fixup commit andgit rebase -i-free squash:git reset --soft HEAD~1then recommit with the same message, force-push with lease, own branch): done = render.trbmarkdownHtml(..., escapesHtml: Bool = false), site.trbsiteFiles/writeSite/*Page/itemHtml/...threaded, command.trbDocOptions.escapesHtml+--escape-html+ usage line + header doc, compiler/tests/reference.test.trb (hostileLike fixture, 3 tests).Still to do in commit 2: (a) docs/tooling/torb-doc.md synopsis line 30 + flag list near line 36 + a sentence under "The site"; compiler/CONTRIBUTING.md/the-torb-command reference if it lists doc flags (grep
--no-rununder docs/ and skills/**/the-torb-command.md); (b) tools/registry/src/documentation.trb (~line 411,torb doc --output): add--escape-html, and tools/registry/tests/documentation.test.trb argument-list assertion; tools/registry/README.md line ~120; (c) docs/design/RELEASE.md section 7.7 names the flag and fail-closed; (d) regenerate or hand-edit skills/ copies of the torb-doc doc (torb docs skill docs skillsneeds a torb; none installed locally: hand-edit the generated copy in skills/torbscript-*/references/tooling/torb-doc.md the same way as the markdown.md copy, links stripped). Nothing was run locally (no torb installed): CI is the first check, expect format/typo rounds (line width 120,torb format). Then: open PRCloses #234, request Argus, ci-wait.[loop:torbscript] release implement: PR #481 (Closes #234), head
3507d567, two Conventional Commits, tools/check-commits.sh clean, Argus review requested. CI: all four checks queued ("Waiting to run", runner pool backlog) after ~35 min of waiting, nothing has run yet; nothing was run locally. Worker tokens: about 120k (continuation 2). Next: waiting (CI + review on PR 481)[loop:torbscript] claim implement by session:loop-torbscript-20261025-0542 until 2026-10-25T14:55:00Z (CI fix on PR 481 head
3507d567: pdisc stale on tools/registry/README.md, run 1785 job 20111; tools/registry test line 332 parse errorassert !moduleText..., run 1784 job 20146)[loop:torbscript] release implement: PR 481 head moved to
ce73eaf3(worker ended without a release comment; pdisc/registry test fixes folded into the two commits), CI is running on it; if red the next fix worker starts from the failing job logs, next: waiting (CI + review on PR 481)