chore(sync): HTML-escape callback page inputs

This commit is contained in:
iamtoruk 2026-07-26 07:05:44 -07:00
parent 2c29352d80
commit d4f85ced71
2 changed files with 26 additions and 1 deletions

View file

@ -149,7 +149,14 @@ export interface CallbackResult {
* is served once from a throwaway localhost server, so it must not depend
* on network fonts, external CSS, or dashboard assets.
*/
export function renderCallbackPage(ok: boolean, title: string, message: string): string {
export function renderCallbackPage(ok: boolean, rawTitle: string, rawMessage: string): string {
// Current call sites pass literals, but escape anyway so a future caller
// interpolating IdP-influenced text (e.g. the callback `error` param) cannot
// turn this localhost page into an XSS sink.
const escapeHtml = (s: string) => s.replace(/[&<>"']/g, c =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]!)
const title = escapeHtml(rawTitle)
const message = escapeHtml(rawMessage)
const accent = ok ? '#1f8a5b' : '#c8541f' // --primary / --chart-5 (terracotta)
const mark = ok ? '&#10003;' : '&#10005;' // ✓ / ✕
return `<!doctype html>

View file

@ -10,6 +10,7 @@ import {
buildAuthUrl,
resolveScopes,
startCallbackServer,
renderCallbackPage,
CALLBACK_PORTS,
} from '../src/sync/auth.js'
@ -258,3 +259,20 @@ describe('syncConfig', () => {
expect(raw).not.toContain('password')
})
})
// ── Callback Page Escaping ────────────────────────────────────────────
describe('renderCallbackPage', () => {
it('escapes HTML metacharacters in title and message', () => {
const html = renderCallbackPage(false, '<script>alert(1)</script>', 'a & b "c" \'d\'')
expect(html).not.toContain('<script>alert(1)</script>')
expect(html).toContain('&lt;script&gt;alert(1)&lt;/script&gt;')
expect(html).toContain('a &amp; b &quot;c&quot; &#39;d&#39;')
})
it('leaves literal copy intact', () => {
const html = renderCallbackPage(true, 'Login successful', 'You can close this tab.')
expect(html).toContain('Login successful')
expect(html).toContain('You can close this tab.')
})
})