@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
JavaScript
/**
* 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