Skip to main content

max / makenotwork

Strip off-platform media from creator markdown (block tracking pixels) Risk remediation (fuzz 2026-07-06, UX-Wiring axis). Creator markdown (blog/item/project prose) rendered via ammonia's default, which permits external http(s) <img>, so a creator could embed ![](https://tracker/px.png) and silently instrument their own audience — every viewer's IP/UA/referrer leaked to a third party, contrary to the no-tracking promise. Add docengine::restrict_media_hosts (additive; existing render_permissive unchanged, so mt is unaffected) that drops src/poster/srcset on img/video/audio/ source pointing off-platform, keeping relative + CDN-host media and all external text links. Wire it into render_creator_markdown before img_to_video. docengine lib test added; server + docengine-lib clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-06 22:25 UTC
Commit: f661b5faabf1f360954cdb71d9c2c77b8db2806b
Parent: 7e06f06
2 files changed, +53 insertions, -0 deletions
@@ -25,5 +25,9 @@ use crate::db::UserId;
25 25 pub fn render_creator_markdown(markdown: &str, user_id: UserId, cdn_base: &str) -> String {
26 26 let md = docengine::rewrite_media_paths(markdown, cdn_base, &user_id.to_string());
27 27 let html = docengine::render_permissive(&md);
28 + // Strip off-platform media loads (tracking pixels) while keeping external
29 + // text links — fuzz 2026-07-06 UX. Runs on the `<img>` output, before the
30 + // img->video conversion. Relative + CDN-host media stay.
31 + let html = docengine::restrict_media_hosts(&html, cdn_base);
28 32 docengine::img_to_video(&html)
29 33 }
@@ -78,6 +78,39 @@ pub fn sanitize_html(html: &str) -> String {
78 78 Renderer::sanitize_only().sanitize_html(html)
79 79 }
80 80
81 + /// Drop `src`/`poster`/`srcset` on media elements (`img`/`video`/`audio`/
82 + /// `source`) that point at an off-platform host, so creator markdown cannot load
83 + /// external images (tracking pixels: an external `<img>` leaks every viewer's
84 + /// IP/UA/referrer to an arbitrary third party — contrary to the no-tracking
85 + /// promise). Relative URLs and URLs under `allowed_host` are kept; external text
86 + /// links are untouched (only media loads matter for the beacon). Run this on the
87 + /// permissive-rendered HTML *before* `img_to_video`, since it works on `<img>`.
88 + /// Additive and opt-in — existing `render_permissive` behaviour is unchanged.
89 + pub fn restrict_media_hosts(html: &str, allowed_host: &str) -> String {
90 + let allowed = allowed_host.trim_end_matches('/').to_string();
91 + ammonia::Builder::default()
92 + .attribute_filter(move |element, attribute, value| {
93 + let is_media_src = matches!(
94 + (element, attribute),
95 + ("img", "src" | "srcset")
96 + | ("video", "src" | "poster")
97 + | ("source", "src" | "srcset")
98 + | ("audio", "src")
99 + );
100 + if is_media_src {
101 + let v = value.trim();
102 + let on_platform =
103 + !v.contains("://") || v.starts_with('/') || v.starts_with(&allowed);
104 + if !on_platform {
105 + return None; // drop the attribute → no external media load
106 + }
107 + }
108 + Some(std::borrow::Cow::Borrowed(value))
109 + })
110 + .clean(html)
111 + .to_string()
112 + }
113 +
81 114 #[cfg(test)]
82 115 mod top_level_tests {
83 116 use super::*;
@@ -87,6 +120,22 @@ mod top_level_tests {
87 120 // suite (the wrappers were untested at the function level).
88 121
89 122 #[test]
123 + fn restrict_media_hosts_drops_external_img_keeps_cdn_and_links() {
124 + let cdn = "https://cdn.makenot.work";
125 + let html = concat!(
126 + r#"<img src="https://tracker.evil/px.png?u=v">"#,
127 + r#"<img src="https://cdn.makenot.work/u/a.png">"#,
128 + r#"<img src="/rel/b.png">"#,
129 + r#"<a href="https://example.com/post">link</a>"#,
130 + );
131 + let out = restrict_media_hosts(html, cdn);
132 + assert!(!out.contains("tracker.evil"), "external img src must be dropped: {out}");
133 + assert!(out.contains("cdn.makenot.work/u/a.png"), "cdn img kept: {out}");
134 + assert!(out.contains("/rel/b.png"), "relative img kept: {out}");
135 + assert!(out.contains(r#"href="https://example.com/post""#), "external text link kept: {out}");
136 + }
137 +
138 + #[test]
90 139 fn render_permissive_emits_paragraph_markup() {
91 140 let out = render_permissive("Hello **world**.");
92 141 assert!(out.contains("<p>"), "expected paragraph: {out:?}");