JSON Security Best Practices: Protecting Your Data
JSON is everywhere — in APIs, configuration files, databases, and real-time data streams. But JSON's ubiquity makes it a prime target for security vulnerabilities if not handled correctly. This guide covers the most common JSON security risks, including prototype pollution injection, malicious payload attacks, XXE in JSON-parsing libraries, Denial of Service through deeply nested structures, and best practices for secure JSON parsing across all major languages. Use our JSON Validator and JSON Sanitizer to clean your payloads before processing.
1. Prototype Pollution (JavaScript)
Prototype pollution is the most dangerous JSON security vulnerability. An attacker injects __proto__ or constructor.prototype keys to pollute the global Object prototype:
// Malicious JSON payload
{
"__proto__": {
"isAdmin": true,
"bypassAuth": true
},
"constructor": {
"prototype": {
"polluted": "property"
}
}
}
// If parsed unsafely:
const data = JSON.parse(maliciousJson);
// All objects in the application now inherit isAdmin: true
// Prevention: Strip dangerous keys during parsing
const safe = JSON.parse(json, (key, value) => {
if (key === '__proto__' || key === 'constructor') {
return undefined;
}
return value;
});
2. Denial of Service via Deeply Nested Structures
Extremely deep nesting can crash parsers or cause stack overflow. An attack might send JSON with 10,000+ levels of nesting:
// Recursive bomb: [ [ [ [ ... ] ] ] ]
function buildBomb(depth) {
if (depth <= 0) return 1;
return [buildBomb(depth - 1)];
}
const bomb = JSON.stringify(buildBomb(20000));
// JSON.parse(bomb) may crash or consume all stack space
// Prevention: Set maximum nesting depth
function safeParse(json, maxDepth = 100) {
let depth = 0;
return JSON.parse(json, (key, value) => {
if (typeof value === 'object' && value !== null) {
depth++;
if (depth > maxDepth) {
throw new Error('JSON exceeds maximum nesting depth');
}
}
return value;
});
}
3. Billion Laughs Attack (JSON Equivalent)
Similar to XML's Billion Laughs attack, JSON can be crafted to expand exponentially through repeated references. While JSON itself has no entity expansion, parsing libraries with custom features can be vulnerable. Our JSON Minifier and JSON Formatter can help you inspect suspicious payloads for unusual patterns.
4. Sensitive Data in JSON
JSON responses often leak sensitive information through verbose error messages, excessive data in responses, and hardcoded secrets:
// Risky: exposing internal details
{
"error": "SQL ERROR: Column 'password' not found in 'users'",
"stack": "at Query.run (server.js:142)",
"query": "SELECT * FROM users WHERE id = 1"
}
// Safe: generic error response
{
"error": "Internal server error"
}
5. JSON Injection via eval()
Using eval() to parse JSON is extremely dangerous as it executes arbitrary JavaScript:
// DANGEROUS: Never do this
const data = eval('(' + userInput + ')');
// An attacker could send: '); process.exit(1); //
// Safe: Always use JSON.parse()
const data = JSON.parse(userInput);
6. Mass Assignment (Object Merging Vulnerabilities)
When merging user-supplied JSON into existing objects without validation, attackers can overwrite critical fields:
// Vulnerable merge pattern
const userRole = { role: 'user' };
const userInput = JSON.parse(req.body); // { role: 'admin' }
Object.assign(userRole, userInput); // userRole.role is now 'admin'
// Safe: Whitelist allowed fields
const ALLOWED_FIELDS = ['name', 'email', 'avatar'];
const safeUser = {};
Object.keys(userInput).forEach(key => {
if (ALLOWED_FIELDS.includes(key)) {
safeUser[key] = userInput[key];
}
});
Language-Specific Security Practices
| Language | Safe Parsing | What to Avoid |
|---|---|---|
| JavaScript | JSON.parse() with reviver to filter dangerous keys | eval(), direct Object.assign() with user input |
| Node.js | Use express.json() with size limits: app.use(express.json({limit: '100kb'})) | Parsing without size limits |
| Python | json.loads() with custom object_hook for filtering | eval(), yaml.load() on JSON data |
| Java | Jackson with DeserializationFeature.FAIL_ON_TRAILING_TOKENS | Default ObjectMapper without limits |
| Go | json.Decoder with DisallowUnknownFields, UseNumber | json.Unmarshal into interface{} |
Secure JSON Parsing by Language
JavaScript / Node.js
// Set up Express with security limits
const app = express();
app.use(express.json({
limit: '100kb',
verify: (req, _, buf) => {
// Validate JSON parity before parsing
try {
JSON.parse(buf.toString());
} catch (e) {
throw new Error('Invalid JSON');
}
}
}));
Python (Flask/FastAPI)
from flask import Flask, request, jsonify
import json
app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 # 100KB
def secure_loads(json_string):
def filter_hook(dct):
dct.pop('__proto__', None)
dct.pop('constructor', None)
return dct
return json.loads(json_string, object_hook=filter_hook)
@app.route('/api/data', methods=['POST'])
def receive_data():
data = secure_loads(request.data)
return jsonify({"received": True})
Security Checklist for JSON APIs
- Set maximum payload sizes (100KB for most APIs, more for file uploads)
- Set maximum nesting depth (100 levels is safe)
- Use
JSON.parse()instead ofeval() - Strip
__proto__andconstructorkeys from all parsed JSON - Validate all JSON inputs against a schema before processing
- Never return stack traces or internal error details in JSON responses
- Log all JSON parsing errors for monitoring and anomaly detection
- Use JSON Validator during development to test payloads
Next Steps
Validate and sanitize your JSON payloads with our JSON Sanitizer. Check your JSON files for security issues with JSON Validator. For API development, use JSON to TypeScript to generate type-safe interfaces with known field whitelists.