@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
170 lines (169 loc) • 5.32 kB
JavaScript
/**
* Shared log-sanitization helpers.
*
* Centralises truncate + secret-redaction patterns so every provider stays
* consistent and any regex improvements only need one change.
*
* Coverage:
* - `Authorization: Bearer <token>` (with required whitespace)
* - `Authorization: Token <token>` (Replicate uses this, not Bearer)
* - `Authorization: Basic <base64>` (D-ID and similar)
* - Bare tokens by known provider prefix:
* sk-/pk- (OpenAI, Anthropic, Stability),
* r8_ (Replicate), gsk_ (Groq), xai- (xAI), tgp_ (Together),
* fw_ (Fireworks), pplx- (Perplexity), pa- (Voyage),
* jina_ (Jina), fish- (Fish Audio)
* - Generic key=value pairs: api_key=…, access_token: …, secret_key=…
*/
const TOKEN_PREFIXES = [
"sk",
"pk",
"r8",
"gsk",
"xai",
"tgp",
"fw",
"pplx",
"pa",
"jina",
"fish",
];
const PREFIX_PATTERN = TOKEN_PREFIXES.join("|");
/**
* Pattern matching common bearer/API-key tokens in plain text.
*
* Case-insensitive (`i` flag) since header names ("Authorization") and scheme
* names ("Bearer", "Token", "Basic") are sometimes lower-cased in error
* bodies. `g` flag for replace-all.
*/
const SECRET_PATTERN = new RegExp(
// Authorization schemes — required whitespace between scheme and value
"\\bBearer\\s+[A-Za-z0-9_\\-\\.]{8,}\\b" +
"|\\bToken\\s+[A-Za-z0-9_\\-\\.]{8,}\\b" +
"|\\bBasic\\s+[A-Za-z0-9+/=]{12,}\\b" +
// Bare tokens by known prefix (e.g. `sk-abc…`, `r8_xyz…`)
`|\\b(?:${PREFIX_PATTERN})[_\\-][A-Za-z0-9_\\-\\.]{8,}\\b` +
// Generic key=value pairs (URL params, JSON bodies, header dumps)
"|\\b(?:api[_-]?key|access[_-]?token|secret[_-]?key|refresh[_-]?token)\\s*[:=]\\s*['\"]?[^\\s,;'\"&]+", "gi");
/** Header names that should always be redacted regardless of value shape. */
const SENSITIVE_HEADER_NAMES = [
"authorization",
"cookie",
"set-cookie",
"x-api-key",
"api-key",
"apikey",
"x-auth-token",
"x-csrf-token",
];
/** Object keys that should always be redacted regardless of value shape. */
const SENSITIVE_OBJECT_KEYS = [
"apikey",
"api_key",
"apiKey",
"access_token",
"accessToken",
"refresh_token",
"refreshToken",
"secret",
"secretkey",
"secret_key",
"secretKey",
"password",
"authorization",
"oauth",
"oauthToken",
"credentials",
];
/**
* Truncate `text` to `maxLen` chars then replace embedded secrets with `***`.
*
* Use this for free-form text logged from response/request bodies. For
* structured data (records, headers) prefer {@link sanitizeRecord} and
* {@link sanitizeHeaders} which know to redact by key name as well.
*
* @param text - Raw text to sanitize (typically an HTTP response body).
* @param maxLen - Maximum number of characters to keep (default 500).
*/
export function sanitizeForLog(text, maxLen = 500) {
if (!text) {
return text;
}
return text.slice(0, maxLen).replace(SECRET_PATTERN, "***");
}
/**
* Recursively sanitize a record/array, returning a structurally identical
* value with sensitive keys redacted and string values run through
* {@link sanitizeForLog}.
*
* Safe to call on any JSON-shaped data. Cycles are detected and replaced
* with the string `"[Circular]"` to avoid infinite recursion when logging
* mid-stream objects that reference themselves.
*
* @param value - The value to sanitize.
* @param maxStringLen - Per-string truncation cap (default 1000).
*/
export function sanitizeRecord(value, maxStringLen = 1000) {
const seen = new WeakSet();
const walk = (v) => {
if (v === null || v === undefined) {
return v;
}
if (typeof v === "string") {
return sanitizeForLog(v, maxStringLen);
}
if (typeof v !== "object") {
return v;
}
if (seen.has(v)) {
return "[Circular]";
}
seen.add(v);
if (Array.isArray(v)) {
return v.map(walk);
}
const out = {};
for (const [k, val] of Object.entries(v)) {
if (SENSITIVE_OBJECT_KEYS.some((name) => name.toLowerCase() === k.toLowerCase())) {
out[k] = "***";
}
else {
out[k] = walk(val);
}
}
return out;
};
return walk(value);
}
/**
* Sanitize an HTTP headers object — redacts sensitive header names entirely
* (`***`) and applies {@link sanitizeForLog} to remaining values.
*
* Accepts both `Headers` instances and plain-object header maps so providers
* can log either shape uniformly.
*/
export function sanitizeHeaders(headers) {
if (!headers) {
return {};
}
const out = {};
const set = (name, value) => {
if (value === undefined || value === null) {
return;
}
const lower = name.toLowerCase();
if (SENSITIVE_HEADER_NAMES.includes(lower)) {
out[name] = "***";
return;
}
out[name] = sanitizeForLog(value, 500);
};
if (headers instanceof Headers) {
headers.forEach((value, key) => set(key, value));
return out;
}
for (const [key, value] of Object.entries(headers)) {
set(key, value);
}
return out;
}