Skip to main content

JSON Security

Learning Focus

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

CheckWhyHow
Never build JSON via string concatInjection riskUse json.dumps(), JSON.stringify()
Validate schema before processingReject malformed/malicious inputUse jsonschema, zod, ajv
Limit payload sizeDoS via huge payloadsSet Content-Length limits in web server
Reject __proto__ keysPrototype pollutionUse Object.create(null) or key sanitizer
Redact PII in logsData exposureImplement a log sanitizer
Use HTTPS for JSON APIsMan-in-the-middleTLS 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