dataveil
Version:
A robust TypeScript library for masking sensitive data including card numbers, emails, passwords, phone numbers, and more.
151 lines (150 loc) • 5.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.maskJSON = maskJSON;
var maskCardNumber_1 = require("./maskCardNumber");
var maskEmail_1 = require("./maskEmail");
var maskPassword_1 = require("./maskPassword");
var maskPhoneNumber_1 = require("./maskPhoneNumber");
var maskUUID_1 = require("./maskUUID");
var maskSubstring_1 = require("./maskSubstring");
var utils_1 = require("../utils");
function maskJSON(json, fieldsToMask, options) {
if (options === void 0) { options = {}; }
if (!json || typeof json !== 'object') {
throw new Error('JSON must be a valid object');
}
if (fieldsToMask.length > 100) {
throw new Error('Too many fields to mask (maximum: 100)');
}
// Estimate object size to prevent memory exhaustion
var jsonString = JSON.stringify(json);
if (jsonString.length > 10 * 1024 * 1024) { // 10MB limit
throw new Error('JSON object too large (maximum: 10MB)');
}
var maskedJson = deepClone(json);
fieldsToMask.forEach(function (fieldConfig) {
var fieldPath;
var fieldType;
var fieldOptions;
if (typeof fieldConfig === 'string') {
fieldPath = sanitizeFieldPath(fieldConfig);
fieldType = inferFieldType(fieldPath);
fieldOptions = options;
}
else {
fieldPath = sanitizeFieldPath(fieldConfig.path);
fieldType = fieldConfig.type || inferFieldType(fieldPath);
fieldOptions = fieldConfig.options ? Object.assign({}, options, fieldConfig.options) : options;
}
var value = (0, utils_1.getNestedField)(maskedJson, fieldPath);
if (value && typeof value === 'string') {
var maskedValue = void 0;
try {
switch (fieldType) {
case 'card':
maskedValue = (0, maskCardNumber_1.maskCardNumber)(value, fieldOptions);
break;
case 'email':
maskedValue = (0, maskEmail_1.maskEmail)(value, fieldOptions);
break;
case 'phone':
maskedValue = (0, maskPhoneNumber_1.maskPhoneNumber)(value, fieldOptions);
break;
case 'uuid':
maskedValue = (0, maskUUID_1.maskUUID)(value, fieldOptions);
break;
case 'password':
maskedValue = (0, maskPassword_1.maskPassword)(value, fieldOptions);
break;
default:
maskedValue = (0, maskSubstring_1.maskSubstring)(value, value, fieldOptions);
}
(0, utils_1.setNestedField)(maskedJson, fieldPath, maskedValue);
}
catch (error) {
// If specific masking fails, fall back to generic masking
maskedValue = (0, maskSubstring_1.maskSubstring)(value, value, fieldOptions);
(0, utils_1.setNestedField)(maskedJson, fieldPath, maskedValue);
}
}
});
return maskedJson;
}
function deepClone(obj, depth, seen) {
if (depth === void 0) { depth = 0; }
if (seen === void 0) { seen = new WeakSet(); }
if (depth > 100) {
throw new Error('Maximum cloning depth exceeded - possible circular reference');
}
if (obj !== null && typeof obj === 'object') {
if (seen.has(obj)) {
throw new Error('Circular reference detected');
}
seen.add(obj);
}
if (obj === null || typeof obj !== 'object')
return obj;
if (obj instanceof Date)
return new Date(obj.getTime());
if (Array.isArray(obj))
return obj.map(function (item) { return deepClone(item, depth + 1, seen); });
var cloned = {};
for (var key in obj) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // Skip dangerous keys
}
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key], depth + 1, seen);
}
}
return cloned;
}
function sanitizeFieldPath(fieldPath) {
if (!fieldPath || typeof fieldPath !== 'string') {
throw new Error('Field path must be a non-empty string');
}
// Prevent dangerous paths
var dangerousPatterns = [
'__proto__',
'constructor',
'prototype',
'../',
'..\\',
'eval',
'function'
];
var lowerPath = fieldPath.toLowerCase();
for (var _i = 0, dangerousPatterns_1 = dangerousPatterns; _i < dangerousPatterns_1.length; _i++) {
var pattern = dangerousPatterns_1[_i];
if (lowerPath.includes(pattern)) {
throw new Error("Dangerous field path detected: ".concat(pattern));
}
}
// Limit path depth
var parts = fieldPath.split('.');
if (parts.length > 20) {
throw new Error('Field path depth exceeds maximum allowed (20 levels)');
}
// Validate each part
for (var _a = 0, parts_1 = parts; _a < parts_1.length; _a++) {
var part = parts_1[_a];
if (!part || !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(part)) {
throw new Error("Invalid field path component: ".concat(part));
}
}
return fieldPath;
}
function inferFieldType(fieldPath) {
var lowerPath = fieldPath.toLowerCase();
if (lowerPath.includes('card') || lowerPath.includes('credit'))
return 'card';
if (lowerPath.includes('email'))
return 'email';
if (lowerPath.includes('phone') || lowerPath.includes('mobile'))
return 'phone';
if (lowerPath.includes('uuid') || lowerPath.includes('id'))
return 'uuid';
if (lowerPath.includes('password') || lowerPath.includes('pwd'))
return 'password';
return 'custom';
}