Commit 2df39252c0

2df39252c00872a83fccb7312a23606e6c4dbc6f

parent: abc7186744

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-29 02:11 UTC

Threat-Model: how pushed markup is rendered

Layout: unified · split

Threat-Model.org +24
@@ -71,6 +71,30 @@ webhook dialer re-checks at connect time so a DNS answer that changes
7171after validation still cannot reach private space. Redirects are never
7272followed.
7373
74* Rendering pushed markup
75
76Rendered markup is attacker-controlled: a README, a wiki page and a
77profile's about text are all whatever someone pushed or typed. The risk
78is not only what the output contains but what the *parser* is willing to
79go and fetch — the filesystem counterpart of the SSRF guard above.
80
81Org is rendered by go-org, whose default configuration resolves
82=#+INCLUDE:= and =#+SETUPFILE:= targets with =os.ReadFile=. Both are
83refused outright (=orgConfig()= in =internal/httpd=): the file is never
84opened and the keyword stays the inert text it already was, so the rest
85of the document renders normally. There is no safe subset to allow
86instead — an absolute path skips go-org's relative-path join, a relative
87one resolves against the daemon's working directory, and the content
88came from a git object rather than a checkout, so there is no directory
89to scope a read to. Markdown is goldmark, which has no include
90mechanism. go-org's parse warnings are discarded rather than logged, so
91pushed content cannot write to the server's log.
92
93Org output is then sanitized (bluemonday UGC policy) because go-org
94passes raw HTML through — export blocks and inline export snippets —
95while goldmark drops it and needs no pass. The policy admits chroma's
96short token classes and nothing else.
97
7498* Web responses
7599
76100Every response carries =Content-Security-Policy= (no scripts, no plugins,