feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* v0.8.2 P1-5: 敏感值脱敏单源工具
|
||||
*
|
||||
* 背景:审计日志 logToolCall 原样落库完整工具 args —— 工具参数中携带的
|
||||
* API Key / 鉴权头 / token(如 http_request 的 headers.authorization、
|
||||
* MCP 工具的鉴权参数)以明文进入 audit_logs 表,密钥链加密(safeStorage)
|
||||
* 只保护配置层,不保护审计层,构成敏感信息二次扩散面。
|
||||
*
|
||||
* 本模块提供与配置层同源(isSensitiveConfigKey 的归一化键名匹配)的脱敏:
|
||||
* - maskSensitiveValue:字符串值掩码(长值保留后 4 位,短值完全掩码)
|
||||
* - deepMaskSensitive:递归遍历对象/数组,按键名匹配掩码字符串值
|
||||
* (循环引用防护 + 深度上限,防御恶意构造的参数结构)
|
||||
*/
|
||||
|
||||
import { isSensitiveConfigKey } from './secure-config';
|
||||
|
||||
/** 长值保留后 4 位,短值(≤4 字符)完全掩码 —— 与 ipc/shared.maskSensitive 同口径 */
|
||||
export function maskSensitiveValue(value: string): string {
|
||||
return value.length > 4 ? '***' + value.slice(-4) : '***';
|
||||
}
|
||||
|
||||
const MAX_MASK_DEPTH = 6;
|
||||
|
||||
/**
|
||||
* 深度脱敏:返回脱敏后的副本(原对象不修改)。
|
||||
* 对象/数组递归;键名命中敏感模式(归一化匹配,api_key/authKey/token/secret/
|
||||
* password/credential 等)时对字符串值掩码;其余值原样保留。
|
||||
*/
|
||||
export function deepMaskSensitive<T>(input: T, depth = 0, seen?: Set<object>): T {
|
||||
if (input === null || typeof input !== 'object') return input;
|
||||
if (depth >= MAX_MASK_DEPTH) return '[depth-limit]' as unknown as T;
|
||||
const seenSet = seen ?? new Set<object>();
|
||||
if (seenSet.has(input as object)) return '[circular]' as unknown as T;
|
||||
seenSet.add(input as object);
|
||||
|
||||
try {
|
||||
if (Array.isArray(input)) {
|
||||
return input.map((item) => deepMaskSensitive(item, depth + 1, seenSet)) as unknown as T;
|
||||
}
|
||||
const out: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
|
||||
if (isSensitiveConfigKey(key) && typeof value === 'string' && value.length > 0) {
|
||||
out[key] = maskSensitiveValue(value);
|
||||
} else {
|
||||
out[key] = deepMaskSensitive(value, depth + 1, seenSet);
|
||||
}
|
||||
}
|
||||
return out as unknown as T;
|
||||
} finally {
|
||||
seenSet.delete(input as object);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user