Skip to content

Trusted Types CSP support β€” innerHTML/insertAdjacentHTML sinks need a named policyΒ #616

@adrianbj

Description

@adrianbj

Hi @dg - apologies for the AI generated synopsis, but it would be nice to work towards support for the require-trusted-types-for 'script' CSP directive.

Tracy's runtime DOM rendering writes HTML strings to innerHTML / insertAdjacentHTML in several places.

On any host page enforcing Content-Security-Policy: require-trusted-types-for 'script', every one of these throws a TypeError: Failed to set the 'innerHTML' property … This document requires 'TrustedHTML' assignment., breaking the bar, panels, dumper, and bluescreen.

This blocks Tracy from being used on applications that adopt Trusted Types as part of their XSS mitigation strategy (a Google-recommended hardening, supported in Chromium-based browsers).

Affected sinks (Tracy 2.12)

src/Tracy/Bar/assets/bar.js:38 β€” elem.innerHTML = elem.dataset.tracyContent                                                                                  
src/Tracy/Bar/assets/bar.js:166 β€” doc.body.innerHTML = '<tracy-div…'                                                                                         
src/Tracy/Bar/assets/bar.js:401 β€” Debug.layer.innerHTML = content                                                                                            
src/Tracy/Bar/assets/bar.js:445 β€” Debug.layer.insertAdjacentHTML('beforeend', content.panels)                                                                
src/Tracy/Bar/assets/bar.js:447 β€” Debug.bar.elem.insertAdjacentHTML('beforeend', content.bar)                                                                
src/Tracy/BlueScreen/assets/bluescreen.js:62 β€” document.body.insertAdjacentHTML('beforeend', content)                                                        
src/Tracy/Dumper/assets/dumper.js:330 β€” el.innerHTML = content.html                                                                                          
src/Tracy/Debugger/DeferredContent.php:153 β€” emits el.innerHTML = '<customCssStr>'                                                                           
src/Tracy/Debugger/DeferredContent.php:157 β€” emits el.innerHTML = '<customBodyStr>'                                  

Reproduction

  1. Add to your application's HTTP response:
    Content-Security-Policy: require-trusted-types-for 'script'
  2. Trigger any of:
    - Open the Tracy bar (touches bar.js:401, 445, 447)
    - Expand a deferred dump (dumper.js:330)
    - Throw an exception with bluescreen visible (bluescreen.js:62)
    - Set Debugger::$customCssStr or Debugger::$customBodyStr (DeferredContent.php:153/157)
  3. Console shows TT enforcement errors and the Tracy UI fails to render.

Proposed fix: a single named policy

Tracy generates its own HTML server-side; the security boundary is Tracy's existing escaping, not the client-side sink. The TT-idiomatic solution is one named policy that all Tracy HTML flows through, so host applications can allow-list it explicitly:

  const tracyTT = (typeof trustedTypes !== 'undefined' && trustedTypes.createPolicy)                                                                           
      ? trustedTypes.createPolicy('tracy', { createHTML: s => s })                                                                                               
      : null;
                                                                                                                                                                 
  function setHTML(el, html) {                                                                                                                                 
      el.innerHTML = tracyTT ? tracyTT.createHTML(html) : html;                                                                                                  
  }                                                                                                                                                              
   
  function appendHTML(el, where, html) {                                                                                                                         
      el.insertAdjacentHTML(where, tracyTT ? tracyTT.createHTML(html) : html);                                                                                 
  }                                                                                                                                                              

Each affected sink becomes a one-line change. The PHP-emitted strings in DeferredContent.php would emit the same wrapper.

A passthrough createHTML: s => s is correct here: Tracy's HTML is built by Tracy from its own templating and is not interpolating attacker-controlled strings as HTML. The named policy is the audit boundary TT requires β€” it doesn't add escaping, it makes the trust explicit and listable in CSP.

Host-side requirement (documentation note)

Apps would need to allow the policy in their CSP, e.g.:

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types tracy default

(or use trusted-types …'allow-duplicates' if multiple libraries register policies with the same name).

This is a one-line README addition.

Alternatives considered

  • Rewriting all sinks to createElement / textContent / appendChild. Inverts Tracy's "PHP renders HTML, JS injects it" model β€” large refactor, fights the rendering architecture, and dataset.tracyContent round-tripping (line 38) inherently needs HTML parsing.
  • DOMParser.parseFromString(html, 'text/html'). Itself a TT sink, so doesn't help.
  • Range.createContextualFragment. Same problem.

Browser support

Trusted Types is a Chromium feature (Chrome 83+, Edge 83+). Firefox / Safari ignore the directive; the trustedTypes !== 'undefined' guard short-circuits there with no effect. So this change is purely additive for non-Chromium users.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions