47 lines
2.1 KiB
TypeScript
47 lines
2.1 KiB
TypeScript
import { Writable } from 'node:stream';
|
|
import fastify from 'fastify';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { safeHttpLoggerOptions, safeHttpErrorHandler } from '@sammo-ts/common';
|
|
|
|
describe('HTTP log credential boundaries', () => {
|
|
it('logs static routes/status but no query, unmatched path, header, body, stack or cause secrets', async () => {
|
|
let output = '';
|
|
const stream = new Writable({
|
|
write(chunk, _encoding, done) {
|
|
output += String(chunk);
|
|
done();
|
|
},
|
|
});
|
|
const app = fastify({ logger: { ...safeHttpLoggerOptions, stream } });
|
|
app.setErrorHandler(safeHttpErrorHandler);
|
|
app.post('/events', async () => {
|
|
throw Object.assign(new Error('synthetic-error-secret', { cause: new Error('synthetic-cause-secret') }), {
|
|
code: 'P2002',
|
|
detail: 'synthetic-detail-secret',
|
|
});
|
|
});
|
|
try {
|
|
const address = await app.listen({ host: '127.0.0.1', port: 0 });
|
|
const response = await fetch(`${address}/events?token=synthetic-query-secret`, {
|
|
method: 'POST',
|
|
headers: { authorization: 'Bearer synthetic-header-secret', 'content-type': 'application/json' },
|
|
body: JSON.stringify({ password: 'synthetic-body-secret' }),
|
|
});
|
|
expect(await response.json()).toMatchObject({ message: 'Internal server error' });
|
|
await fetch(`${address}/synthetic-path-secret`);
|
|
const logs = output
|
|
.trim()
|
|
.split('\n')
|
|
.map((line) => JSON.parse(line));
|
|
expect(logs.some((entry) => entry.req?.url === '/events')).toBe(true);
|
|
expect(logs.some((entry) => entry.res?.statusCode === 500)).toBe(true);
|
|
expect(logs.some((entry) => entry.err?.code === 'P2002')).toBe(true);
|
|
for (const field of ['query', 'header', 'body', 'error', 'cause', 'detail', 'path']) {
|
|
expect(output).not.toContain(`synthetic-${field}-secret`);
|
|
}
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
});
|