Skip to content
Back to Learn
·7 min read

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

LanguageSafe ParsingWhat to Avoid
JavaScriptJSON.parse() with reviver to filter dangerous keyseval(), direct Object.assign() with user input
Node.jsUse express.json() with size limits: app.use(express.json({limit: '100kb'}))Parsing without size limits
Pythonjson.loads() with custom object_hook for filteringeval(), yaml.load() on JSON data
JavaJackson with DeserializationFeature.FAIL_ON_TRAILING_TOKENSDefault ObjectMapper without limits
Gojson.Decoder with DisallowUnknownFields, UseNumberjson.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 of eval()
  • Strip __proto__ and constructor keys 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.