Skip to content

Commit baffdb8

Browse files
committed
fix: bound sanitized error causes after redaction
1 parent c3d998d commit baffdb8

4 files changed

Lines changed: 24 additions & 11 deletions

File tree

‎packages/kernel/src/redaction.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const SECRET_TOKEN_RE =
55
const SENSITIVE_ASSIGNMENT_RE =
66
/\b([a-z0-9_-]*(?:api[_-]?key|token|secret|password|user[_-]?code|device[_-]?code|refresh[_-]?credential)[a-z0-9_-]*)(\s*[=:]\s*)("[^"]*"|'[^']*'|\S+)/gi;
77
const URL_RE = /https?:\/\/[^\s"'<>]+/gi;
8+
const REDACTED_STRING_MAX_LENGTH = 400;
9+
const TRUNCATION_SUFFIX = '...<truncated>';
810

911
export function redactDiagnosticData<T>(input: T): T {
1012
return redactValue(input, new WeakSet<object>()) as T;
@@ -48,7 +50,7 @@ function redactValue(value: unknown, seen: WeakSet<object>, keyHint?: string): u
4850

4951
function redactString(value: string, keyHint?: string): string {
5052
const trimmed = value.trim();
51-
if (!trimmed) return value;
53+
if (!trimmed) return boundRedactedString(value);
5254
if (keyHint && SENSITIVE_KEY_RE.test(keyHint)) return '[REDACTED]';
5355
let output = redactUrls(trimmed);
5456
output = output.replace(SECRET_TOKEN_RE, '[REDACTED]');
@@ -60,9 +62,12 @@ function redactString(value: string, keyHint?: string): string {
6062
return `${key}${separator}[REDACTED]`;
6163
},
6264
);
63-
if (output !== trimmed) return output;
64-
if (trimmed.length > 400) return `${trimmed.slice(0, 200)}...<truncated>`;
65-
return trimmed;
65+
return boundRedactedString(output);
66+
}
67+
68+
function boundRedactedString(value: string): string {
69+
if (value.length <= REDACTED_STRING_MAX_LENGTH) return value;
70+
return `${value.slice(0, REDACTED_STRING_MAX_LENGTH - TRUNCATION_SUFFIX.length)}${TRUNCATION_SUFFIX}`;
6671
}
6772

6873
function redactUrls(value: string): string {

‎src/__tests__/test-file-size-ratchet.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const TRIPWIRE_LINES = 1_000;
3434
// Exact current lengths. Lower a pin when its file shrinks; never raise one — extract instead.
3535
const PINNED_TEST_FILE_LINES: Readonly<Record<string, number>> = Object.freeze({
3636
'src/__tests__/remote-connection.test.ts': 2973,
37-
'src/daemon/handlers/__tests__/snapshot-handler.test.ts': 2640,
37+
'src/daemon/handlers/__tests__/snapshot-handler.test.ts': 2638,
3838
'src/commands/interaction/runtime/settle.test.ts': 2361,
3939
'src/daemon/handlers/__tests__/session-replay-runtime-maestro.test.ts': 2031,
4040
'src/platforms/apple/core/__tests__/runner-session.test.ts': 2001,

‎src/client/client-normalizers.test.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { normalizeOpenForegroundComposition } from './client-normalizers.ts';
44

55
test('embedded daemon errors sanitize an untrusted cause before client exposure', () => {
66
const secret = 'adc_live_remote-secret';
7+
const oversizedCode = `token=${secret} ${'c'.repeat(500)}`;
78
const message =
89
`request to https://user:[email protected]/app?token=${secret} failed: bearer ${secret} ` +
910
'x'.repeat(500);
@@ -12,14 +13,17 @@ test('embedded daemon errors sanitize an untrusted cause before client exposure'
1213
initialSnapshotError: {
1314
code: 'COMMAND_FAILED',
1415
message: 'capture failed',
15-
cause: { code: `token=${secret}`, message },
16+
cause: { code: oversizedCode, message },
1617
},
1718
});
1819

1920
const cause = result.initialSnapshotError?.cause;
2021
assert.ok(cause);
2122
assert.equal(JSON.stringify(cause).includes(secret), false);
2223
assert.match(cause.message, /REDACTED/);
23-
assert.ok(cause.message.length < message.length);
24-
assert.equal(cause.code, 'token=[REDACTED]');
24+
assert.equal(cause.message.length, 400);
25+
assert.match(cause.message, /<truncated>$/);
26+
assert.match(cause.code ?? '', /^token=\[REDACTED\]/);
27+
assert.equal(cause.code?.length, 400);
28+
assert.match(cause.code ?? '', /<truncated>$/);
2529
});

‎src/daemon/client/daemon-client-rpc.test.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { handleDaemonHttpResponseBody } from './daemon-client-rpc.ts';
55

66
test('HTTP RPC errors sanitize an untrusted cause before rehydration', () => {
77
const secret = 'adc_agent_remote-secret';
8+
const oversizedCode = `apiKey=${secret} ${'c'.repeat(500)}`;
89
const message =
910
`download https://user:[email protected]/app?token=${secret} failed: bearer ${secret} ` +
1011
'x'.repeat(500);
@@ -16,7 +17,7 @@ test('HTTP RPC errors sanitize an untrusted cause before rehydration', () => {
1617
data: {
1718
code: 'COMMAND_FAILED',
1819
message: 'remote download failed',
19-
cause: { code: `apiKey=${secret}`, message },
20+
cause: { code: oversizedCode, message },
2021
},
2122
},
2223
}),
@@ -41,6 +42,9 @@ test('HTTP RPC errors sanitize an untrusted cause before rehydration', () => {
4142
assert.ok(cause);
4243
assert.equal(JSON.stringify(cause).includes(secret), false);
4344
assert.match(cause.message, /REDACTED/);
44-
assert.ok(cause.message.length < message.length);
45-
assert.equal(cause.code, 'apiKey=[REDACTED]');
45+
assert.equal(cause.message.length, 400);
46+
assert.match(cause.message, /<truncated>$/);
47+
assert.match(cause.code ?? '', /^apiKey=\[REDACTED\]/);
48+
assert.equal(cause.code?.length, 400);
49+
assert.match(cause.code ?? '', /<truncated>$/);
4650
});

0 commit comments

Comments
 (0)