549915a2be
- 新增 directThreadSanitize:项目内路径归一成相对路径,绝对路径 / 密钥 / 敏感赋值 / Bearer / 配置文件 / 私钥块 / 敏感开关统一换成占位符,只遮挡不截断 - 订阅 hook 在 bootstrap / consume / 历史切片进入聊天状态之前统一过脱敏,复用已有的 projectPath - 新增 12 条单测:项目内外路径、密钥前缀与 JWT、Bearer、.env、私钥块、多行换行保留、条目与增量事件
138 lines
4.0 KiB
TypeScript
138 lines
4.0 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',
|
|
);
|
|
});
|
|
|
|
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>',
|
|
});
|
|
});
|
|
});
|