Skip to content
← Back to thoughts
10 min read

XSS Is Still a Junior Trap

Reflected and stored XSS keep showing up in student projects — here is a practical checklist before you ship.

The "Junior Trap": Lessons Learned on Cross-Site Scripting

If you render user input into HTML without encoding, someone will eventually own a session. XSS is rarely just about making an alert(1) box pop up for a harmless laugh. In a real-world scenario, unescaped output gives an attacker direct access to session tokens, authentication cookies, and sensitive user data. Treating output encoding as an afterthought is the fastest way to hand over application control to whoever typed the payload.

1. Let Your Framework Do the Heavy Lifting

  • Prefer framework escaping by default: Modern frontend frameworks automatically sanitize and escape variables rendered in templates, neutralizing most injection vectors right out of the box.
  • Respect the escape hatches: Avoid using methods like dangerouslySetInnerHTML unless you completely control the sanitizer and understand the exact threat model. Bypassing framework guards opens the door wide open for exploitation.

2. Rich Text Requires Strict Rules

  • Use a maintained sanitizer and tight allowlist: Allowing users to format text means accepting markup, which inherently invites risk. Always rely on community-vetted libraries rather than writing custom logic.
  • Test with payloads, not vibes: Hoping your custom regex or basic string replacement is enough won't hold up against creative injection vectors. You have to actively bombard your implementation with edge-case payloads to verify it actually works.

3. Look Beyond Standard Form Fields

Junior security focus tends to linger exclusively on traditional input boxes and URL query parameters. However, modern web apps process untrusted data from hidden sinks everywhere:

  • location objects and URL fragments
  • postMessage APIs communicating across frames
  • Markdown renderers converting text into dynamic HTML

Any data path that transitions from raw input to an execution context needs to be locked down and treated as an injection surface until proven otherwise.