d07f894bf6
- directThreadSanitize.ts:BEARER_VALUE 的引号分支由 \\.?["'] 改为 \\?["'],与同文件 SENSITIVE_ASSIGNMENT 的写法一致 - 原写法要求引号前必须有一个字面反斜杠,导致 Bearer "abc def" 走不到引号分支,退化到裸 token 分支后只遮到第一个空格,示例会输出 Bearer [redacted-secret] def" - 修正后引号内的整段值都被遮掉;JSON 转义的 \"abc def\" 仍然匹配 - 同步在 directThreadSanitize.test.ts 增加带引号 Bearer 的用例(修复前该用例失败)
142 lines
4.3 KiB
TypeScript
142 lines
4.3 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
sanitizeDirectThreadEvent,
|
|
sanitizeDirectThreadText,
|
|
} from '../src/view/project-development/chat/conversation/directThreadSanitize';
|
|
import type { ThreadEvent } from '../src/view/project-development/chat/generated/ThreadEvent';
|
|
|
|
const ROOT = '/workspace/direct-project';
|
|
|
|
describe('directThreadSanitize', () => {
|
|
it('项目内绝对路径归一成项目相对路径', () => {
|
|
expect(
|
|
sanitizeDirectThreadText(
|
|
ROOT,
|
|
'open /workspace/direct-project/game/src/x.ts',
|
|
),
|
|
).toBe('open game/src/x.ts');
|
|
});
|
|
|
|
it('项目外绝对路径与 file:// 换成占位符', () => {
|
|
expect(sanitizeDirectThreadText(ROOT, 'at /root/secret/x')).toBe(
|
|
'at <absolute-path>',
|
|
);
|
|
expect(sanitizeDirectThreadText(ROOT, 'file:///etc/passwd')).toBe(
|
|
'<absolute-path>',
|
|
);
|
|
expect(sanitizeDirectThreadText(ROOT, 'win C:\\Users\\me\\x.txt')).toBe(
|
|
'win <absolute-path>',
|
|
);
|
|
});
|
|
|
|
it('有前缀的密钥与 JWT 换成占位符', () => {
|
|
const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.abcdefgh';
|
|
expect(sanitizeDirectThreadText(ROOT, 'key=sk-abcdefghijklmnop')).toBe(
|
|
'key=[redacted-secret]',
|
|
);
|
|
expect(sanitizeDirectThreadText(ROOT, `token ${jwt}`)).toBe(
|
|
'token [redacted-secret]',
|
|
);
|
|
});
|
|
|
|
it('敏感赋值只遮挡值,保留同行其它信息', () => {
|
|
expect(sanitizeDirectThreadText(ROOT, 'password=hunter2 status=401')).toBe(
|
|
'[redacted-sensitive-field]=[redacted-secret] status=401',
|
|
);
|
|
expect(sanitizeDirectThreadText(ROOT, '"token": "raw-value"')).toBe(
|
|
'"[redacted-sensitive-field]": [redacted-secret]',
|
|
);
|
|
});
|
|
|
|
it('Bearer 值换成占位符', () => {
|
|
expect(sanitizeDirectThreadText(ROOT, 'Authorization: Bearer abc123')).toBe(
|
|
'[redacted-sensitive-field]: [redacted-secret]',
|
|
);
|
|
expect(sanitizeDirectThreadText(ROOT, 'h x Bearer abc123 y')).toBe(
|
|
'h x Bearer [redacted-secret] y',
|
|
);
|
|
// 带引号的 Bearer 值(含空格)也要整段遮掉,不能只遮到第一个空格。
|
|
expect(sanitizeDirectThreadText(ROOT, 'h x Bearer "abc def" y')).toBe(
|
|
'h x Bearer [redacted-secret] y',
|
|
);
|
|
});
|
|
|
|
it('.env / game-creator.config 换成配置占位符', () => {
|
|
expect(sanitizeDirectThreadText(ROOT, 'cat .env.local')).toBe(
|
|
'cat [redacted-config]',
|
|
);
|
|
});
|
|
|
|
it('私钥块整块吞掉', () => {
|
|
const pem = [
|
|
'-----BEGIN PRIVATE KEY-----',
|
|
'MIIEvQIBADANBg',
|
|
'-----END PRIVATE KEY-----',
|
|
'',
|
|
].join('\n');
|
|
expect(sanitizeDirectThreadText(ROOT, pem)).toBe(
|
|
'[redacted sensitive context]\n',
|
|
);
|
|
});
|
|
|
|
it('敏感开关整行遮挡', () => {
|
|
expect(
|
|
sanitizeDirectThreadText(ROOT, 'curl --token abc123 https://x'),
|
|
).toBe('[redacted sensitive context]');
|
|
});
|
|
|
|
it('已有占位符不再二次变形', () => {
|
|
expect(sanitizeDirectThreadText(ROOT, 'at <absolute-path>')).toBe(
|
|
'at <absolute-path>',
|
|
);
|
|
});
|
|
|
|
it('多行文本保留换行', () => {
|
|
const value = '第一段\nkey=sk-abcdefghijklmnop\n第三段\n';
|
|
expect(sanitizeDirectThreadText(ROOT, value)).toBe(
|
|
'第一段\nkey=[redacted-secret]\n第三段\n',
|
|
);
|
|
});
|
|
|
|
it('条目事件只改自由文本字段,身份与时间原样', () => {
|
|
const event: ThreadEvent = {
|
|
type: 'item.completed',
|
|
item: {
|
|
itemType: 'message',
|
|
itemId: 'msg-1',
|
|
role: 'assistant',
|
|
text: 'key=sk-abcdefghijklmnop at /root/secret/x',
|
|
at: 1_700_000_000_000,
|
|
},
|
|
at: 1_700_000_000_100,
|
|
};
|
|
expect(sanitizeDirectThreadEvent(ROOT, event)).toEqual({
|
|
type: 'item.completed',
|
|
item: {
|
|
itemType: 'message',
|
|
itemId: 'msg-1',
|
|
role: 'assistant',
|
|
text: 'key=[redacted-secret] at <absolute-path>',
|
|
at: 1_700_000_000_000,
|
|
},
|
|
at: 1_700_000_000_100,
|
|
});
|
|
});
|
|
|
|
it('流式增量事件遮挡正文', () => {
|
|
const event: ThreadEvent = {
|
|
type: 'item.delta',
|
|
itemId: 'msg-1',
|
|
kind: 'message',
|
|
delta: 'see /root/secret/x',
|
|
};
|
|
expect(sanitizeDirectThreadEvent(ROOT, event)).toEqual({
|
|
type: 'item.delta',
|
|
itemId: 'msg-1',
|
|
kind: 'message',
|
|
delta: 'see <absolute-path>',
|
|
});
|
|
});
|
|
});
|