UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

1,374 lines (1,367 loc) 58.2 kB
'use strict'; var secureMemory = require('../../secure-memory.js'); var index = require('../../../utils/memory/index.js'); var sensitiveKeys = require('../encryption/sensitive-keys.js'); var cryptoHandler = require('../encryption/crypto-handler.js'); var metadataManager = require('../metadata/metadata-manager.js'); var eventManager = require('../events/event-manager.js'); var serializationHandler = require('../serialization/serialization-handler.js'); var idGenerator = require('../utils/id-generator.js'); var validation = require('../utils/validation.js'); var secureStringCore = require('../../secure-string/core/secure-string-core.js'); require('crypto'); require('../../secure-string/advanced/entropy-analyzer.js'); require('../../secure-string/advanced/quantum-safe.js'); require('../../secure-string/advanced/performance-monitor.js'); var types = require('../../../utils/memory/types.js'); /*************************************************************************** * FortifyJS - Secure Array Types * * This file contains type definitions for the SecureArray modular architecture * * @author Nehonix * * @license MIT * * Copyright (c) 2025 Nehonix. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. ***************************************************************************** */ /** * A secure object that can store sensitive data * T represents the initial type, but the object can be extended with additional keys */ class SecureObject { /** * Creates a new secure object */ constructor(initialData, options) { // Core data storage this.data = new Map(); this.secureBuffers = new Map(); // State management this._isDestroyed = false; this._isReadOnly = false; // Enhanced memory management this._memoryTracking = false; this._autoCleanup = false; this._createdAt = Date.now(); this._lastAccessed = Date.now(); this._id = idGenerator.IdGenerator.generate(); this._isReadOnly = false; // Start as writable // Initialize modular components this.sensitiveKeysManager = new sensitiveKeys.SensitiveKeysManager(); this.cryptoHandler = new cryptoHandler.CryptoHandler(this._id); this.metadataManager = new metadataManager.MetadataManager(); this.eventManager = new eventManager.EventManager(); this.serializationHandler = new serializationHandler.SerializationHandler(this.cryptoHandler, this.metadataManager); // Set encryption key if provided if (options?.encryptionKey) { this.cryptoHandler.setEncryptionKey(options.encryptionKey); } // Configure memory management with enhanced features this._memoryTracking = options?.enableMemoryTracking ?? true; // Enable by default this._autoCleanup = options?.autoCleanup ?? true; // Enable by default // Set memory limits if provided if (options?.maxMemory) { index.memoryManager.setLimits(options.maxMemory, options.gcThreshold || 0.8); } // Register with advanced memory manager if tracking is enabled if (this._memoryTracking) { index.memoryManager.registerObject(this, this._id); // Listen to memory events for proactive management index.memoryManager.on(types.MemoryEventType.MEMORY_PRESSURE, (event) => { if (event.data?.pressure > 0.8) { this.handleMemoryPressure(); } }); index.memoryManager.on(types.MemoryEventType.LEAK_DETECTED, (event) => { if (event.data?.leaks?.includes(this._id)) { console.warn(`Potential memory leak detected in SecureObject ${this._id}`); } }); } // Create memory pool for secure buffers if not exists this.initializeSecureBufferPool(); // Set initial data if (initialData) { this.setAll(initialData); } // Set read-only status after initial data is set this._isReadOnly = options?.readOnly ?? false; } /** * Creates a SecureObject from another SecureObject (deep copy) */ static from(other) { other.ensureNotDestroyed(); const copy = new SecureObject(); for (const key of other.keys()) { const value = other.get(key); copy.set(String(key), value); } return copy; } /** * Creates a read-only SecureObject */ static readOnly(data) { return new SecureObject(data, { readOnly: true }); } /** * Creates a read-only SecureObject (public usage) */ /** Permanently enable read-only mode (cannot be disabled). */ enableReadOnly() { this.ensureNotDestroyed(); this._isReadOnly = true; return this; } // ===== PROPERTY ACCESSORS ===== /** * Gets the unique ID of this SecureObject */ get id() { return this._id; } /** * Checks if the SecureObject is read-only */ get isReadOnly() { return this._isReadOnly; } /** * Checks if the SecureObject has been destroyed */ get isDestroyed() { return this._isDestroyed; } /** * Gets the number of stored values */ get size() { this.ensureNotDestroyed(); return this.data.size; } /** * Checks if the object is empty */ get isEmpty() { this.ensureNotDestroyed(); return this.data.size === 0; } // ===== VALIDATION METHODS ===== /** * Ensures the SecureObject hasn't been destroyed */ ensureNotDestroyed() { if (this._isDestroyed) { throw new Error("SecureObject has been destroyed and cannot be used"); } } /** * Ensures the SecureObject is not read-only for write operations */ ensureNotReadOnly() { if (this._isReadOnly) { throw new Error("SecureObject is read-only"); } } /** * Updates the last accessed timestamp for memory management */ updateLastAccessed() { this._lastAccessed = Date.now(); } // ===== MEMORY MANAGEMENT ===== /** * Initialize secure buffer pool for efficient memory reuse */ initializeSecureBufferPool() { if (!this.secureBufferPool) { try { this.secureBufferPool = index.memoryManager.getPool("secure-buffer-pool") || index.memoryManager.createPool({ name: "secure-buffer-pool", factory: () => new Uint8Array(1024), // 1KB buffers reset: (buffer) => { // Secure wipe before reuse this.secureWipe(buffer); }, capacity: 50, strategy: types.PoolStrategy.LRU, validator: (buffer) => buffer instanceof Uint8Array, }); } catch (error) { // Pool might already exist, try to get it this.secureBufferPool = index.memoryManager.getPool("secure-buffer-pool"); } } } /** * Handle memory pressure situations */ handleMemoryPressure() { if (this._autoCleanup) { // Clean up unused secure buffers this.forceGarbageCollection(); // Emit event for external handlers this.eventManager.emit("gc", undefined, { timestamp: Date.now(), objectId: this._id, action: "memory_pressure_cleanup", }); } } /** * Secure wipe of buffer memory */ secureWipe(buffer) { if (!buffer || buffer.length === 0) return; // Multiple-pass secure wipe const passes = [0x00, 0xff, 0xaa, 0x55, 0x00]; for (const pattern of passes) { buffer.fill(pattern); } // Final random pass if crypto is available if (typeof crypto !== "undefined" && crypto.getRandomValues) { crypto.getRandomValues(buffer); } buffer.fill(0x00); // Final zero pass } /** * Gets enhanced memory usage statistics for this SecureObject */ getMemoryUsage() { this.ensureNotDestroyed(); let allocatedMemory = 0; for (const buffer of this.secureBuffers.values()) { allocatedMemory += buffer.length(); } const now = Date.now(); const usage = { allocatedMemory, bufferCount: this.secureBuffers.size, dataSize: this.data.size, createdAt: this._createdAt, lastAccessed: this._lastAccessed, age: now - this._createdAt, formattedMemory: index.MemoryUtils.formatBytes(allocatedMemory), poolStats: this.secureBufferPool?.getStats(), }; return usage; } /** * Forces enhanced garbage collection for this SecureObject */ forceGarbageCollection() { this.ensureNotDestroyed(); if (this._memoryTracking) { const beforeUsage = this.getMemoryUsage(); // Clean up unused secure buffers with secure wipe for (const [key, buffer] of this.secureBuffers.entries()) { if (!this.data.has(key)) { // Secure wipe before destroying (get buffer data safely) try { const bufferData = buffer.getBuffer(); // Use correct method if (bufferData instanceof Uint8Array) { this.secureWipe(bufferData); } } catch (error) { // Buffer might already be destroyed, continue } buffer.destroy(); this.secureBuffers.delete(key); } } // Return unused buffers to pool if (this.secureBufferPool) ; // Trigger global GC with enhanced features const gcResult = index.memoryManager.forceGC(); const afterUsage = this.getMemoryUsage(); const freedMemory = beforeUsage.allocatedMemory - afterUsage.allocatedMemory; this.eventManager.emit("gc", undefined, { timestamp: Date.now(), bufferCount: this.secureBuffers.size, freedMemory, gcDuration: gcResult.duration, gcSuccess: gcResult.success, beforeUsage: beforeUsage.formattedMemory, afterUsage: afterUsage.formattedMemory, }); } } /** * Enables memory tracking for this SecureObject */ enableMemoryTracking() { this.ensureNotDestroyed(); if (!this._memoryTracking) { this._memoryTracking = true; index.memoryManager.registerObject(this, this._id); } return this; } /** * Disables memory tracking for this SecureObject */ disableMemoryTracking() { this.ensureNotDestroyed(); if (this._memoryTracking) { this._memoryTracking = false; index.memoryManager.removeReference(this._id); } return this; } // ===== SENSITIVE KEYS MANAGEMENT ===== /** * Adds keys to the sensitive keys list */ addSensitiveKeys(...keys) { this.ensureNotDestroyed(); validation.ValidationUtils.validateKeys(keys); this.sensitiveKeysManager.add(...keys); return this; } /** * Removes keys from the sensitive keys list */ removeSensitiveKeys(...keys) { this.ensureNotDestroyed(); validation.ValidationUtils.validateKeys(keys); this.sensitiveKeysManager.remove(...keys); return this; } /** * Sets the complete list of sensitive keys (replaces existing list) */ setSensitiveKeys(keys) { this.ensureNotDestroyed(); validation.ValidationUtils.validateKeys(keys); this.sensitiveKeysManager.set(keys); return this; } /** * Gets the current list of sensitive keys */ getSensitiveKeys() { this.ensureNotDestroyed(); return this.sensitiveKeysManager.getAll(); } /** * Checks if a key is marked as sensitive */ isSensitiveKey(key) { validation.ValidationUtils.validateKey(key); return this.sensitiveKeysManager.isSensitive(key); } /** * Clears all sensitive keys */ clearSensitiveKeys() { this.ensureNotDestroyed(); this.sensitiveKeysManager.clear(); return this; } /** * Resets sensitive keys to default values */ resetToDefaultSensitiveKeys() { this.ensureNotDestroyed(); this.sensitiveKeysManager.resetToDefault(); return this; } /** * Gets the default sensitive keys that are automatically initialized */ static get getDefaultSensitiveKeys() { return sensitiveKeys.SensitiveKeysManager.getDefaultKeys(); } /** * Adds custom regex patterns for sensitive key detection * @param patterns - Regex patterns or strings to match sensitive keys */ addSensitivePatterns(...patterns) { this.ensureNotDestroyed(); this.sensitiveKeysManager.addCustomPatterns(...patterns); return this; } /** * Removes custom sensitive patterns */ removeSensitivePatterns(...patterns) { this.ensureNotDestroyed(); this.sensitiveKeysManager.removeCustomPatterns(...patterns); return this; } /** * Clears all custom sensitive patterns */ clearSensitivePatterns() { this.ensureNotDestroyed(); this.sensitiveKeysManager.clearCustomPatterns(); return this; } /** * Gets all custom sensitive patterns */ getSensitivePatterns() { this.ensureNotDestroyed(); return this.sensitiveKeysManager.getCustomPatterns(); } // ===== ENCRYPTION MANAGEMENT ===== /** * Sets the encryption key for sensitive data encryption */ setEncryptionKey(key = null) { this.ensureNotDestroyed(); validation.ValidationUtils.validateEncryptionKey(key); this.cryptoHandler.setEncryptionKey(key); return this; } /** * Gets the current encryption key */ get getEncryptionKey() { return this.cryptoHandler.getEncryptionKey(); } /** * Decrypts a value using the encryption key */ decryptValue(encryptedValue) { this.ensureNotDestroyed(); return this.cryptoHandler.decryptValue(encryptedValue); } /** * Decrypts all encrypted values in an object */ decryptObject(obj) { this.ensureNotDestroyed(); return this.cryptoHandler.decryptObject(obj); } /** * Encrypts all values in the object using AES-256-CTR-HMAC encryption * with proper memory management and atomic operations */ encryptAll() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); // Check if encryption key is set const encryptionStatus = this.cryptoHandler.getEncryptionStatus(); if (!encryptionStatus.hasEncryptionKey) { throw new Error("Encryption key must be set before calling encryptAll()"); } // Prepare temporary map for atomic operation const encryptedEntries = new Map(); const originalMetadata = new Map(); const keysToProcess = []; try { // First pass: encrypt all values into temporary storage for (const [key, value] of this.data.entries()) { if (value !== undefined) { // Skip already encrypted values to avoid double encryption if (typeof value === "string" && this.cryptoHandler.isEncrypted(value)) { continue; } // Store original metadata for rollback if (this.metadataManager.has(key)) { originalMetadata.set(key, this.metadataManager.get(key)); } // Get the actual value to encrypt let valueToEncrypt = value; // If it's a SecureBuffer, convert it back to its original form if (value instanceof secureMemory.SecureBuffer) { const metadata = this.metadataManager.get(key); if (metadata?.type === "string") { valueToEncrypt = new TextDecoder().decode(value.getBuffer()); } else if (metadata?.type === "Uint8Array") { valueToEncrypt = new Uint8Array(value.getBuffer()); } else { valueToEncrypt = new TextDecoder().decode(value.getBuffer()); } } // Encrypt the value const encryptedValue = this.cryptoHandler.encryptValue(valueToEncrypt); encryptedEntries.set(key, encryptedValue); keysToProcess.push(key); } } // Second pass: atomically commit all changes for (const key of keysToProcess) { const encryptedValue = encryptedEntries.get(key); const originalValue = this.data.get(key); // Clean up any existing SecureBuffer for this key this.cleanupKey(key); // Store encrypted value this.data.set(key, encryptedValue); // Update metadata with correct type information // Store original type in the type field using special format const originalType = typeof originalValue; this.metadataManager.update(key, `encrypted:${originalType}`, true); } } catch (error) { // Rollback: restore original state on any failure for (const key of keysToProcess) { if (originalMetadata.has(key)) { // Restore original metadata const original = originalMetadata.get(key); this.metadataManager.update(key, original.type, original.isSecure); } } throw new Error(`Encryption failed: ${error.message}`); } this.updateLastAccessed(); this.eventManager.emit("set", "encrypt_all", `${keysToProcess.length}_values_encrypted`); return this; } /** * Gets the raw encrypted data without decryption (for verification) */ getRawEncryptedData() { this.ensureNotDestroyed(); return new Map(this.data); } /** * Gets a specific key's raw encrypted form (for verification) */ getRawEncryptedValue(key) { this.ensureNotDestroyed(); const stringKey = validation.ValidationUtils.sanitizeKey(key); return this.data.get(stringKey); } /** * Gets encryption status from the crypto handler */ getEncryptionStatus() { return this.cryptoHandler.getEncryptionStatus(); } // ===== EVENT MANAGEMENT ===== /** * Adds an event listener */ addEventListener(event, listener) { validation.ValidationUtils.validateEventType(event); validation.ValidationUtils.validateEventListener(listener); this.eventManager.addEventListener(event, listener); } /** * Removes an event listener */ removeEventListener(event, listener) { validation.ValidationUtils.validateEventType(event); validation.ValidationUtils.validateEventListener(listener); this.eventManager.removeEventListener(event, listener); } /** * Creates a one-time event listener */ once(event, listener) { validation.ValidationUtils.validateEventType(event); validation.ValidationUtils.validateEventListener(listener); this.eventManager.once(event, listener); } /** * Waits for a specific event to be emitted */ waitFor(event, timeout) { validation.ValidationUtils.validateEventType(event); if (timeout !== undefined) { validation.ValidationUtils.validateTimeout(timeout); } return this.eventManager.waitFor(event, timeout); } // ===== CORE DATA OPERATIONS ===== /** * Sets a value - allows both existing keys and new dynamic keys */ set(key, value) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); this.updateLastAccessed(); const stringKey = validation.ValidationUtils.sanitizeKey(key); validation.ValidationUtils.isValidSecureValue(value); // Clean up any existing secure buffer for this key this.cleanupKey(stringKey); // Handle different types of values if (value && typeof value === "object" && value.constructor.name === "Uint8Array") { // Store Uint8Array in a secure buffer const secureBuffer = secureMemory.SecureBuffer.from(value); this.secureBuffers.set(stringKey, secureBuffer); this.data.set(stringKey, secureBuffer); this.metadataManager.update(stringKey, "Uint8Array", true); } else if (typeof value === "string") { // Store strings in secure buffers const secureBuffer = secureMemory.SecureBuffer.from(value); this.secureBuffers.set(stringKey, secureBuffer); this.data.set(stringKey, secureBuffer); this.metadataManager.update(stringKey, "string", true); } else if (validation.ValidationUtils.isSecureString(value)) { // Store SecureString reference this.data.set(stringKey, value); this.metadataManager.update(stringKey, "SecureString", true); } else if (validation.ValidationUtils.isSecureObject(value)) { // Store SecureObject reference this.data.set(stringKey, value); this.metadataManager.update(stringKey, "SecureObject", true); } else { // Store other values directly (numbers, booleans, null, undefined) this.data.set(stringKey, value); this.metadataManager.update(stringKey, typeof value, false); } this.eventManager.emit("set", stringKey, value); return this; } /** * Sets multiple values at once */ setAll(values) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); for (const key in values) { if (Object.prototype.hasOwnProperty.call(values, key)) { this.set(String(key), values[key]); } } return this; } /** * Gets a value with automatic decryption */ get(key) { this.ensureNotDestroyed(); this.updateLastAccessed(); const stringKey = validation.ValidationUtils.sanitizeKey(key); const value = this.data.get(stringKey); // Update access metadata if (this.metadataManager.has(stringKey)) { const metadata = this.metadataManager.get(stringKey); this.metadataManager.update(stringKey, metadata.type, metadata.isSecure); } // Check if value is encrypted (starts with [ENCRYPTED:) if (typeof value === "string" && this.cryptoHandler.isEncrypted(value)) { try { // Decrypt the value automatically const decryptedValue = this.cryptoHandler.decryptValue(value); this.eventManager.emit("get", stringKey, decryptedValue); return decryptedValue; } catch (error) { console.error(`Failed to decrypt value for key ${stringKey}:`, error); // Return the encrypted value if decryption fails this.eventManager.emit("get", stringKey, value); return value; } } if (value instanceof secureMemory.SecureBuffer) { // Convert SecureBuffer back to original type based on metadata const buffer = value.getBuffer(); const metadata = this.metadataManager.get(stringKey); if (metadata?.type === "Uint8Array" || metadata?.type === "encrypted:Uint8Array") { // Return as Uint8Array for binary data const result = new Uint8Array(buffer); this.eventManager.emit("get", stringKey, result); return result; } else { // Return as string for text data const result = new TextDecoder().decode(buffer); this.eventManager.emit("get", stringKey, result); return result; } } this.eventManager.emit("get", stringKey, value); return value; } /** * Gets a value safely, returning undefined if key doesn't exist */ getSafe(key) { try { return this.has(key) ? this.get(key) : undefined; } catch { return undefined; } } /** * Gets a value with a default fallback */ getWithDefault(key, defaultValue) { return this.has(key) ? this.get(key) : defaultValue; } /** * Checks if a key exists */ has(key) { this.ensureNotDestroyed(); const stringKey = validation.ValidationUtils.sanitizeKey(key); return this.data.has(stringKey); } /** * Deletes a key - allows both existing keys and dynamic keys */ delete(key) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); const stringKey = validation.ValidationUtils.sanitizeKey(key); if (!this.data.has(stringKey)) { return false; } // Clean up any secure buffer this.cleanupKey(stringKey); const deleted = this.data.delete(stringKey); this.metadataManager.delete(stringKey); this.eventManager.emit("delete", stringKey); return deleted; } /** * Cleans up resources associated with a key */ cleanupKey(key) { if (this.secureBuffers.has(key)) { this.secureBuffers.get(key)?.destroy(); this.secureBuffers.delete(key); } // Note: We don't destroy SecureString or SecureObject instances // as they might be used elsewhere } /** * Clears all data */ clear() { this.ensureNotDestroyed(); this.ensureNotReadOnly(); // Destroy all secure buffers for (const buffer of this.secureBuffers.values()) { buffer.destroy(); } this.secureBuffers.clear(); this.data.clear(); this.metadataManager.clear(); this.eventManager.emit("clear"); } // ===== ITERATION AND COLLECTION METHODS ===== /** * Gets all keys */ keys() { this.ensureNotDestroyed(); return Array.from(this.data.keys()); } /** * Gets all values */ values() { this.ensureNotDestroyed(); return this.keys().map((key) => this.get(key)); } /** * Gets all entries as [key, value] pairs */ entries() { this.ensureNotDestroyed(); return this.keys().map((key) => [key, this.get(key)]); } /** * Iterates over each key-value pair */ forEach(callback) { this.ensureNotDestroyed(); validation.ValidationUtils.validateCallback(callback, "forEach callback"); for (const key of this.keys()) { callback(this.get(key), key, this); } } /** * Maps over values and returns a new array */ map(callback) { this.ensureNotDestroyed(); validation.ValidationUtils.validateMapper(callback); return this.keys().map((key) => callback(this.get(key), key, this)); } /** * Filters entries based on a predicate function (like Array.filter) * Returns a new SecureObject with only the entries that match the condition */ filter(predicate) { this.ensureNotDestroyed(); validation.ValidationUtils.validatePredicate(predicate); const filtered = new SecureObject(); for (const key of this.keys()) { const value = this.get(key); if (predicate(value, key, this)) { filtered.set(String(key), value); } } // Emit event with filter details this.eventManager.emit("filtered", undefined, { operation: "filter", resultSize: filtered.size, originalSize: this.size, }); return filtered; } /** * Filters entries by specific key names (type-safe for known keys) * Returns a new SecureObject with only the specified keys * * @example * const user = createSecureObject({ name: "John", password: "secret", age: 30 }); * const credentials = user.filterByKeys("name", "password"); */ filterByKeys(...keys) { this.ensureNotDestroyed(); const filtered = new SecureObject(); for (const key of keys) { if (this.has(key)) { const stringKey = String(key); filtered.set(stringKey, this.get(key)); } } // Copy sensitive keys that are included in the filter const relevantSensitiveKeys = this.getSensitiveKeys().filter((k) => keys.includes(k)); if (relevantSensitiveKeys.length > 0) { filtered.setSensitiveKeys(relevantSensitiveKeys); } this.eventManager.emit("filtered", undefined, { operation: "filterByKeys", keys: keys.map((k) => String(k)), resultSize: filtered.size, }); return filtered; } /** * Filters entries by value type using a type guard function * Returns a new SecureObject with only values of the specified type * * @example * const data = createSecureObject({ name: "John", age: 30, active: true }); * const strings = data.filterByType((v): v is string => typeof v === "string"); */ filterByType(typeGuard) { this.ensureNotDestroyed(); const filtered = new SecureObject(); for (const key of this.keys()) { const value = this.get(key); if (typeGuard(value)) { const stringKey = String(key); filtered.set(stringKey, value); } } this.eventManager.emit("filtered", undefined, { operation: "filterByType", resultSize: filtered.size, }); return filtered; } /** * Filters entries to only include sensitive keys * Returns a new SecureObject with only sensitive data * * @example * const user = createSecureObject({ name: "John", password: "secret", age: 30 }); * user.addSensitiveKeys("password"); * const sensitiveData = user.filterSensitive(); // Only contains password */ filterSensitive() { this.ensureNotDestroyed(); const filtered = new SecureObject(); const sensitiveKeys = this.getSensitiveKeys(); for (const key of this.keys()) { const stringKey = String(key); if (sensitiveKeys.includes(stringKey)) { filtered.set(stringKey, this.get(key)); } } // Copy all sensitive keys to the filtered object filtered.setSensitiveKeys([...sensitiveKeys]); this.eventManager.emit("filtered", undefined, { operation: "filterSensitive", resultSize: filtered.size, }); return filtered; } /** * Filters entries to exclude sensitive keys * Returns a new SecureObject with only non-sensitive data * * @example * const user = createSecureObject({ name: "John", password: "secret", age: 30 }); * user.addSensitiveKeys("password"); * const publicData = user.filterNonSensitive(); // Contains name and age */ filterNonSensitive(options) { this.ensureNotDestroyed(); // Default to non-strict mode (false) - only exact matches const strictMode = options?.strictMode === true; const filtered = new SecureObject(); for (const key of this.keys()) { const stringKey = String(key); // Use the enhanced sensitive key detection with strictMode if (!this.sensitiveKeysManager.isSensitive(stringKey, strictMode)) { const value = this.get(key); // If the value is a nested object, process it with the same strictMode if (value && typeof value === "object" && value !== null && !Array.isArray(value)) { // Process nested object with the same strict mode const processedValue = this.processNestedObjectForFiltering(value, strictMode); filtered.set(stringKey, processedValue); } else { filtered.set(stringKey, value); } } } this.eventManager.emit("filtered", undefined, { operation: "filterNonSensitive", resultSize: filtered.size, }); return filtered; } /** * Processes nested objects for filtering with the same strict mode */ processNestedObjectForFiltering(obj, strictMode) { if (Array.isArray(obj)) { // Handle arrays return obj.map((item) => typeof item === "object" && item !== null ? this.processNestedObjectForFiltering(item, strictMode) : item); } else if (typeof obj === "object" && obj !== null) { // Handle objects const result = {}; for (const [key, value] of Object.entries(obj)) { if (!this.sensitiveKeysManager.isSensitive(key, strictMode)) { if (typeof value === "object" && value !== null) { // Recursively process nested objects/arrays result[key] = this.processNestedObjectForFiltering(value, strictMode); } else { result[key] = value; } } // If key is sensitive, skip it (don't add to result) } return result; } return obj; } // ===== METADATA OPERATIONS ===== /** * Gets metadata for a specific key */ getMetadata(key) { this.ensureNotDestroyed(); const stringKey = validation.ValidationUtils.sanitizeKey(key); return this.metadataManager.get(stringKey); } /** * Gets metadata for all keys */ getAllMetadata() { this.ensureNotDestroyed(); return this.metadataManager.getAll(); } // ===== SERIALIZATION METHODS ===== /** * Converts to a regular object with security-focused serialization * * BEHAVIOR: This is the security-focused method that handles sensitive key filtering. * Use this method when you need controlled access to data with security considerations. * For simple object conversion without filtering, use toObject(). * * @example * const user = fObject({ id: "1", email: "test@test.com", password: "test123", isVerified: true, userName: "test", firstName: "test", lastName: "test", bio: "test", }); const getAllResult = user.getAll(); console.log("getAllResult.email:", getAllResult.email); console.log("getAllResult.password:", getAllResult.password); console.log("Has password?", "password" in getAllResult); // Purpose: Security-conscious data access // Behavior: Filters out sensitive keys by default // Result: password: undefined (filtered for security) // With encryptSensitive: true: ✔ password: "[ENCRYPTED:...]" (encrypted but included) */ getAll(options = {}) { this.ensureNotDestroyed(); validation.ValidationUtils.validateSerializationOptions(options); const sensitiveKeys = new Set(this.sensitiveKeysManager.getAll()); return this.serializationHandler.toObject(this.data, sensitiveKeys, options); } /** * Gets the full object as a regular JavaScript object * * BEHAVIOR: Returns ALL data including sensitive keys (like standard JS object conversion). * This method does NOT filter sensitive keys by default - use getAll() for security-focused serialization. * * @example * const user = fObject({ id: "1", email: "test@test.com", password: "test123", isVerified: true, userName: "test", firstName: "test", lastName: "test", bio: "test", }); const toObjectResult = user.toObject(); console.log("toObjectResult.email:", toObjectResult.email); console.log("toObjectResult.password:", toObjectResult.password); console.log("Has password?", "password" in toObjectResult); // Purpose: Standard JavaScript object conversion // Behavior: Returns ALL data including sensitive keys (like password) // Result: ✔ password: "test123" (included) Sensitive keys can be handled using .add/removeSensitiveKeys() */ toObject(options) { // toObject() should return ALL data by default (no filtering) // Pass an empty Set to indicate no keys should be filtered const noFiltering = new Set(); return this.serializationHandler.toObject(this.data, noFiltering, options); } /** * Converts to JSON string */ toJSON(options = {}) { this.ensureNotDestroyed(); validation.ValidationUtils.validateSerializationOptions(options); // Use non-strict mode by default (only exact matches) const strictMode = options.strictSensitiveKeys === true; const isSensitiveKey = (key) => this.sensitiveKeysManager.isSensitive(key, strictMode); return this.serializationHandler.toJSON(this.data, isSensitiveKey, options); } // ===== UTILITY METHODS ===== /** * Creates a hash of the entire object content */ async hash(algorithm = "SHA-256", format = "hex") { this.ensureNotDestroyed(); const serialized = this.serializationHandler.createHashableRepresentation(this.entries()); const secureString = new secureStringCore.SecureString(serialized); try { return await secureString.hash(algorithm, format); } finally { secureString.destroy(); } } /** * Executes a function with the object data and optionally clears it afterward */ use(fn, autoClear = false) { this.ensureNotDestroyed(); validation.ValidationUtils.validateCallback(fn, "use function"); try { return fn(this); } finally { if (autoClear) { this.destroy(); } } } /** * Creates a shallow copy of the SecureObject */ clone() { this.ensureNotDestroyed(); return SecureObject.from(this); } /** * Merges another object into this one */ merge(other, overwrite = true) { this.ensureNotDestroyed(); this.ensureNotReadOnly(); if (other instanceof SecureObject) { for (const key of other.keys()) { const stringKey = String(key); if (overwrite || !this.has(key)) { this.set(stringKey, other.get(key)); } } } else { for (const key in other) { if (Object.prototype.hasOwnProperty.call(other, key)) { if (overwrite || !this.has(key)) { this.set(String(key), other[key]); } } } } return this; } // ===== AMAZING NEW FEATURES ===== /** * Transform values with a mapper function (like Array.map but returns SecureObject) * Returns a new SecureObject with transformed values */ transform(mapper) { this.ensureNotDestroyed(); validation.ValidationUtils.validateMapper(mapper); const transformed = new SecureObject(); for (const key of this.keys()) { const value = this.get(key); const newValue = mapper(value, key, this); const stringKey = String(key); transformed.set(stringKey, newValue); } this.eventManager.emit("filtered", undefined, { operation: "transform", resultSize: transformed.size, }); return transformed; } /** * Group entries by a classifier function * Returns a Map where keys are group identifiers and values are SecureObjects */ groupBy(classifier) { this.ensureNotDestroyed(); validation.ValidationUtils.validateCallback(classifier, "Classifier function"); const groups = new Map(); for (const key of this.keys()) { const value = this.get(key); const groupKey = classifier(value, key); if (!groups.has(groupKey)) { groups.set(groupKey, new SecureObject()); } const group = groups.get(groupKey); const stringKey = String(key); group.set(stringKey, value); } this.eventManager.emit("filtered", undefined, { operation: "groupBy", resultSize: groups.size, }); return groups; } /** * Partition entries into two groups based on a predicate * Returns [matching, notMatching] SecureObjects */ partition(predicate) { this.ensureNotDestroyed(); validation.ValidationUtils.validatePredicate(predicate); const matching = new SecureObject(); const notMatching = new SecureObject(); for (const key of this.keys()) { const value = this.get(key); const stringKey = String(key); if (predicate(value, key)) { matching.set(stringKey, value); } else { notMatching.set(stringKey, value); } } this.eventManager.emit("filtered", undefined, { operation: "partition", resultSize: matching.size + notMatching.size, }); return [matching, notMatching]; } /** * Pick specific keys (like Lodash pick but type-safe) * Returns a new SecureObject with only the specified keys */ pick(...keys) { return this.filterByKeys(...keys); } /** * Omit specific keys (opposite of pick) * Returns a new SecureObject without the specified keys */ omit(...keys) { this.ensureNotDestroyed(); const omitted = new SecureObject(); const keysToOmit = new Set(keys.map((k) => String(k))); for (const key of this.keys()) { const stringKey = String(key); if (!keysToOmit.has(stringKey)) { omitted.set(stringKey, this.get(key)); } } this.eventManager.emit("filtered", undefined, { operation: "omit", keys: keys.map((k) => String(k)), resultSize: omitted.size, }); return omitted; } /** * Flatten nested objects (one level deep) * Converts { user: { name: "John" } } to { "user.name": "John" } */ flatten(separator = ".") { this.ensureNotDestroyed(); const flattened = new SecureObject(); for (const key of this.keys()) { const value = this.get(key); const stringKey = String(key); if (value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) { // Flatten nested object for (const [nestedKey, nestedValue] of Object.entries(value)) { const flatKey = `${stringKey}${separator}${nestedKey}`; flattened.set(flatKey, nestedValue); } } else { flattened.set(stringKey, value); } } this.eventManager.emit("filtered", undefined, { operation: "flatten", resultSize: flattened.size, }); return flattened; } /** * Compact - removes null, undefined, and empty values * Returns a new SecureObject with only truthy values */ compact() { return this.filter((value) => { if (value === null || value === undefined) return false; if (typeof value === "string" && value.trim() === "") return false; if (Array.isArray(value) && value.length === 0) return false; if (typeof value === "object" && Object.keys(value).length === 0) return false; return true; }); } /** * Invert - swap keys and values * Returns a new SecureObject with keys and values swapped */ invert() { this.ensureNotDestroyed(); const inverted = new SecureObject(); for (const key of this.keys()) { const value = this.get(key); const stringValue = String(value); const stringKey = String(key); inverted.set(stringValue, stringKey); } this.eventManager.emit("filtered", undefined, { operation: "invert", resultSize: inverted.size, }); return inverted; } /** * Defaults - merge with default values (only for missing keys) * Returns a new SecureObject with defaults applied */ defaults(defaultValues) { this.ensureNotDestroyed(); const result = new SecureObject(); // First, copy all existing values for (const key of this.keys()) { const stringKey = String(key); result.set(stringKey, this.get(key)); } // Then, add defaults for missing keys for (const [key, value] of Object.entries(defaultValues)) { if (!this.has(key)) { result.set(key, value); } } this.eventManager.emit("filtered", undefined, { operation: "defaults", resultSize: result.size, }); return result; } /** * Tap - execute a function with the object and return the object (for chaining) * Useful for debugging or side effects in method chains */ tap(fn) { this.ensureNotDestroyed(); validation.ValidationUtils.validateCallback(fn, "Tap function"); fn(this); return this; } pipe(...fns) { this.ensureNotDestroyed(); return fns.reduce((result, fn) => { validation.ValidationUtils.validateCallback(fn, "Pipe function"); return fn(result); }, this); } /** * Sample - get random entries from the object * Returns a new SecureObject with randomly selected entries */ sample(count = 1) { this.ensureNotDestroyed(); if (count <= 0) { return new SecureObject(); } const allKeys = this.keys(); const sampleSize = Math.min(count, allKeys.length); const sampledKeys = []; // Simple random sampling without replacement const availableKeys = [...allKeys]; for (let i = 0; i < sampleSize; i++) { const randomIndex = Math.floor(Math.random() * availableKeys.length); sampledKeys.push(availableKeys.splice(randomIndex, 1)[0]); } const sampled = new SecureObject(); for (const key of sampledKeys) { const stringKey = String(key); sampled.set(stri