Files
accounted/lib/observability/__tests__/redact.test.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

104 lines
3.8 KiB
TypeScript

/**
* The redaction primitives themselves.
*
* sink.test.ts and lib/__tests__/logger-sink.test.ts cover redaction on the
* way INTO the sink; this file locks in the primitive's own contract: which
* shapes are scrubbed, that scrubbing is idempotent, and that serialized
* errors keep a (redacted) stack in every environment: the sink only runs in
* production, and a stackless event is useless to group on.
*/
import { describe, it, expect, afterEach, vi } from 'vitest'
import { redact, redactString, REDACTED } from '../redact'
afterEach(() => {
vi.unstubAllEnvs()
})
describe('redactString', () => {
it('replaces the WHOLE string when a personnummer appears', () => {
expect(redactString('payment for 800101-1234 failed')).toBe(REDACTED)
expect(redactString('customer 19800101-1234')).toBe(REDACTED)
})
it('preserves UUIDs', () => {
const s = 'company 57484518-3409-4b29-9d23-5d22f08bda63 ok'
expect(redactString(s)).toBe(s)
})
it('scrubs gnubok API keys in place, keeping the rest of the line', () => {
expect(redactString('auth failed for key gnubok_sk_live_abc123 on /v1')).toBe(
'auth failed for key [REDACTED_API_KEY] on /v1',
)
expect(redactString('invite gnubok_inv_XyZ_9 expired')).toBe(
'invite [REDACTED_API_KEY] expired',
)
})
it('scrubs Swedish IBANs, compact and space-grouped', () => {
expect(redactString('payout to SE4550000000058398257466 bounced')).toBe(
'payout to [REDACTED_IBAN] bounced',
)
expect(redactString('iban SE45 5000 0000 0583 9825 7466 rejected')).toBe(
'iban [REDACTED_IBAN] rejected',
)
})
it('scrubs email addresses in place', () => {
expect(redactString('reminder sent to anna.svensson@example.com just now')).toBe(
'reminder sent to [REDACTED_EMAIL] just now',
)
})
it('does not mistake package specs for emails', () => {
const s = 'installing @anthropic-ai/bedrock-sdk@0.29.1'
expect(redactString(s)).toBe(s)
})
it('scrubs several kinds of secret in one string', () => {
expect(
redactString('key gnubok_sk_live_secret1 for anna@example.com on SE4550000000058398257466'),
).toBe('key [REDACTED_API_KEY] for [REDACTED_EMAIL] on [REDACTED_IBAN]')
})
it('is idempotent: a second pass changes nothing', () => {
const inputs = [
'key gnubok_sk_live_secret1 for anna@example.com on SE4550000000058398257466',
'payment for 800101-1234 failed',
'plain harmless message',
]
for (const input of inputs) {
const once = redactString(input)
expect(redactString(once)).toBe(once)
}
})
})
describe('redact() on Error instances', () => {
it('keeps a stack on the serialized error in production', () => {
vi.stubEnv('NODE_ENV', 'production')
const err = new Error('clean failure')
const out = redact(err) as { name: string; message: string; stack?: string }
expect(out.name).toBe('Error')
expect(out.message).toBe('clean failure')
expect(typeof out.stack).toBe('string')
expect(out.stack).toContain('clean failure')
})
it('redacts the stack like any other string', () => {
// The first stack line carries the message, so a personnummer in the
// message means the whole stack is nuked (whole-string personnummer rule).
const pii = new Error('no match for 800101-1234')
const outPii = redact(pii) as { message: string; stack?: string }
expect(outPii.message).toBe(REDACTED)
expect(outPii.stack).toBe(REDACTED)
// Substring secrets are scrubbed in place: the frames stay readable.
const keyErr = new Error('bad key gnubok_sk_live_abc')
const outKey = redact(keyErr) as { message: string; stack?: string }
expect(outKey.message).toBe('bad key [REDACTED_API_KEY]')
expect(outKey.stack).toContain('[REDACTED_API_KEY]')
expect(outKey.stack).not.toContain('gnubok_sk_live_abc')
expect(outKey.stack).toContain('at ')
})
})