Files
thzxx 4cd6e997b5
CI / 类型检查 + Lint + 单元测试 (push) Failing after 9m45s
CI / 全量测试 (Electron ABI) (push) Failing after 6m28s
CI / 产物编译验证 (push) Successful in 11m18s
feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
2026-09-08 14:30:27 +08:00

53 lines
2.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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);
}
}