mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-25 08:33:55 +00:00
fix(daemon): harden workspace memory detail extraction
This commit is contained in:
parent
6ba0ba7994
commit
d1cbf11abf
2 changed files with 40 additions and 10 deletions
|
|
@ -46,6 +46,12 @@ describe('extractRememberErrorDetails', () => {
|
|||
expect(extractRememberErrorDetails({ data: 'raw data detail' })).toBe(
|
||||
'raw data detail',
|
||||
);
|
||||
expect(
|
||||
extractRememberErrorDetails({
|
||||
data: 'ERR_BRIDGE_INTERNAL',
|
||||
message: 'Connection to memory service refused',
|
||||
}),
|
||||
).toBe('Connection to memory service refused');
|
||||
expect(
|
||||
extractRememberErrorDetails({
|
||||
data: { message: 'provider rejected the request' },
|
||||
|
|
@ -56,6 +62,9 @@ describe('extractRememberErrorDetails', () => {
|
|||
cause: new Error('nested failure reason'),
|
||||
}),
|
||||
).toBe('nested failure reason');
|
||||
expect(extractRememberErrorDetails({ cause: 'string cause reason' })).toBe(
|
||||
'string cause reason',
|
||||
);
|
||||
expect(extractRememberErrorDetails('raw string error')).toBe(
|
||||
'raw string error',
|
||||
);
|
||||
|
|
@ -71,12 +80,14 @@ describe('extractRememberErrorDetails', () => {
|
|||
});
|
||||
|
||||
it('normalizes hidden separators before redacting credentials', () => {
|
||||
const details = extractRememberErrorDetails(
|
||||
new Error('Authorization: Bearer\u200bsecret-token-value'),
|
||||
);
|
||||
for (const separator of ['\u200b', '\u2060', '\u2064']) {
|
||||
const details = extractRememberErrorDetails(
|
||||
new Error(`Authorization: Bearer${separator}secret-token-value`),
|
||||
);
|
||||
|
||||
expect(details).toBe('Authorization: <redacted>');
|
||||
expect(details).not.toContain('secret-token-value');
|
||||
expect(details).toBe('Authorization: <redacted>');
|
||||
expect(details).not.toContain('secret-token-value');
|
||||
}
|
||||
});
|
||||
|
||||
it('normalizes line separators before redacting credentials', () => {
|
||||
|
|
@ -103,6 +114,19 @@ describe('extractRememberErrorDetails', () => {
|
|||
expect(extractRememberErrorDetails(cyclic)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('limits cause traversal depth', () => {
|
||||
const root: Record<string, unknown> = {};
|
||||
let current = root;
|
||||
for (let index = 0; index < 60; index += 1) {
|
||||
const next: Record<string, unknown> = {};
|
||||
current['cause'] = next;
|
||||
current = next;
|
||||
}
|
||||
current['message'] = 'too deep';
|
||||
|
||||
expect(extractRememberErrorDetails(root)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('caps long details', () => {
|
||||
const details = extractRememberErrorDetails(new Error('x'.repeat(1100)));
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { redactLogCredentials } from '@qwen-code/acp-bridge/logRedaction';
|
||||
|
||||
const MAX_REMEMBER_ERROR_DETAILS_CHARS = 1000;
|
||||
const MAX_REMEMBER_ERROR_CAUSE_DEPTH = 50;
|
||||
|
||||
function errorCodeFromRecord(
|
||||
record: Record<string, unknown>,
|
||||
|
|
@ -43,11 +44,11 @@ export function extractRememberErrorCode(
|
|||
function detailFromRecord(
|
||||
record: Record<string, unknown>,
|
||||
seen: WeakSet<object>,
|
||||
depth: number,
|
||||
): string | undefined {
|
||||
// Bridge errors carry the best failure reason in `data`; top-level
|
||||
// `message` and `cause` are generic fallbacks.
|
||||
const data = record['data'];
|
||||
if (typeof data === 'string' && data.length > 0) return data;
|
||||
if (data && typeof data === 'object') {
|
||||
const dataRecord = data as Record<string, unknown>;
|
||||
const details = dataRecord['details'];
|
||||
|
|
@ -59,9 +60,11 @@ function detailFromRecord(
|
|||
const message = record['message'];
|
||||
if (typeof message === 'string' && message.length > 0) return message;
|
||||
|
||||
if (typeof data === 'string' && data.length > 0) return data;
|
||||
|
||||
const cause = record['cause'];
|
||||
if (cause && typeof cause === 'object') {
|
||||
return rawRememberErrorDetails(cause, seen);
|
||||
if (cause != null) {
|
||||
return rawRememberErrorDetails(cause, seen, depth + 1);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
|
@ -70,12 +73,14 @@ function detailFromRecord(
|
|||
function rawRememberErrorDetails(
|
||||
err: unknown,
|
||||
seen: WeakSet<object>,
|
||||
depth: number,
|
||||
): string | undefined {
|
||||
if (depth > MAX_REMEMBER_ERROR_CAUSE_DEPTH) return undefined;
|
||||
if (typeof err === 'string' && err.length > 0) return err;
|
||||
if (!err || typeof err !== 'object') return undefined;
|
||||
if (seen.has(err)) return undefined;
|
||||
seen.add(err);
|
||||
return detailFromRecord(err as Record<string, unknown>, seen);
|
||||
return detailFromRecord(err as Record<string, unknown>, seen, depth);
|
||||
}
|
||||
|
||||
function shouldReplaceControlChar(code: number): boolean {
|
||||
|
|
@ -84,6 +89,7 @@ function shouldReplaceControlChar(code: number): boolean {
|
|||
(code >= 127 && code <= 159) ||
|
||||
(code >= 0x200b && code <= 0x200f) ||
|
||||
(code >= 0x2028 && code <= 0x202e) ||
|
||||
(code >= 0x2060 && code <= 0x2064) ||
|
||||
(code >= 0x2066 && code <= 0x2069) ||
|
||||
code === 0xfeff
|
||||
);
|
||||
|
|
@ -119,7 +125,7 @@ function sanitizeRememberErrorDetails(details: string): string | undefined {
|
|||
}
|
||||
|
||||
export function extractRememberErrorDetails(err: unknown): string | undefined {
|
||||
const raw = rawRememberErrorDetails(err, new WeakSet<object>());
|
||||
const raw = rawRememberErrorDetails(err, new WeakSet<object>(), 0);
|
||||
if (!raw) return undefined;
|
||||
return sanitizeRememberErrorDetails(raw);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue