UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

329 lines 10.4 kB
/** * Default fields to redact from stream chunks */ const DEFAULT_REDACTED_FIELDS = [ "request", "args", "result", "apiKey", "token", "authorization", "credentials", "password", "secret", ]; /** * Default redaction placeholder */ const DEFAULT_PLACEHOLDER = "[REDACTED]"; /** * Maximum recursion depth for redactObject */ const MAX_REDACTION_DEPTH = 10; /** * Redact sensitive data from a stream chunk. * * IMPORTANT: Redaction is DISABLED by default. You must set `config.enabled = true` * to enable redaction. This is a security feature that requires explicit opt-in. * * @param chunk - The stream chunk to redact * @param config - Redaction configuration (enabled: false by default) * @returns The redacted chunk with sensitive data removed, or original if disabled * * @example * ```typescript * // Redaction disabled by default - returns chunk unchanged * const unchanged = redactStreamChunk(chunk); * * // Enable redaction explicitly * const redacted = redactStreamChunk(chunk, { enabled: true }); * // Sensitive fields replaced with "[REDACTED]" * ``` * * @example * ```typescript * // With custom configuration (must enable first) * const redacted = redactStreamChunk(chunk, { * enabled: true, * additionalFields: ["customSecret"], * preserveFields: ["result"], // Don't redact result * }); * ``` */ export function redactStreamChunk(chunk, config) { // CRITICAL: Redaction is DISABLED by default // Must explicitly enable with config.enabled = true if (!config?.enabled) { return chunk; } if (!chunk || typeof chunk !== "object") { return chunk; } const placeholder = config?.placeholder ?? DEFAULT_PLACEHOLDER; const redactedFields = new Set([ ...DEFAULT_REDACTED_FIELDS, ...(config?.additionalFields ?? []), ]); // Remove preserved fields from redaction set if (config?.preserveFields) { for (const field of config.preserveFields) { redactedFields.delete(field); } } return redactChunkByType(chunk, redactedFields, placeholder, config); } /** * Redact chunk based on its type */ function redactChunkByType(chunk, redactedFields, placeholder, config) { const typedChunk = chunk; const chunkType = typedChunk.type; switch (chunkType) { case "step-start": return redactStepStart(typedChunk, redactedFields, placeholder); case "tool-call": return redactToolCall(typedChunk, redactedFields, placeholder, config); case "tool-result": return redactToolResult(typedChunk, redactedFields, placeholder, config); case "error": return redactError(typedChunk, redactedFields, placeholder); default: return redactGenericChunk(typedChunk, redactedFields, placeholder); } } /** * Redact step-start chunks */ function redactStepStart(chunk, redactedFields, placeholder) { if (!hasPayload(chunk)) { return chunk; } const { payload, ...rest } = chunk; const payloadObj = payload; const redactedPayload = redactObject(payloadObj, redactedFields, placeholder, 0); return { ...rest, type: "step-start", payload: redactedPayload, }; } /** * Redact tool-call chunks */ function redactToolCall(chunk, redactedFields, placeholder, config) { // Handle chunks with payload structure if (hasPayload(chunk)) { const { payload, ...rest } = chunk; const payloadObj = payload; // Redact tool arguments if configured if (payloadObj.toolCall && typeof payloadObj.toolCall === "object") { const toolCall = payloadObj.toolCall; const redactArgs = config?.redactToolArgs !== false && redactedFields.has("args"); return { ...rest, type: "tool-call", payload: { ...payloadObj, toolCall: { ...toolCall, args: redactArgs ? placeholder : toolCall.args, }, }, }; } return { ...rest, type: "tool-call", payload: redactObject(payloadObj, redactedFields, placeholder, 0), }; } // Handle chunks with data structure (DataStreamEvent format) if (hasData(chunk)) { const { data, ...rest } = chunk; const dataObj = data; const redactArgs = config?.redactToolArgs !== false && redactedFields.has("args"); // Check for arguments field in data if ("arguments" in dataObj && redactArgs) { return { ...rest, type: "tool-call", data: { ...dataObj, arguments: placeholder, }, }; } return { ...rest, type: "tool-call", data: redactObject(dataObj, redactedFields, placeholder, 0), }; } return chunk; } /** * Redact tool-result chunks */ function redactToolResult(chunk, redactedFields, placeholder, config) { const redactResult = config?.redactToolResults !== false && redactedFields.has("result"); // Handle chunks with payload structure if (hasPayload(chunk)) { const { payload, ...rest } = chunk; const payloadObj = payload; return { ...rest, type: "tool-result", payload: { ...payloadObj, result: redactResult ? placeholder : payloadObj.result, }, }; } // Handle chunks with data structure (DataStreamEvent format) if (hasData(chunk)) { const { data, ...rest } = chunk; const dataObj = data; return { ...rest, type: "tool-result", data: { ...dataObj, result: redactResult ? placeholder : dataObj.result, }, }; } return chunk; } /** * Redact error chunks (remove stack traces in production) */ function redactError(chunk, redactedFields, placeholder) { const isProduction = process.env.NODE_ENV === "production"; // Handle chunks with data structure (DataStreamEvent format) if (hasData(chunk)) { const { data, ...rest } = chunk; const dataObj = data; const { stack, ...dataRest } = dataObj; return { ...rest, type: "error", data: { ...redactObject(dataRest, redactedFields, placeholder, 0), ...(isProduction ? {} : { stack }), }, }; } // Handle direct chunk with stack const { stack, ...restChunk } = chunk; const redactedRest = redactObject(restChunk, redactedFields, placeholder, 0); return { ...redactedRest, type: "error", ...(isProduction ? {} : { stack }), }; } /** * Redact generic chunks by scanning all fields */ function redactGenericChunk(chunk, redactedFields, placeholder) { return redactObject(chunk, redactedFields, placeholder, 0); } /** * Recursively redact sensitive fields from an object. * * @param obj - Object to redact * @param redactedFields - Set of field names to redact (case-insensitive matching) * @param placeholder - Replacement value for redacted fields * @param depth - Current recursion depth * @returns Object with sensitive fields redacted */ function redactObject(obj, redactedFields, placeholder, depth) { // Prevent infinite recursion if (depth > MAX_REDACTION_DEPTH) { return obj; } const result = {}; for (const [key, value] of Object.entries(obj)) { // Case-insensitive field name matching const shouldRedact = redactedFields.has(key.toLowerCase()); if (shouldRedact) { result[key] = placeholder; } else if (value && typeof value === "object" && !Array.isArray(value)) { result[key] = redactObject(value, redactedFields, placeholder, depth + 1); } else if (Array.isArray(value)) { result[key] = value.map((item) => { if (item && typeof item === "object") { return redactObject(item, redactedFields, placeholder, depth + 1); } return item; }); } else { result[key] = value; } } return result; } /** * Type guard for chunks with payload */ function hasPayload(chunk) { return ("payload" in chunk && chunk.payload !== null && typeof chunk.payload === "object"); } /** * Type guard for chunks with data (DataStreamEvent format) */ function hasData(chunk) { return ("data" in chunk && chunk.data !== null && typeof chunk.data === "object"); } /** * Create a redaction transform function for streams. * * IMPORTANT: Redaction is DISABLED by default. You must set `config.enabled = true` * to enable redaction. If not enabled, the returned function passes chunks through unchanged. * * @param config - Redaction configuration * @returns A transform function for stream chunks * * @example * ```typescript * // Redaction disabled - chunks pass through unchanged * const redactor = createStreamRedactor(); * const unchanged = redactor(chunk); // Returns original chunk * * // Enable redaction * const redactor = createStreamRedactor({ enabled: true }); * const redactedStream = stream.pipeThrough(new TransformStream({ * transform: (chunk, controller) => { * controller.enqueue(redactor(chunk)); * } * })); * ``` * * @example * ```typescript * // With custom options * const redactor = createStreamRedactor({ * enabled: true, * redactToolArgs: true, * redactToolResults: true, * additionalFields: ["internalId"], * }); * ``` */ export function createStreamRedactor(config) { return (chunk) => { // When redaction is disabled (default), return chunk unchanged // This allows the redactor to be used with any chunk type as a no-op if (!config?.enabled) { return chunk; } // When enabled, apply redaction (works with any object-based chunk) return redactStreamChunk(chunk, config); }; } //# sourceMappingURL=redaction.js.map