/**
* R2-002 regression: the OAuth callback reflects untrusted query params and
* upstream response bodies into HTML. Every untrusted value must be
* HTML-escaped (via escapeXml) before interpolation so a quote/`';
const descParam = '">
';
const res = await server.inject({
method: 'GET',
url: `/api/oauth/github/callback?error=${encodeURIComponent(errorParam)}&error_description=${encodeURIComponent(descParam)}`,
});
expect(res.statusCode).toBe(200);
const body = res.body;
// Raw payload markup must NOT appear verbatim.
expect(body).not.toContain('');
expect(body).not.toContain('
');
// Escaped forms must be present instead.
expect(body).toContain('<script>alert(1)</script>');
expect(body).toContain('<img src=x onerror=alert(2)>');
expect(body).toContain('">');
});
it('does not double-escape benign error descriptions', async () => {
server = createTestServer();
const res = await server.inject({
method: 'GET',
url: '/api/oauth/github/callback?error=access_denied&error_description=user%20declined',
});
expect(res.statusCode).toBe(200);
expect(res.body).toContain('access_denied: user declined');
});
});