JSON Security
JSON parsing and deserialization introduce security risks that are easy to miss. This lesson covers the most critical vulnerabilities and how to prevent them.
JSON Injection
JSON injection occurs when user input is embedded directly into a JSON string without sanitization:
// ⚠️ DANGEROUS — never do this
const userInput = '", "admin": true, "x": "';
const json = `{"username": "${userInput}"}`;
// Produces: {"username": "", "admin": true, "x": ""}
// ✓ SAFE — always serialize via the language's JSON encoder
const json = JSON.stringify({ username: userInput });
// Produces: {"username": "\", \"admin\": true, \"x\": \""}
Rule: Never construct JSON via string concatenation or template literals with untrusted input.
Prototype Pollution (JavaScript)
Some JSON-parsing libraries and JSON.parse with __proto__ can pollute the Object prototype:
// ⚠️ Dangerous with vulnerable deep-merge libraries
const payload = JSON.parse('{"__proto__": {"isAdmin": true}}');
({}).isAdmin // true — prototype polluted!
// ✓ Mitigations
// 1. Validate JSON Schema before processing
// 2. Use Object.create(null) for untrusted maps
// 3. Sanitize keys: reject any key starting with __
function sanitize(obj) {
return JSON.parse(JSON.stringify(obj).replace(/__proto__/g, '__proto_sanitized__'));
}
Deeply Nested JSON (ReDoS / DoS)
Parsers can be exhausted by maliciously deep nesting:
{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":...}}}}}}}}
Mitigations:
# Set max depth in Python (no built-in — use a wrapper or third-party library)
import jsonref # or orjson with limits
# No built-in max depth in stdlib json module — validate size first
MAX_BYTES = 1_000_000 # 1 MB
raw = request.body
if len(raw) > MAX_BYTES:
raise ValueError("Payload too large")
data = json.loads(raw)
// Express.js: limit body size
app.use(express.json({ limit: '1mb' }));
Large Number Attacks
Extremely large numbers can cause integer overflow or float precision issues:
{ "value": 9999999999999999999999999999999999999999 }
Prevention: Use JSON Schema maximum / minimum constraints before processing.
Sensitive Data Exposure
Never log raw JSON payloads that may contain credentials, tokens, or PII:
# ⚠️ Dangerous — logs may contain passwords
logger.debug(f"Request body: {request.body}")
# ✓ Safe — redact sensitive fields before logging
import copy
safe = copy.deepcopy(data)
for key in ["password", "token", "secret", "creditCard"]:
if key in safe:
safe[key] = "***REDACTED***"
logger.debug(f"Request body: {json.dumps(safe)}")
Security Checklist
| Check | Why | How |
|---|---|---|
| Never build JSON via string concat | Injection risk | Use json.dumps(), JSON.stringify() |
| Validate schema before processing | Reject malformed/malicious input | Use jsonschema, zod, ajv |
| Limit payload size | DoS via huge payloads | Set Content-Length limits in web server |
Reject __proto__ keys | Prototype pollution | Use Object.create(null) or key sanitizer |
| Redact PII in logs | Data exposure | Implement a log sanitizer |
| Use HTTPS for JSON APIs | Man-in-the-middle | TLS everywhere — never plain HTTP for APIs |
Concept Map
Security Processing Pipeline
Untrusted JSON Input
1. Size Check (reject if > limit)
2. Schema Validation (reject if structure is wrong)
3. Key Sanitization (reject __proto__, constructor, etc.)
4. Business Logic (process clean data)
5. Redacted Logging (strip PII before logging)
6. Safe JSON Response (encode with serializer, never by hand)
What's Next
- Previous: Troubleshooting — Fix common parse and serialization errors.
- Course Overview — Return to the JSON curriculum root.