Files
WRNexusJS/packages/security/src/serialization.ts
T
2026-08-01 10:04:42 +05:30

67 lines
2.1 KiB
TypeScript

import { SecurityError } from "./errors.ts";
import { assertSafeObject, isDangerousObjectKey } from "./object.ts";
export interface SecureSerializeOptions {
maxDepth?: number;
maxKeys?: number;
maxBytes?: number;
redact?: RegExp | string[];
redactedValue?: string;
}
const DEFAULT_SECRET =
/(?:password|passwd|secret|token|api[-_]?key|private[-_]?key|otp|authorization|cookie)/i;
function shouldRedact(key: string, rule: SecureSerializeOptions["redact"]): boolean {
if (Array.isArray(rule)) return rule.includes(key);
return (rule ?? DEFAULT_SECRET).test(key);
}
export function secureJsonStringify(value: unknown, options: SecureSerializeOptions = {}): string {
assertSafeObject(value, { maxDepth: options.maxDepth, maxKeys: options.maxKeys });
const redactedValue = options.redactedValue ?? "[REDACTED]";
const json = JSON.stringify(value, function secureReplacer(key, current) {
if (isDangerousObjectKey(key)) {
throw new SecurityError(
"WRN-SEC-PROTOTYPE-POLLUTION",
`Dangerous key '${key}' is not allowed.`,
);
}
if (key && shouldRedact(key, options.redact)) return redactedValue;
if (typeof current === "bigint") return current.toString();
if (typeof current === "function" || typeof current === "symbol") {
throw new SecurityError(
"WRN-SEC-SERIALIZE-TYPE",
`Cannot serialize ${typeof current} values.`,
);
}
return current;
});
if (json === undefined) {
throw new SecurityError("WRN-SEC-SERIALIZE-EMPTY", "Value cannot be serialized.");
}
const safe = json
.replace(/&/g, "\\u0026")
.replace(/</g, "\\u003c")
.replace(/>/g, "\\u003e")
.replace(/\u2028/g, "\\u2028")
.replace(/\u2029/g, "\\u2029");
const bytes = new TextEncoder().encode(safe).byteLength;
const maxBytes = options.maxBytes ?? 256 * 1024;
if (bytes > maxBytes) {
throw new SecurityError(
"WRN-SEC-SERIALIZE-SIZE",
`Serialized payload exceeds ${maxBytes} bytes.`,
413,
);
}
return safe;
}
export function serializeForHtml(value: unknown, options: SecureSerializeOptions = {}): string {
return secureJsonStringify(value, options);
}