Enhance JavaScript security! Explore key web threats like XSS and CSP, and implement secure coding practices to protect your web applications.
One unescaped string is all it takes. A comment box, a search field, a URL parameter, someone drops a <script> tag into it, your page renders it as HTML, and now their code is running in your users’ browsers, reading cookies and session tokens. That’s the whole game of web security in one sentence: the browser can’t tell your intent from an attacker’s. It just runs what you hand it. On Day 29 of our 30-day JavaScript journey, we’ll look at the threats you actually run into shipping JavaScript, how Cross-Site Scripting (XSS) and Content Security Policy (CSP) work, and the coding habits that keep the door shut. Fair warning up front: a lot of what gets called “security” in the browser is really convenience. The real controls live on the server.
Table of Contents
- Understanding Common Web Security Threats
- Cross-Site Scripting (XSS) and Content Security Policy (CSP)
- Implementing Secure Coding Practices
- Conclusion
Understanding Common Web Security Threats
Why This Matters
Your app touches things people care about: logins, personal details, payment info. That makes it a target. You don’t need to memorize a threat encyclopedia, but you do need a working feel for how the common attacks land, because the defenses follow directly from understanding the attack.
The Ones You’ll Actually Meet
A handful of attacks show up again and again in JavaScript-heavy apps:
- Cross-Site Scripting (XSS): An attacker gets their script to run inside your page. Once it runs, it can read cookies, grab session tokens, and act as the logged-in user. This is the one you’ll deal with most as a front-end dev, so it gets the bulk of today.
- SQL Injection: Untrusted input gets stitched into a database query and changes what the query does. It’s a server-side problem (use parameterized queries), but worth knowing because it’s the same root cause as XSS: mixing data with code.
- Cross-Site Request Forgery (CSRF): A user who’s logged into your site visits a malicious page, and that page quietly fires a request to your app using the user’s existing cookies. The fix is server-side: anti-CSRF tokens and the
SameSitecookie attribute. - Man-in-the-Middle (MITM): Someone sits between the user and your server and reads or rewrites the traffic. HTTPS is the answer, which is why it’s non-negotiable now.
Where JavaScript Trips You Up
A few of these hit JavaScript apps in particular:
- DOM-based XSS: Your client-side code reads something untrusted (a URL fragment, a query string) and writes it straight into the page. No server involved, the vulnerability is entirely in your JS.
- Stashing secrets in the browser: Tokens and other sensitive data in
localStorageare readable by any script that runs on the page, which means one XSS bug hands them over. Treat client storage as public. - Third-party code: Every dependency you pull in runs with your page’s full privileges. A compromised package is a compromised app.
Start With HTTPS
Before anything clever, serve the site over HTTPS. It encrypts traffic between the browser and your server so it can’t be read or tampered with in transit, and a valid certificate proves the client is talking to the real you. Certificates are free (Let’s Encrypt), so there’s no excuse to skip it.
Cross-Site Scripting (XSS) and Content Security Policy (CSP)
How XSS Works
XSS happens when an attacker’s script ends up executing in someone else’s browser session on your site. There are three flavors:
- Stored XSS: The payload is saved on your server (a comment, a profile field) and runs every time someone loads the page. The most dangerous kind, because it hits every visitor.
- Reflected XSS: The payload rides in on a request (usually a URL) and bounces straight back into the response. The attacker has to trick someone into clicking a crafted link.
- DOM-based XSS: Your own client-side code takes untrusted input and injects it into the DOM. The payload may never touch the server.
Here’s the bug in its simplest form:
Example of XSS Attack:
<!-- Vulnerable code -->
<div id="output"></div>
<script>
const userInput = location.search.split('=')[1];
document.getElementById('output').innerHTML = userInput;
</script>That innerHTML assignment is the problem. Whatever’s in the URL gets parsed as HTML, so a link like ?q=<img src=x onerror=alert(1)> runs code. The input was data, and you told the browser to treat it as markup.
Stopping It
The core rule: never let untrusted data reach an HTML sink as HTML. In order of what actually does the work:
- Escape at output: This is the primary defense. Encode data for the context it lands in so its characters can’t be read as code. In plain DOM work, that usually means writing to
textContentinstead ofinnerHTML. - Sanitize when you truly need HTML: Sometimes you do have to render user-supplied markup (a rich-text comment). Don’t hand-roll it. Run it through a maintained library like
DOMPurify, which strips the dangerous bits. - Validate input as a secondary layer: Rejecting obviously bad input is worth doing, but it’s backup, not the main defense, and it only counts when it runs on the server. Escaping is what keeps you safe.
The one-line fix for the example above:
Example of Safe Output Encoding:
document.getElementById('output').textContent = userInput;textContent treats the value as plain text. A <script> in there shows up as literal characters on the page instead of executing. Same idea applies to the framework you’re using: React’s JSX and similar templating escape by default, and the danger is the escape hatch (dangerouslySetInnerHTML and friends).
Content Security Policy (CSP)
CSP is your backstop. It’s an HTTP header that tells the browser which sources it’s allowed to load and run, so even if an XSS payload slips through, an inline or off-domain script gets blocked before it executes. Treat it as defense in depth, not a substitute for escaping. A weak or wide-open policy protects nothing.
Example of CSP Header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.comThis says: load resources only from your own origin by default, and scripts only from your origin plus one trusted CDN. Anything else, including inline <script> blocks, is refused. That last part is the point, and it’s also why retrofitting CSP onto an existing app takes work: you have to get your inline scripts out first.
Setting It Up
The right place for CSP is a response header from your server. If you can’t set headers, a meta tag works as a fallback, though it can’t cover everything a real header can:
Example of CSP Meta Tag:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://trusted.cdn.com">Start in report-only mode if your server supports it, watch what the policy would have blocked, then tighten and enforce. Rolling out a strict CSP cold on a live site is a good way to break your own page.
Implementing Secure Coding Practices
A Few Principles Worth Keeping
Most security wins come from a small set of habits, not heroics:
- Least privilege: Give code and users only the access they need, nothing spare.
- Defense in depth: Layer your controls so one mistake isn’t the whole breach. Escaping plus CSP is exactly this pattern.
- Fail securely: When something goes wrong, fall back to the locked state, not the open one, and don’t leak details in error messages.
Validate on the Server, Always
Check that incoming data matches what you expect, and reject the rest. Here’s a simple alphanumeric check:
Example of Input Validation:
function validateInput(input) {
const regex = /^[a-zA-Z0-9]+$/;
return regex.test(input);
}This rejects anything that isn’t letters or digits. One thing to be clear about: if this function runs in the browser, it’s a UX nicety and nothing more. An attacker skips your page entirely and posts straight to your endpoint, so any check that runs only client-side can be walked around. Client-side validation is not a security control. The same validation has to run on the server, where the attacker can’t reach it, before you trust the data.
Authentication and Sessions
Getting logins and sessions right protects everything behind them:
- Strong, unique passwords: Enforce them, and add multi-factor auth where the account is worth protecting.
- Never store passwords in the clear: Hash them with a slow, salted algorithm built for the job, like bcrypt or Argon2. Plain hashing (or, worse, plaintext) is not enough.
- Lock down session cookies: Mark them
HttpOnlyso JavaScript can’t read them,Secureso they only travel over HTTPS, andSameSiteto blunt CSRF. Rotate tokens on privilege changes.
Handling Sensitive Data
Passwords, personal info, payment details, all of it needs care:
- Encrypt it: In transit with HTTPS, and at rest in your database or storage.
- Keep it off the client: Don’t park sensitive data in
localStorageorsessionStorage. Any script on the page can read them, so one XSS bug is one data leak.
Third-Party Libraries
Your dependencies are part of your attack surface. Treat them like it:
- Keep them current: Updates carry security patches. Stale versions are known holes.
- Scan them: Run
npm audit(and a tool like Dependabot) to catch known vulnerabilities in what you’ve pulled in. - Pull in less: Every package is more code you’re trusting. Fewer dependencies, smaller surface.
Conclusion
Today we walked through the threats a JavaScript app actually faces, and we spent most of the time on XSS because it’s the one you’ll meet most: an attacker getting their script to run in your users’ browsers. We named the neighbors too, CSRF, SQL injection, MITM, so you know how they differ.
The through-line is simple. Escape output for its context (textContent over innerHTML, sanitize with DOMPurify when you genuinely need HTML), and back it with a Content Security Policy so a slip doesn’t become a breach. Validate input, but do it on the server, because anything client-side is UX, not defense. Keep secrets out of the browser, lock your cookies down with HttpOnly, Secure, and SameSite, and stay on top of your dependencies. None of it is exotic. It’s a handful of habits applied consistently.
Build those habits in from the start and security stops being a scramble before launch. It’s just how you write the code.
What’s Next?
That brings us to the end of the 30-day run. Tomorrow we put it all together and build a Portfolio Website to show off what you’ve made: planning it, structuring it, and deploying it to a live server. One capstone project, everything from the last month in one place. See you for the finale.


