In short
JavaScript security basics help you protect users. Learn what XSS is, why textContent is safer than innerHTML, why to avoid eval, and how to handle secrets and links safely. What is XSS (cross-site scripting)?
JavaScript security basics help you protect users. Learn what XSS is, why textContent is safer than innerHTML, why to avoid eval, and how to handle secrets and links safely.
What is XSS (cross-site scripting)?
XSS (cross-site scripting) is when an attacker gets their own code to run on your website, inside other people's browsers. It is one of the most common web security problems.
Imagine a comment section. A normal user writes "Great post!". An attacker writes a "comment" that contains HTML with a script hidden inside it. If your page puts that text into the page as HTML, the browser runs the attacker's code for every visitor. That code could steal login sessions, change the page, or send fake requests as the user.
The root cause is always the same: the page treats user data as code.
textContent vs innerHTML: which is safe?
innerHTML reads the string as HTML and builds elements from it. textContent puts the string in as plain text, so any tags appear as visible characters and never run.
<form id="commentForm">
<input id="commentInput" placeholder="Write a comment" required>
<button>Post</button>
</form>
<ul id="comments"></ul>
<script>
const form = document.querySelector('#commentForm');
const list = document.querySelector('#comments');
form.addEventListener('submit', (event) => {
event.preventDefault();
const text = document.querySelector('#commentInput').value;
// UNSAFE: list.innerHTML += `<li>${text}</li>`;
// SAFE: build the element and set its text
const li = document.createElement('li');
li.textContent = text;
list.append(li);
form.reset();
});
</script>
Try posting <img src=x onerror="alert('hacked')">. With the safe version, you see that text exactly as typed in the list. With the unsafe innerHTML version, the browser would create an image, fail to load it, and run the alert. A real attacker would run something much worse.
Common mistake: Thinking "the
<script>tag is blocked, soinnerHTMLis safe". Browsers do not run<script>tags inserted withinnerHTML, but attackers use event attributes likeonerrorinstead. Never put untrusted text intoinnerHTML.
When you need some HTML
Sometimes you want a template with structure. Use innerHTML only for your own fixed markup, and fill in user data with textContent.
const renderUserCard = (user) => {
const card = document.createElement('article');
card.className = 'user-card';
card.innerHTML = '<h3></h3><p class="bio"></p>'; // fixed markup, no user data
card.querySelector('h3').textContent = user.name;
card.querySelector('.bio').textContent = user.bio;
return card;
};
document.body.append(renderUserCard({ name: 'Ana', bio: '<b>Loves</b> JavaScript' }));
// You will see: Ana, and the bio shown literally as "<b>Loves</b> JavaScript"
If you truly must show user-written HTML (like a rich text editor), clean it with a trusted library such as DOMPurify first. The same danger applies to insertAdjacentHTML() and outerHTML.
Never use eval (and its friends)
eval(string) runs any string as JavaScript code. If any part of that string comes from a user, the URL, or a server, an attacker can run whatever they want. The same is true for new Function(string) and passing a string to setTimeout('code', 1000).
// Dangerous: turning text into code
// const result = eval(userInput);
// Safe alternatives
const data = JSON.parse('{"name":"Ana","age":28}'); // parse data, not code
console.log(data.name); // Output: Ana
const actions = {
greet: () => console.log('Hello!'),
bye: () => console.log('Goodbye!'),
};
const choice = 'greet'; // imagine this came from a button
actions[choice]?.(); // Output: Hello!
Use JSON.parse() for data and an object of functions instead of building code from strings. You almost never need eval.
Validate on the server and keep secrets out of front-end code
Front-end validation is not security
Form checks in JavaScript are for a nice user experience. They are not protection. Anyone can open DevTools, turn off your JavaScript, or send a request directly with other tools. The server must check every piece of data again: type, length, format and permissions.
Never put secrets in front-end code
Everything you send to the browser can be read: your JavaScript files, localStorage, network requests. So never put these in front-end code:
- Secret API keys (payment, email or AI service keys)
- Database passwords
- Admin passwords or "hidden" admin checks like
if (user === 'admin')
Instead, keep secret keys on your own server. Your front end calls your server, and your server calls the other service with the key. Some keys are designed to be public (like a map widget key that is locked to your domain); the service's documentation will say so.
Note: Hiding a key in a
.envfile does not help if a build tool copies it into your JavaScript bundle. If the browser can use it, users can read it.
Safe external links and Content Security Policy
When you open a link in a new tab with target="_blank", add rel="noopener noreferrer". noopener stops the new page from controlling your page through window.opener. Modern browsers already do this by default for target="_blank", but adding it is clear and safe. When you build links from user data, also check the address starts with https:// so nobody can sneak in a javascript: link.
const addSafeLink = (url, label) => {
const parsed = new URL(url, location.href);
if (!['https:', 'http:'].includes(parsed.protocol)) {
console.warn('Blocked unsafe link:', url);
return;
}
const a = document.createElement('a');
a.href = parsed.href;
a.textContent = label;
a.target = '_blank';
a.rel = 'noopener noreferrer';
document.body.append(a);
};
addSafeLink('https://developer.mozilla.org', 'MDN'); // added
addSafeLink('javascript:alert(1)', 'Click me'); // Output: Blocked unsafe link: javascript:alert(1)
Content Security Policy (CSP) is an extra safety net that your server sends as an HTTP header. It tells the browser which sources of scripts, styles and images are allowed, for example "only scripts from my own domain, and no inline scripts". If an attacker manages to inject a script anyway, the browser refuses to run it. CSP does not replace safe coding, but it greatly limits the damage of a mistake. You can learn how to write one on MDN's Content Security Policy guide.
Practice exercise
Build a small "guestbook": a form with a name and a message, and a list that shows each entry as "Name: message". Make it safe against XSS by using createElement and textContent only. Then test it by entering <b>bold</b> and <img src=x onerror=alert(1)> and check that they appear as plain text.
Hint: create a <strong> for the name and a text node or <span> for the message, and never use innerHTML with the input values.
Key takeaways
- XSS happens when user data is treated as code; it can steal sessions and change your page.
- Use
textContentand DOM methods for user data; useinnerHTMLonly for your own fixed markup. - Never use
eval,new Functionor string timers; useJSON.parseand objects of functions. - Front-end validation is for user experience; the server must validate everything again.
- Never put secrets in front-end code, use
rel="noopener noreferrer"for new-tab links, and add CSP as a safety net.