Skip to content

fix(security)- refuse srcdoc in generic attribute writers and route d… - #57

Merged
hexplus merged 1 commit into
mainfrom
chore/57-hardening
Aug 27, 2026
Merged

fix(security)- refuse srcdoc in generic attribute writers and route d…#57
hexplus merged 1 commit into
mainfrom
chore/57-hardening

Conversation

@hexplus

@hexplus hexplus commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Description

Two attribute-security gaps found after the framework-wide hardening pass in #56.

srcdoc was treated as an inert attribute. The shared policy sorted attributes into event handlers, URLs, srcset, style, and "everything else, which setAttribute stores as inert text". That last claim is false for exactly one attribute: the browser decodes <iframe srcdoc> and parses it as a complete nested HTML document, and without a sandbox its scripts run with the embedding page's origin.

Escaping is not a weaker defence here — it is the wrong layer. srcdoc="&lt;script&gt;…" is correctly escaped as an attribute value and still becomes <script>… once parsed as a document, because the escaping is undone before the parse by design. So the generic writers refuse the attribute outright, and — as with on* — remove any pre-existing value rather than merely declining to add one. Taking the slot means governing it, so server markup or a third-party widget cannot leave a live document behind.

The rule lives in one place (isHtmlContentAttribute()) and is consulted by the tag factory, bindAttribute/bindDynamic, bindAttrs, enhance().attr(), svgElement, the html template, and all four SSR attribute serializers.

Three things deliberately not done: arbitrary HTML is not sanitized (a much larger problem, and doing it badly is worse than refusing); TrustedHTML does not unlock it, because that type is a compile-time brand — trustHTML() returns the same string through a cast — with no runtime identity; and sandbox does not unlock it either, since making security depend on an attribute later code can remove is not a guarantee.

Dynamic html attributes bypassed the shared policy. The tagged-template executor carried its own rules — srcset, then URL attributes, then write. That list was the shared policy minus style, so html`<div style=${untrusted}>` never reached the declaration-list sanitizer, contradicting the sanitizer's own documented invariant, and would have missed the new srcdoc rule too.

A duplicated policy is a policy that drifts, so there is now one. Both dynamic forms commit through the shared primitive — a single expression, and a mixed attribute after concatenating statics and expressions. Sanitizing the assembled string is what catches attacks split across the boundary, like href="java${x}:…". Fully static template text is unchanged: an attribute the developer typed into their own source stays developer-controlled, at the same trust level as hand-written markup.

Related Issue

Closes #

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Breaking: applications that set srcdoc through any generic SibuJS attribute API will find the attribute refused and removed. There is no opt-in — a trusted-document API would need a runtime-verifiable wrapper or browser Trusted Types, and is not part of this change.

Checklist

  • I have read CONTRIBUTING.md
  • My code builds without errors
  • I have tested my changes
  • I have updated documentation if needed

…ynamic html attributes through the shared policy
@hexplus
hexplus merged commit 72acb84 into main Aug 27, 2026
5 checks passed
@hexplus
hexplus deleted the chore/57-hardening branch August 28, 2026 10:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant