238 lines
15 KiB
TypeScript
238 lines
15 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildPiInitializationDiagnostic } from '../../electron/coding-runtime/pi/initialization-diagnostic';
|
|
|
|
const root = '/private/bundled-app/resources';
|
|
const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcml2YXRlIn0.private-signature';
|
|
const credential = 'private-key-S3cr3t-12345';
|
|
|
|
function withStack<T extends Error>(error: T, stack: string): T {
|
|
Object.defineProperty(error, 'stack', { configurable: true, writable: true, value: stack });
|
|
return error;
|
|
}
|
|
|
|
describe('Pi initialization diagnostic privacy and technical evidence', () => {
|
|
it('retains the technical class and trusted source location without the exception text or absolute root', () => {
|
|
const error = withStack(Object.assign(new TypeError(`Cannot read ${credential} from https://private.example/api`),
|
|
{ code: 'ERR_INVALID_ARG_TYPE' }), [
|
|
`TypeError: ${credential}`,
|
|
` at secretFunctionName (${root}/pi/runtime.ts:253:20)`,
|
|
' at processTicksAndRejections (node:internal/process/task_queues:95:5)',
|
|
' at execute (/Users/private-user/project/private-module.ts:8:1)',
|
|
].join('\n'));
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'worker-start', trustedRoots: [root], sensitiveValues: [credential] });
|
|
expect(diagnostic).toMatchObject({
|
|
version: 1, stage: 'worker-start', exception: 'TypeError', code: 'ERR_INVALID_ARG_TYPE',
|
|
category: 'runtime-contract', frames: ['root0/pi/runtime.ts:253:20', 'node:internal/process/task_queues:95:5', '[REDACTED_FRAME]'],
|
|
});
|
|
expect(diagnostic.fingerprint).toMatch(/^[a-f0-9]{16}$/);
|
|
const serialized = JSON.stringify(diagnostic);
|
|
for (const privateValue of [credential, 'Cannot read', 'secretFunctionName', root, 'private-user', 'https://private.example/api'])
|
|
expect(serialized).not.toContain(privateValue);
|
|
});
|
|
|
|
it('classifies a failed worker with its filesystem cause while ignoring arbitrary custom error fields', () => {
|
|
const cause = Object.assign(new Error(`open /Users/alice/${credential}.json`), { code: 'ENOENT', path: `/Users/alice/${credential}.json` });
|
|
const error = Object.assign(new Error(`worker failed: ${jwt}`, { cause }), {
|
|
name: 'PiProcessError', code: 'PI_WORKER_START_FAILED', diagnostic: `authorization: Bearer ${credential}`, token: credential,
|
|
});
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'worker-start' });
|
|
expect(diagnostic).toMatchObject({
|
|
exception: 'PiProcessError', code: 'PI_WORKER_START_FAILED', category: 'worker-lifecycle',
|
|
causes: [{ exception: 'Error', code: 'ENOENT', category: 'local-storage' }],
|
|
});
|
|
const serialized = JSON.stringify(diagnostic);
|
|
for (const value of [credential, jwt, '/Users/alice', 'authorization', 'token']) expect(serialized).not.toContain(value);
|
|
});
|
|
|
|
it('never records free-form headers, quoted JSON, JWTs, URLs or private paths', () => {
|
|
const input = [
|
|
`Authorization: "Bearer ${credential}"`, `{"api_key":"${credential}","prompt":"用户的私密问题"}`,
|
|
jwt, 'https://user:password@example.test/private?q=secret', '/Users/alice/private-folder/a.txt',
|
|
'C:\\Users\\Alice\\private-folder\\a.txt', 'file:///Users/alice/private-folder/a.txt', '../private-folder/a.txt',
|
|
].join('\n');
|
|
const diagnostic = buildPiInitializationDiagnostic(new Error(input), { stage: input });
|
|
const serialized = JSON.stringify(diagnostic);
|
|
expect(diagnostic.stage).toBe('unknown');
|
|
expect(diagnostic.fingerprint).toMatch(/^[a-f0-9]{16}$/);
|
|
for (const value of [credential, jwt, '用户的私密问题', 'private-folder', 'example.test', 'Authorization', 'api_key', 'password'])
|
|
expect(serialized).not.toContain(value);
|
|
});
|
|
|
|
it('redacts literal and encoded sensitive values even inside trusted stack locations', () => {
|
|
const privateName = '私密配置-Alpha42';
|
|
const encodings = [
|
|
privateName, encodeURIComponent(privateName), encodeURIComponent(encodeURIComponent(privateName)),
|
|
Buffer.from(privateName).toString('base64url'),
|
|
];
|
|
const error = withStack(new Error(privateName), encodings.map(value => ` at fn (${root}/${value}.ts:11:2)`).join('\n'));
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'resource-materialization', trustedRoots: [root], sensitiveValues: [privateName] });
|
|
expect(diagnostic.frames).toEqual(encodings.map(() => '[REDACTED_FRAME]'));
|
|
const serialized = JSON.stringify(diagnostic);
|
|
for (const value of encodings) expect(serialized).not.toContain(value);
|
|
});
|
|
|
|
it('keeps file-URL and Windows bundled code locations relative without logging their parent names', () => {
|
|
const error = withStack(new Error('private text'), [
|
|
' at load (file:///Users/private-owner/My%20App/resources/pi/loader.mjs:42:9)',
|
|
' at load (C:\\Users\\private-owner\\App\\resources\\pi\\loader.cjs:48:2)',
|
|
' at load (file://remote-host/private/a.js:8:1)',
|
|
' at load (./private/a.js:8:1)',
|
|
' at load (/Users/private-owner/My App/resources/pi/../secret.js:8:1)',
|
|
].join('\n'));
|
|
const diagnostic = buildPiInitializationDiagnostic(error, {
|
|
stage: 'worker-resources', trustedRoots: ['/Users/private-owner/My App/resources', 'C:\\Users\\private-owner\\App\\resources'],
|
|
});
|
|
expect(diagnostic.frames).toEqual(['root0/pi/loader.mjs:42:9', 'root1/pi/loader.cjs:48:2', '[REDACTED_FRAME]', '[REDACTED_FRAME]', '[REDACTED_FRAME]']);
|
|
const serialized = JSON.stringify(diagnostic);
|
|
for (const value of ['private-owner', 'Users', 'file:', 'remote-host', 'My%20App', 'C:']) expect(serialized).not.toContain(value);
|
|
});
|
|
|
|
it('rejects forged exception names and non-whitelisted codes without falling back to their raw values', () => {
|
|
const error = { name: credential, code: 'ERR_PRIVATE_API_KEY_' + credential, message: credential, publicError: { code: credential, message: jwt } };
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'provider-catalog' });
|
|
expect(diagnostic.exception).toBe('UnknownError');
|
|
expect(diagnostic.code).toBeUndefined();
|
|
expect(diagnostic.category).toBe('unclassified-initialization');
|
|
expect(JSON.stringify(diagnostic)).not.toContain(credential);
|
|
expect(JSON.stringify(diagnostic)).not.toContain(jwt);
|
|
});
|
|
|
|
it('does not execute error getters, coercion hooks or lazy stack formatting through hostile fields', () => {
|
|
const accessed: string[] = [];
|
|
const error = new Error('ordinary placeholder');
|
|
for (const key of ['name', 'message', 'stack', 'code', 'cause', 'publicError']) {
|
|
Object.defineProperty(error, key, { configurable: true, get() { accessed.push(key); throw new Error(credential); } });
|
|
}
|
|
Object.assign(error, {
|
|
toString() { accessed.push('toString'); return credential; },
|
|
[Symbol.toPrimitive]() { accessed.push('toPrimitive'); return credential; },
|
|
});
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'snapshot' });
|
|
expect(accessed).toEqual([]);
|
|
expect(diagnostic).toMatchObject({ stage: 'snapshot', exception: 'UnknownError' });
|
|
expect(JSON.stringify(diagnostic)).not.toContain(credential);
|
|
});
|
|
|
|
it('survives proxies which throw during structural inspection', () => {
|
|
const error = new Proxy({}, {
|
|
get() { throw new Error(credential); },
|
|
getOwnPropertyDescriptor() { throw new Error(credential); },
|
|
getPrototypeOf() { throw new Error(credential); },
|
|
});
|
|
expect(() => buildPiInitializationDiagnostic(error, { stage: 'registry' })).not.toThrow();
|
|
expect(JSON.stringify(buildPiInitializationDiagnostic(error, { stage: 'registry' }))).not.toContain(credential);
|
|
});
|
|
|
|
it('does not invoke inherited formatting getters or a replaced global stack formatter', () => {
|
|
let calls = 0;
|
|
const prototype = Object.create(Error.prototype);
|
|
Object.defineProperty(prototype, 'name', { get() { calls++; return credential; } });
|
|
const inherited = new Error('placeholder');
|
|
Object.setPrototypeOf(inherited, prototype);
|
|
expect(buildPiInitializationDiagnostic(inherited, { stage: 'snapshot' }).frames).toBeUndefined();
|
|
const prior = Object.getOwnPropertyDescriptor(Error, 'prepareStackTrace');
|
|
try {
|
|
Object.defineProperty(Error, 'prepareStackTrace', { configurable: true, value() { calls++; return credential; } });
|
|
const diagnostic = buildPiInitializationDiagnostic(new Error('placeholder'), { stage: 'snapshot' });
|
|
expect(diagnostic.frames).toBeUndefined();
|
|
expect(JSON.stringify(diagnostic)).not.toContain(credential);
|
|
expect(calls).toBe(0);
|
|
} finally {
|
|
if (prior) Object.defineProperty(Error, 'prepareStackTrace', prior);
|
|
else Reflect.deleteProperty(Error, 'prepareStackTrace');
|
|
}
|
|
});
|
|
|
|
it('bounds UTF-8 output, stack frames and cause chains and terminates cause cycles', () => {
|
|
const make = () => withStack(new Error('长'.repeat(40_000)),
|
|
Array.from({ length: 50 }, (_, index) => ` at initialize (${root}/pi/${'a'.repeat(120)}${index}.ts:999999:123)`).join('\n'));
|
|
const error = make();
|
|
const cause = make();
|
|
const third = make();
|
|
Object.assign(error, { cause });
|
|
Object.assign(cause, { cause: third });
|
|
Object.assign(third, { cause: error });
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'initial-hydration', trustedRoots: [root] });
|
|
expect(Buffer.byteLength(JSON.stringify(diagnostic))).toBeLessThanOrEqual(4_096);
|
|
expect(diagnostic.truncated).toBe(true);
|
|
let current = diagnostic;
|
|
for (let depth = 0; depth < 3; depth++) {
|
|
expect(current.frames?.length ?? 0).toBeLessThanOrEqual(6);
|
|
if (!current.causes?.[0]) break;
|
|
expect(depth).toBeLessThan(2);
|
|
current = current.causes[0];
|
|
}
|
|
expect(JSON.stringify(diagnostic)).not.toContain('长');
|
|
});
|
|
|
|
it('projects known public contract codes and primitive failures without error content', () => {
|
|
const diagnostic = buildPiInitializationDiagnostic({ publicError: { code: 'CODING_MIGRATION_MODEL_REQUIRED', message: credential } }, { stage: 'provider-input' });
|
|
expect(diagnostic).toMatchObject({ code: 'CODING_MIGRATION_MODEL_REQUIRED', category: 'provider-configuration' });
|
|
expect(JSON.stringify(diagnostic)).not.toContain(credential);
|
|
expect(buildPiInitializationDiagnostic(credential, { stage: 'prepare' })).toMatchObject({ exception: 'UnknownError', fingerprint: expect.stringMatching(/^[a-f0-9]{16}$/) });
|
|
});
|
|
|
|
it('locates a wrapped thread_open SDK failure without turning its private property into a logged field', () => {
|
|
const error = Object.assign(new Error(`Pi RPC thread_open failed: Cannot read properties of undefined (reading '${credential}')`), {
|
|
name: 'PiProcessError', code: 'PI_RPC_RESPONSE_ERROR',
|
|
});
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'worker-start' });
|
|
expect(diagnostic).toMatchObject({
|
|
exception: 'PiProcessError', code: 'PI_RPC_RESPONSE_ERROR', category: 'worker-protocol',
|
|
rpcOperation: 'thread_open', rpcFailureSignature: 'undefined-property',
|
|
});
|
|
expect(JSON.stringify(diagnostic)).not.toContain(credential);
|
|
expect(JSON.stringify(diagnostic)).not.toContain('Cannot read properties');
|
|
});
|
|
|
|
it.each([
|
|
{ tail: `TypeError: Cannot read properties of null (reading '${credential}')`, signature: 'null-property' },
|
|
{ tail: `${credential}.request is not a function`, signature: 'not-callable' },
|
|
{ tail: `Cannot find module '/Users/private-user/${credential}.js'\nRequire stack:\n- /Users/private-user/${jwt}.js`, signature: 'module-not-found' },
|
|
{ tail: `Cannot find package '${credential}' imported from https://private.example/${jwt}`, signature: 'module-not-found' },
|
|
{ tail: `ENOENT: no such file or directory, open 'C:\\Users\\private-user\\${credential}.json'`, signature: 'missing-local-resource' },
|
|
])('projects only a fixed signature from private RPC content: $signature', ({ tail, signature }) => {
|
|
const error = Object.assign(new Error(`Pi RPC get_state failed: ${tail}`), { name: 'PiProcessError', code: 'PI_RPC_RESPONSE_ERROR' });
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'initial-hydration' });
|
|
expect(diagnostic).toMatchObject({ rpcOperation: 'get_state', rpcFailureSignature: signature, exception: 'PiProcessError' });
|
|
const serialized = JSON.stringify(diagnostic);
|
|
for (const value of [credential, jwt, 'private-user', 'private.example', 'C:', 'Users', '.request']) expect(serialized).not.toContain(value);
|
|
});
|
|
|
|
it('does not interpret user text, unknown commands, embedded wrappers or unknown response patterns as RPC signatures', () => {
|
|
const wrapped = `Pi RPC thread_open failed: Cannot read properties of undefined (reading '${credential}')`;
|
|
for (const error of [
|
|
new Error(wrapped),
|
|
Object.assign(new Error('private prefix\n' + wrapped), { code: 'PI_RPC_RESPONSE_ERROR' }),
|
|
Object.assign(new Error(`Pi RPC ${credential} failed: ${wrapped}`), { code: 'PI_RPC_RESPONSE_ERROR' }),
|
|
{ publicError: { code: 'PI_RPC_RESPONSE_ERROR' }, message: wrapped },
|
|
]) {
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'worker-start' });
|
|
expect(diagnostic.rpcOperation).toBeUndefined();
|
|
expect(diagnostic.rpcFailureSignature).toBeUndefined();
|
|
expect(JSON.stringify(diagnostic)).not.toContain(credential);
|
|
}
|
|
const unknown = Object.assign(new Error(`Pi RPC thread_open failed: Authorization: Bearer ${credential} ${jwt}`), { code: 'PI_RPC_RESPONSE_ERROR' });
|
|
const diagnostic = buildPiInitializationDiagnostic(unknown, { stage: 'worker-start' });
|
|
expect(diagnostic.rpcOperation).toBe('thread_open');
|
|
expect(diagnostic.rpcFailureSignature).toBeUndefined();
|
|
expect(JSON.stringify(diagnostic)).not.toContain(credential);
|
|
expect(JSON.stringify(diagnostic)).not.toContain(jwt);
|
|
});
|
|
|
|
it('rejects trailing additions and overlong UTF-8 response tails while retaining only a recognized operation', () => {
|
|
for (const tail of [
|
|
`Cannot read properties of undefined (reading '${credential}')\n${jwt}`,
|
|
`${credential}${'a'.repeat(4_096)} is not a function`,
|
|
`${'私'.repeat(1_000)} is not a function`,
|
|
]) {
|
|
const error = Object.assign(new Error(`Pi RPC thread_open failed: ${tail}`), { code: 'PI_RPC_RESPONSE_ERROR' });
|
|
const diagnostic = buildPiInitializationDiagnostic(error, { stage: 'worker-start' });
|
|
expect(diagnostic.rpcOperation).toBe('thread_open');
|
|
expect(diagnostic.rpcFailureSignature).toBeUndefined();
|
|
expect(Buffer.byteLength(JSON.stringify(diagnostic))).toBeLessThanOrEqual(4_096);
|
|
for (const value of [credential, jwt, '私']) expect(JSON.stringify(diagnostic)).not.toContain(value);
|
|
}
|
|
});
|
|
});
|