What you'll be able to do
By the end of this page you should be able to explain a simple XSS risk, prefer safe DOM APIs for untrusted text, and state clearly that frontend checks are not enough for real security boundaries.
Previous post: clean code habits for juniors.
Who this is for
- People building forms and rendering user-generated text
- Beginners who copy
innerHTMLexamples from old tutorials - Anyone who thought "hiding an API key in JS" was fine
You can skip this if you already treat all user content as unsafe and use vetted libraries for rich HTML. Still skim the secrets note.
Hostile input (two scenes)
Scenario A — comment box. A user pastes <script>... or an image with an onerror handler into content you inject with innerHTML.
Scenario B — query string. You read ?name= from the URL and drop it into HTML. Attackers send victims a crafted link.
The rule: data you did not fully create is untrusted.
Prefer text APIs for plain text
// safer for plain text
el.textContent = userName;
// dangerous if userName can contain HTML
// el.innerHTML = userName;
Scenario A — display a username. textContent escapes the idea of "this is text."
Scenario B — you truly need rich HTML. Use a vetted sanitizer library and a content policy — not a homemade regex "cleaner."
Secrets do not belong in frontend code
Scenario A — API key in a .js file. Anyone can read it in DevTools.
Scenario B — "obscure" endpoint names. Obscurity is not authorization.
Private keys and privileged operations belong on a server you control, with auth checks.
Links and target="_blank"
<a href="https://example.com" target="_blank" rel="noopener noreferrer">Open</a>
Scenario A — user-supplied URLs. Validate allowed schemes (https:) so javascript: links do not run.
Scenario B — noopener reduces reverse-tabnabbing surprises on older patterns.
Client validation is UX; server validation is authority
Scenario A — disable a button when the form is incomplete — good UX.
Scenario B — attacker calls your API directly — your JS never runs. The server must enforce rules.
Reflective practice (checklist)
- Find one
innerHTMLin a practice project — is the string fully yours? - Search your frontend for
key,secret,token— anything private? - Confirm important rules also exist on any backend you use.
Mistakes I see a lot
1. Sanitizing with .replace('<', '') once and calling it safe.
2. Trusting localStorage tokens as if XSS could not steal them — reduce XSS first; understand token storage tradeoffs.
3. Copy-pasting admin UI examples into public pages.
What to try before the next post
- Replace one unsafe
innerHTMLwithtextContentin a demo. - Write the sentence: "My client check is for UX; my server check is for security."
- Skim one Learn Node security article when you bridge later.
Next in this series: testing basics for JS functions — assert behavior without a giant framework lecture.
Try this next outside the series
Engineering habits only stick when they show up in testing, security, and maintenance work.
- React Testing Library basics — turn clean habits into repeatable checks instead of guesses
- Node.js security basics — connect beginner discipline to safer real-world code