Saltar al contenido
JSONCraft

Seguridad

Seguridad de entradas JSON: valida forma, limita trabajo y conserva números

Un ejemplo ejecutable con campos permitidos separa el parseo, la validación y la actualización segura del estado.

Parsear confirma la sintaxis. La aplicación aún debe decidir qué campos y tipos admite, cuánto trabajo pueden provocar y si es seguro copiarlos a su estado.

Un contrato de entrada ejecutable

Este ejemplo admite un perfil pequeño con exactamente name y age. El límite de 4 KiB y el intervalo de edad pertenecen a la demostración: no son límites universales de seguridad. Descarga profile-validation.mjs y ejecuta node profile-validation.mjs.

import assert from 'node:assert/strict';

export function readProfile(text) {
  // Local example only; limit received and decompressed bytes at the HTTP layer too.
  if (Buffer.byteLength(text, 'utf8') > 4096) throw new Error('Example input limit exceeded');
  const data = JSON.parse(text);
  if (!data || typeof data !== 'object' || Array.isArray(data)) throw new Error('Expected object');
  if (Object.keys(data).some(key => !['name', 'age'].includes(key))) throw new Error('Unknown field');
  if (typeof data.name !== 'string' || data.name.length < 1 || data.name.length > 80) throw new Error('Invalid name');
  if (!Number.isInteger(data.age) || data.age < 0 || data.age > 150) throw new Error('Invalid age');
  // Construct the permitted result instead of merging untrusted keys into application state.
  return { name: data.name, age: data.age };
}
assert.deepEqual(readProfile('{"name":"Ana","age":28}'), { name: 'Ana', age: 28 });
assert.throws(() => readProfile('{"name":"Ana","age":28,"__proto__":{"admin":true}}'));
assert.throws(() => readProfile('{"name":"Ana","age":"28"}'));
assert.equal({}.admin, undefined);
console.log('3 validation checks passed; Object.prototype unchanged');

Salida esperada: 3 validation checks passed; Object.prototype unchanged. Acepta el perfil válido, rechaza una propiedad __proto__ inesperada y rechaza una edad escrita como string. Construye el resultado con propiedades nombradas, sin mezclar recursivamente claves desconocidas.

El ejemplo valida un registro plano pequeño. No es un validador general, un algoritmo de mezcla profunda ni un servidor HTTP. Si tu contrato contiene objetos anidados, comprueba cada nivel. Puedes rechazar, eliminar o conservar campos adicionales de forma deliberada; no los transfieras sin control a opciones privilegiadas.

Limita recursos antes y después del parseo

La comprobación del tamaño del texto sucede cuando el string ya existe. En la entrada de red limita también los bytes recibidos, los descomprimidos, el tiempo de procesamiento y las peticiones simultáneas. No dependas sólo de Content-Length: puede faltar y no describe el contenido expandido.

Strings grandes, muchas propiedades, anidamiento profundo y expresiones regulares costosas consumen recursos de maneras distintas. El esquema ayuda después del parseo; no recupera la memoria que el parser ya ha reservado. Un temporizador en el mismo bucle de eventos no interrumpe un parseo o validador síncrono. Si necesitas poder detenerlo, usa un worker o proceso y limita el tamaño antes de iniciarlo.

No presupongas que una biblioteca contra contaminación del prototipo también impone una profundidad máxima. Comprueba sus opciones documentadas. secure-json-parse, por ejemplo, documenta el tratamiento de claves relacionadas con prototipos.

Los números necesitan un contrato entre lenguajes

const rounded = JSON.parse('{"id":9007199254740993}');
console.log(String(rounded.id)); // 9007199254740992
const exact = JSON.parse('{"id":"9007199254740993"}');
console.log(BigInt(exact.id).toString()); // 9007199254740993

Es el comportamiento de Number en JavaScript, no una exigencia de JSON para todos los lenguajes. El decodificador estándar de Python utiliza normalmente int para enteros JSON. Si un identificador atraviesa JavaScript, utiliza strings decimales y valida su sintaxis. Convertir un Number ya redondeado a string no recupera el valor original.

Esquemas, presentación y registros

Utiliza esquemas de confianza cuando sea posible. Los esquemas arbitrarios y las referencias remotas constituyen otra entrada que controlar: limita el trabajo y resuelve localmente las referencias aprobadas. Validar un esquema no autoriza una petición URL, insertar HTML o ejecutar una operación de base de datos. Aplica por separado restricciones de URL, APIs DOM de texto y consultas parametrizadas.

Prueba con datos sintéticos en un proceso local. Evita registrar tokens, campos personales o cuerpos completos. La guía de esquemas incluye casos válidos e inválidos; el formateador sólo comprueba sintaxis JSON.

Referencias: JSON en Python, procesos en Node y objetos en JSON Schema.

Herramientas relacionadas

Preguntas frecuentes

¿JSON.parse contamina Object.prototype por sí mismo?

Una clave __proto__ parseada es una propiedad propia de datos. Una mezcla o asignación posterior insegura puede convertirla en contaminación del prototipo.

¿Todos los parsers redondean enteros superiores a 2^53?

No. Number de JavaScript tiene ese límite de precisión; Python suele leer enteros JSON como int de precisión arbitraria. Define cómo viajan tus identificadores entre lenguajes.

Por