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
- Add to your application's HTTP response:
Content-Security-Policy: require-trusted-types-for 'script'
- 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)
- 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
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)
Reproduction
Content-Security-Policy: require-trusted-types-for 'script'
- 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)
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:
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
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