mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
724c88c201
A mechanical prettier --write over server/src, server/tests, server/scripts and shared/src with the workspaces' .prettierrc; nothing but layout and import order changes, and every file keeps the same set of imports. shared is formatted throughout and its format baseline is empty. The server baseline keeps the 35 files that Prettier would grow past their lint:size limit (they need a split first, which is not a formatting change), and the generated entity files stay as their generator writes them.
176 lines
7.0 KiB
TypeScript
176 lines
7.0 KiB
TypeScript
import { redact, SENSITIVE_KEYS } from '../../../src/middleware/globalMiddleware';
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
describe('globalMiddleware request-log redaction', () => {
|
|
it('redacts secretAccessKey (any casing) — storage admin PUT bodies land in the same debug log line', () => {
|
|
expect(redact({ secretAccessKey: 'sk-super-secret' })).toEqual({ secretAccessKey: '[REDACTED]' });
|
|
expect(redact({ SecretAccessKey: 'sk-super-secret' })).toEqual({ SecretAccessKey: '[REDACTED]' });
|
|
expect(redact({ secretaccesskey: 'sk-super-secret' })).toEqual({ secretaccesskey: '[REDACTED]' });
|
|
});
|
|
|
|
it('lists secretaccesskey in the lowercase lookup set (lookup lowercases the key first)', () => {
|
|
expect(SENSITIVE_KEYS.has('secretaccesskey')).toBe(true);
|
|
});
|
|
|
|
it('still redacts every pre-existing sensitive key (no regression)', () => {
|
|
const input = {
|
|
password: 'p',
|
|
new_password: 'p',
|
|
current_password: 'p',
|
|
token: 't',
|
|
jwt: 'j',
|
|
authorization: 'a',
|
|
cookie: 'c',
|
|
client_secret: 's',
|
|
mfa_token: 'm',
|
|
code: '123456',
|
|
smtp_pass: 's',
|
|
};
|
|
const out = redact(input) as Record<string, unknown>;
|
|
for (const key of Object.keys(input)) {
|
|
expect(out[key]).toBe('[REDACTED]');
|
|
}
|
|
});
|
|
|
|
it('redacts nested objects and arrays, leaving non-sensitive fields untouched', () => {
|
|
const out = redact({
|
|
name: 'off-box',
|
|
options: { secretAccessKey: 'sk-1', accessKeyId: 'ak-1' },
|
|
backends: [{ secretAccessKey: 'sk-2' }, { accessKeyId: 'ak-2' }],
|
|
});
|
|
expect(out).toEqual({
|
|
name: 'off-box',
|
|
options: { secretAccessKey: '[REDACTED]', accessKeyId: 'ak-1' },
|
|
backends: [{ secretAccessKey: '[REDACTED]' }, { accessKeyId: 'ak-2' }],
|
|
});
|
|
});
|
|
|
|
it('redacts secret-shaped keys by suffix — the OAuth token body and the settings API keys', () => {
|
|
const out = redact({
|
|
refresh_token: 'rt-1',
|
|
code_verifier: 'cv-1',
|
|
llm_api_key: 'sk-1',
|
|
mapbox_access_token: 'pk-1',
|
|
grant_type: 'refresh_token',
|
|
}) as Record<string, unknown>;
|
|
expect(out.refresh_token).toBe('[REDACTED]');
|
|
expect(out.code_verifier).toBe('[REDACTED]');
|
|
expect(out.llm_api_key).toBe('[REDACTED]');
|
|
expect(out.mapbox_access_token).toBe('[REDACTED]');
|
|
expect(out.grant_type).toBe('refresh_token');
|
|
});
|
|
|
|
it('redacts the value of a {key, value} settings body when the key names a secret', () => {
|
|
expect(redact({ key: 'carto_api_key', value: 'carto-secret' })).toEqual({
|
|
key: 'carto_api_key',
|
|
value: '[REDACTED]',
|
|
});
|
|
expect(redact({ key: 'mapbox_access_token', value: 'pk-1' })).toEqual({
|
|
key: 'mapbox_access_token',
|
|
value: '[REDACTED]',
|
|
});
|
|
// A harmless setting keeps its value: the name is what decides.
|
|
expect(redact({ key: 'map_tile_url', value: 'https://tiles.example/{z}/{x}/{y}.png' })).toEqual({
|
|
key: 'map_tile_url',
|
|
value: 'https://tiles.example/{z}/{x}/{y}.png',
|
|
});
|
|
});
|
|
|
|
it('matches the suffix on the end of the key only, so identifiers stay readable', () => {
|
|
expect(redact({ accessKeyId: 'ak-1', tokenCount: 5 })).toEqual({ accessKeyId: 'ak-1', tokenCount: 5 });
|
|
});
|
|
|
|
it('hides the Web Push subscription body: the endpoint capability URL and both keys', () => {
|
|
// POST /api/notifications/push/subscriptions, as PushSubscription.toJSON() hands it over.
|
|
expect(
|
|
redact({
|
|
subscription: {
|
|
endpoint: 'https://fcm.googleapis.com/fcm/send/device-capability',
|
|
expirationTime: null,
|
|
keys: { p256dh: 'BNcRdreALRFXTkOOUHK1EtK2wtaz5Ry4YfYCA', auth: 'tBHItJI5svbpez7KI4CCXg' },
|
|
},
|
|
}),
|
|
).toEqual({
|
|
subscription: {
|
|
endpoint: '[REDACTED]',
|
|
expirationTime: null,
|
|
keys: { p256dh: '[REDACTED]', auth: '[REDACTED]' },
|
|
},
|
|
});
|
|
});
|
|
|
|
it('hides the subscription the service worker posts again after the browser renewed it', () => {
|
|
// sw-push.js on pushsubscriptionchange: the same route and shape, and a renewed
|
|
// subscription may carry a real expiry instead of null.
|
|
expect(
|
|
redact({
|
|
subscription: {
|
|
endpoint: 'https://updates.push.services.mozilla.com/wpush/v2/renewed-capability',
|
|
expirationTime: 1767225600000,
|
|
keys: { p256dh: 'BOr1renewedPublicKey', auth: 'renewedAuthSecret' },
|
|
},
|
|
}),
|
|
).toEqual({
|
|
subscription: {
|
|
endpoint: '[REDACTED]',
|
|
expirationTime: 1767225600000,
|
|
keys: { p256dh: '[REDACTED]', auth: '[REDACTED]' },
|
|
},
|
|
});
|
|
});
|
|
|
|
it('hides the endpoint of a subscription that arrives without its keys', () => {
|
|
// The route refuses this body with a 400, but the request is logged all the same.
|
|
expect(
|
|
redact({ subscription: { endpoint: 'https://fcm.googleapis.com/fcm/send/keyless', expirationTime: null } }),
|
|
).toEqual({ subscription: { endpoint: '[REDACTED]', expirationTime: null } });
|
|
expect(redact({ subscription: { endpoint: 'https://fcm.googleapis.com/fcm/send/lone' } })).toEqual({
|
|
subscription: { endpoint: '[REDACTED]' },
|
|
});
|
|
expect(
|
|
redact({ subscription: { endpoint: 'https://fcm.googleapis.com/fcm/send/odd-keys', keys: 'not-an-object' } }),
|
|
).toEqual({ subscription: { endpoint: '[REDACTED]', keys: 'not-an-object' } });
|
|
});
|
|
|
|
it('hides the endpoint of the body that forgets a push device', () => {
|
|
// DELETE /api/notifications/push/subscriptions (logout, switching push off, the worker's renewal).
|
|
expect(redact({ endpoint: 'https://web.push.apple.com/QGx-device' })).toEqual({ endpoint: '[REDACTED]' });
|
|
expect(redact({ Endpoint: 'https://web.push.apple.com/QGx-device' })).toEqual({ Endpoint: '[REDACTED]' });
|
|
});
|
|
|
|
it('keeps an endpoint that is an ordinary setting readable, like the S3 backend URL', () => {
|
|
expect(
|
|
redact({ endpoint: 'http://127.0.0.1:9000', bucket: 'trek', region: 'us-east-1', secretAccessKey: 'sk' }),
|
|
).toEqual({
|
|
endpoint: 'http://127.0.0.1:9000',
|
|
bucket: 'trek',
|
|
region: 'us-east-1',
|
|
secretAccessKey: '[REDACTED]',
|
|
});
|
|
expect(redact({ endpoint: 'https://s3.example.com', forcePathStyle: true })).toEqual({
|
|
endpoint: 'https://s3.example.com',
|
|
forcePathStyle: true,
|
|
});
|
|
});
|
|
|
|
it('decides p256dh and auth by name wherever they appear, and nothing near them by accident', () => {
|
|
// Only the push subscription sends either field today; an `auth` anywhere
|
|
// else would carry credentials too. The fields around them stay readable.
|
|
expect(redact({ p256dh: 'BPub', Auth: 'secret', auth_method: 'basic', author: 'Ada' })).toEqual({
|
|
p256dh: '[REDACTED]',
|
|
Auth: '[REDACTED]',
|
|
auth_method: 'basic',
|
|
author: 'Ada',
|
|
});
|
|
expect(redact({ auth: { user: 'mailer', pass: 'hunter2' } })).toEqual({ auth: '[REDACTED]' });
|
|
});
|
|
|
|
it('passes non-object values through untouched', () => {
|
|
expect(redact('plain string')).toBe('plain string');
|
|
expect(redact(42)).toBe(42);
|
|
expect(redact(null)).toBe(null);
|
|
expect(redact(undefined)).toBe(undefined);
|
|
});
|
|
});
|