patterns: serve the session-explainer template as text/plain - #45
Merged
Conversation
…g it
Cloudflare Web Analytics appends its beacon to every text/html response at
the edge. That was landing inside the session-explainer skeleton, and the
skeleton is the one HTML response here that an agent copies rather than
renders — so the <script> ended up in the drop body the agent uploads.
Three things wrong with that, in order of how long they last:
- It becomes stored user content, not a transient edge addition. The tag
carries a pinned SRI hash and a versioned URL; when the beacon rotates,
every baked copy fails integrity and sits there dead.
- Drops built from the skeleton carry two beacons, one baked and one
injected per-response.
- It contradicts the pattern's own rule of zero script tags. An agent
running the conformance checklist against the skeleton it just fetched
would fail before writing a line.
A skeleton is source to copy, not a page to render, so text/plain is both
the fix and the more honest content type. The .md served beside it was
never affected, which is direct evidence from this zone that non-HTML
responses aren't rewritten.
Edge injection into user drops at /p/:slug/raw is left alone on purpose —
raised with the user, who chose to keep it. CLAUDE.md records that so a
future session doesn't read the Sentry rule ("we never inject into user
content") as covering it. That rule is about what the Worker adds.
The e2e now asserts the served bytes rather than the content type, and
fetches a second time with `Accept: text/html` because that request shape
is what triggers injection. The previous assertion checked the header and
checked the source file for scripts, so it passed while the served
response had one. Third time this session a check measured a proxy for
the thing instead of the thing.
Verified: tsc clean, 148 e2e checks pass (was 146).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🚀 Cloudflare previewURL: https://40b8e61b-htmlbin.utkarsh2012.workers.dev Built from commit
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Cloudflare Web Analytics appends its beacon to every
text/htmlresponse at the edge. Thesession-explainerskeleton was served astext/html, so it came back with a<script>in it.Found it by accident during the CLI release: an online
patterns initwrote a template 359 bytes larger than the repo source. The diff was one line.Only visible when the request signals it wants HTML, which is why it slipped past the existing checks.
Why it matters more for the skeleton than for a page
The skeleton is the one HTML response here that an agent copies rather than renders. So the injected script stops being a transient edge addition and becomes part of the drop body the agent uploads:
session-explainer.mdrequires zero<script>tags and its conformance checklist assertsscript.length === 0. An agent checking the skeleton it just fetched would fail before writing a line.The fix
getPatternAsset()serves templates astext/plain; charset=utf-8. Non-HTML responses aren't rewritten — the.mdserved from the same route was never affected, which is direct evidence from this zone.A skeleton is source to copy, not a page to render, so
text/plainis both the fix and the more honest content type.Not fixed, on purpose
Edge injection into user drops at
/p/:slug/rawis left alone. I raised it and the call was to keep it. CLAUDE.md now records that decision, because the existing Sentry note ("we never inject into user-published drop HTML") reads like it forbids this — that rule is about what the Worker adds, not what the edge does, and a future session shouldn't "fix" it.Test change
The old assertion checked the response content type, and separately checked the source file for scripts. Both passed while the served response had a script in it. Now it asserts the served bytes, and fetches a second time with
Accept: text/htmlsince that's the request shape that triggers injection:Verification
npx tsc --noEmitcleannpm run test:e2e— all 148 checks pass (was 146)Accept: text/htmlno longer changes the bytes🤖 Generated with Claude Code