- Shipped
- September 23, 2026 at 10:03 AM UTC
- Author
- Kamo
- Commit
- 617c224
MeetProviderController's GET /oauth/callback is sessionless and reflects the provider's error_description into an inline <script> block. The old jsString() only escaped backslash/quote/newline, so a value shaped like </script><script>...</script> ended the element early and ran attacker HTML on the public API gateway host (probed live and confirmed reflected verbatim, no auth required). jsString() now also escapes < > & / and the JS line terminators U+2028/U+2029 as \u escapes, because the HTML tokenizer looks for a bare </script while reading script content, before it has any notion of JS string quoting - keeping those bytes out of the output entirely is what stops it, not better JS quoting. The result page also now carries a per-response nonce on its <script> tag and a matching restrictive Content-Security-Policy header, as a second layer in case an escaper ever has a gap. postMessage still targets '*': this endpoint is the shared gateway host for every tenant, including orgs on their own custom domain, so there is no single opener origin to pin, and kamo-internal's own listener already treats delivery as best-effort for the same reason. Covered by **************** mutation-checked against the old escaping (reverting it turns the new </script>-boundary assertion red).
