Learn/Advanced Topics

JSON Security — Injection, Prototype Pollution & Sanitization

JSON seems harmless — it's just data. But unsanitized JSON input is the root cause of prototype pollution, mass assignment, XSS, and SQL injection. This guide shows you exactly how each attack works and how to prevent them.

Advanced~15 min read

Attack Surface Overview

JSON Attack Vectors

1. Prototype Pollution

The most dangerous JSON-specific vulnerability. An attacker sends a payload with __proto__ keys that modify the global Object prototype.

The Attack

Vulnerable codejavascript
1// Attacker sends this JSON body:
2const malicious = JSON.parse('{"__proto__": {"isAdmin": true}}');
3
4// Vulnerable merge function
5function merge(target, source) {
6 for (const key in source) {
7 if (typeof source[key] === 'object') {
8 target[key] = target[key] || {};
9 merge(target[key], source[key]);
10 } else {
11 target[key] = source[key];
12 }
13 }
14}
15
16const config = {};
17merge(config, malicious);
18
19// Now EVERY object in the app is "admin"
20const user = {};
21console.log(user.isAdmin); // true — pollution succeeded

The Fix

Safe merge with prototype guardjavascript
1function safeMerge(target, source) {
2 for (const key of Object.keys(source)) {
3 // Block prototype pollution vectors
4 if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
5 continue;
6 }
7 if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
8 target[key] = target[key] || Object.create(null);
9 safeMerge(target[key], source[key]);
10 } else {
11 target[key] = source[key];
12 }
13 }
14 return target;
15}
16
17// Or use Object.create(null) for prototype-free objects
18const safeObj = Object.create(null); // no __proto__ at all

Libraries Affected

lodash.merge, jQuery.extend, Hoek.merge, and many others had prototype pollution vulnerabilities. Always update dependencies and use audit tools.

2. Mass Assignment

When you directly spread or assign JSON request bodies into database models, an attacker can overwrite fields they shouldn't have access to.

Vulnerable: trusting all input fieldsjavascript
1// POST /api/users — attacker sends:
2// { "name": "Evil", "role": "admin", "verified": true }
3
4app.post('/api/users', async (req, res) => {
5 // BAD: trusting every field from the request
6 const user = await db.users.create(req.body);
7});
Fixed: allowlist patternjavascript
1app.post('/api/users', async (req, res) => {
2 // GOOD: only pick allowed fields
3 const { name, email } = req.body;
4 const user = await db.users.create({
5 name,
6 email,
7 role: 'user', // hardcoded default
8 verified: false, // never trust input
9 });
10});

3. XSS via JSON in HTML

Embedding JSON in HTML script tags or innerHTML without escaping allows script injection.

Vulnerable: unescaped JSON in HTMLjavascript
1// Server-side rendering danger
2const userData = { bio: '</script><script>alert("xss")</script>' };
3
4// BAD: raw embedding
5res.send(`<script>const user = ${JSON.stringify(userData)}</script>`);
6// Renders: </script><script>alert("xss")</script>
Fixed: escape dangerous charactersjavascript
1function safeJsonForHtml(data) {
2 return JSON.stringify(data)
3 .replace(/</g, '\u003c')
4 .replace(/>/g, '\u003e')
5 .replace(/&/g, '\u0026')
6 .replace(/'/g, '\u0027');
7}
8
9// Safe embedding
10res.send(`<script>const user = ${safeJsonForHtml(userData)}</script>`);

4. Never Use eval() for JSON

eval() is a critical vulnerabilityjavascript
1// NEVER DO THIS
2const data = eval('(' + jsonString + ')');
3// Attacker can inject: '); process.exit(); //
4// This runs arbitrary code on your server
5
6// ALWAYS DO THIS
7const data = JSON.parse(jsonString);
8// Only accepts valid JSON — no code execution

5. Schema Validation as Defense

JSON Schema validation is your strongest defense layer. It rejects unexpected fields, wrong types, and malicious patterns before they reach your business logic.

Schema-based defensejavascript
1import Ajv from 'ajv';
2const ajv = new Ajv({ allErrors: true, removeAdditional: true });
3
4const userSchema = {
5 type: 'object',
6 properties: {
7 name: { type: 'string', minLength: 1, maxLength: 100 },
8 email: { type: 'string', format: 'email' },
9 },
10 required: ['name', 'email'],
11 additionalProperties: false, // rejects __proto__, role, etc.
12};
13
14const validate = ajv.compile(userSchema);
15
16function validateInput(body) {
17 if (!validate(body)) {
18 return { valid: false, errors: validate.errors };
19 }
20 return { valid: true, data: body };
21}

Tip

additionalProperties: false combined with removeAdditional: true strips unknown fields and rejects __proto__ / constructor keys. Use our JSON Schema Validator to test your schemas.

Security Checklist

Never use eval() to parse JSON
Validate all input with JSON Schema
Set additionalProperties: false in schemas
Guard against __proto__ and constructor keys
Use allowlists for field extraction (not blocklists)
Escape JSON before embedding in HTML
Use parameterized queries — never concatenate JSON into SQL
Run npm audit / snyk regularly
Sanitize JSON strings before DOM insertion
Set Content-Type: application/json on API responses

Try It — Is This JSON Safe?

This JSON contains a prototype pollution attempt. Our validator will tell you it's valid JSON — but it's not safe to merge blindly.

Try It Yourself

Valid JSON — but extremely dangerous if merged naively

Frequently Asked Questions

Is JSON.parse() safe?
JSON.parse() itself is safe — it only creates data objects, not executable code. However, the data it produces must still be sanitized before being used in HTML, SQL queries, or shell commands.
What is prototype pollution?
Prototype pollution is an attack where a malicious JSON payload contains __proto__ or constructor.prototype keys that modify the base Object prototype, affecting all objects in the application.
How do I prevent JSON injection?
Always parse JSON with JSON.parse() (never eval()). Validate the parsed data against a schema. Sanitize before inserting into HTML, SQL, or templates. Use parameterized queries for databases.
Should I use eval() to parse JSON?
Never. eval() executes arbitrary JavaScript code. A malicious payload could run any code on your server or client. Always use JSON.parse() which only accepts valid JSON data.
How do I sanitize JSON before rendering in HTML?
Escape HTML entities in all string values: replace < > & " with their HTML entity equivalents. Or use a library like DOMPurify. Never use innerHTML with unsanitized JSON data.