Files
ccs/tests/unit/services/logging/log-redaction-extended.test.ts
T
Tam Nhu Tran 7a22e89046 feat(logging): add structured contract, ALS context, stage helpers, and redaction coverage
Establish the structured-log foundation that downstream modules consume:
- LogStage union (intake|route|auth|dispatch|upstream|transform|respond|cleanup)
- AsyncLocalStorage-backed log-context for end-to-end requestId propagation
- Logger.stage() emit helper with optional latencyMs and structured metadata
- Extended log-redaction patterns for proxy-authorization, x-goog-api-key,
  oauth-code, refresh-token classes; Bearer/Basic/Token value-shape masking
- Public exports: getRecentLogEntries, withRequestContext, runWithRequestId,
  getRequestId

Refs #1141, #1138
2026-04-30 12:59:42 -04:00

140 lines
3.9 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import {
redactArgv,
redactContext,
} from '../../../../src/services/logging/log-redaction';
describe('log redaction (extended sensitive keys)', () => {
const newSensitiveKeys = [
'refresh_token',
'id_token',
'access_token',
'client_secret',
'bearer',
'assertion',
'copilot_token',
'copilot-token',
'cursor_session_key',
'cursor-session-key',
'x-api-key',
'x_goog_api_key',
'proxy-authorization',
'oauth_code',
'auth_code',
'auth_token',
];
it.each(newSensitiveKeys)('redacts %s key (top-level)', (key) => {
const redacted = redactContext({ [key]: 'secret-value', safe: 'ok' });
expect(redacted[key]).toBe('[redacted]');
expect(redacted.safe).toBe('ok');
});
it('redacts new keys nested under headers', () => {
const redacted = redactContext({
headers: {
'x-api-key': 'sk-xxx',
'proxy-authorization': 'Bearer abc',
'copilot-token': 'gho_xxx',
},
});
expect(redacted).toEqual({
headers: {
'x-api-key': '[redacted]',
'proxy-authorization': '[redacted]',
'copilot-token': '[redacted]',
},
});
});
it('redacts Bearer/Basic/Token scheme prefix in raw string values', () => {
const redacted = redactContext({
raw: 'Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig',
basic: 'Basic dXNlcjpwYXNz',
tokenLine: 'Token abc.def',
plain: 'no-scheme-here',
});
expect(redacted.raw).toBe('Bearer [redacted]');
expect(redacted.basic).toBe('Basic [redacted]');
expect(redacted.tokenLine).toBe('Token [redacted]');
expect(redacted.plain).toBe('no-scheme-here');
});
it('passes numeric/boolean values through even if key matches', () => {
// expires_at is not a sensitive key; ensure non-string sensitive values
// would also pass through if pattern matched (defense check).
const redacted = redactContext({
access_token: 'real-secret',
expires_at: 1234567890,
enabled: true,
});
expect(redacted.access_token).toBe('[redacted]');
expect(redacted.expires_at).toBe(1234567890);
expect(redacted.enabled).toBe(true);
});
it('recurses through arrays of objects', () => {
const redacted = redactContext({
tokens: [
{ access_token: 'a' },
{ refresh_token: 'b' },
{ id_token: 'c', meta: 'kept' },
],
});
expect(redacted).toEqual({
tokens: [
{ access_token: '[redacted]' },
{ refresh_token: '[redacted]' },
{ id_token: '[redacted]', meta: 'kept' },
],
});
});
it('fuzz-style: many random token-shaped keys all redact', () => {
const variants = ['token', 'auth_token', 'refresh-token', 'id-token', 'X-API-KEY'];
for (const v of variants) {
const out = redactContext({ [v]: 'secret' });
expect(out[v]).toBe('[redacted]');
}
});
});
describe('redactArgv', () => {
it('redacts the value following sensitive flags', () => {
expect(redactArgv(['--api-key', 'secret', '--other', 'ok'])).toEqual([
'--api-key',
'[redacted]',
'--other',
'ok',
]);
});
it('redacts multiple sensitive flags', () => {
expect(
redactArgv(['--token', 'a', '--secret', 'b', '--bearer', 'c', '--keep', 'd'])
).toEqual(['--token', '[redacted]', '--secret', '[redacted]', '--bearer', '[redacted]', '--keep', 'd']);
});
it('passes argv unchanged when no sensitive flags present', () => {
expect(redactArgv(['build', '--watch', '--out', 'dist'])).toEqual([
'build',
'--watch',
'--out',
'dist',
]);
});
it('handles trailing sensitive flag with no value', () => {
expect(redactArgv(['--api-key'])).toEqual(['--api-key']);
});
it('redacts kebab and snake case flag variants', () => {
expect(redactArgv(['--api_key', 'x', '--auth-token', 'y'])).toEqual([
'--api_key',
'[redacted]',
'--auth-token',
'[redacted]',
]);
});
});