import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { OBJECT_KEY_REGEX, EML_OBJECT_KEY_REGEX, presignDownload, presignUpload, B2InvalidObjectKeyError, type B2Config, _B2_INTERNALS, } from './client'; const FIXTURE_CFG: B2Config = { keyId: 'AKIA-FIXTURE', secret: 'sec-fixture', bucket: 'wulf-audits', region: 'us-west-002', endpoint: 's3.us-west-002.backblazeb2.com', }; describe('OBJECT_KEY_REGEX', () => { it('accepts the production shape', () => { expect( OBJECT_KEY_REGEX.test( 'ba03268b-5528-4dde-ad76-867523446ecd/unknown-server/eventlogs_20251202_173301.json.gz' ) ).toBe(true); expect( OBJECT_KEY_REGEX.test( 'site_uuid_short/MISYS-SQL/eventlogs_20260502_120000.json.gz' ) ).toBe(true); }); it('rejects path traversal', () => { expect(OBJECT_KEY_REGEX.test('../etc/passwd')).toBe(false); expect(OBJECT_KEY_REGEX.test('site/../../escape/eventlogs_1.json.gz')).toBe(false); }); it('rejects wrong shapes', () => { expect(OBJECT_KEY_REGEX.test('site/host/something.json.gz')).toBe(false); // missing eventlogs_ prefix expect(OBJECT_KEY_REGEX.test('eventlogs_1.json.gz')).toBe(false); // missing prefix dirs expect(OBJECT_KEY_REGEX.test('site/host/eventlogs_1.json')).toBe(false); // missing .gz expect(OBJECT_KEY_REGEX.test('site host/x/eventlogs_1.json.gz')).toBe(false); // space in client id }); }); describe('presignDownload + presignUpload', () => { const realDate = Date; beforeEach(() => { // Pin time so signatures are deterministic. const fixed = new Date('2026-05-02T20:00:00.000Z'); vi.stubGlobal( 'Date', class extends realDate { constructor(...args: unknown[]) { if (args.length === 0) { super(fixed.getTime()); } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any super(...(args as [any])); } } static now() { return fixed.getTime(); } } as unknown as DateConstructor ); }); afterEach(() => { vi.unstubAllGlobals(); }); it('produces a stable presigned GET URL', () => { const url = presignDownload( 'site/host/eventlogs_20260502_120000.json.gz', 600, FIXTURE_CFG ); expect(url).toContain('https://s3.us-west-002.backblazeb2.com/wulf-audits/'); expect(url).toContain('X-Amz-Algorithm=AWS4-HMAC-SHA256'); expect(url).toContain('X-Amz-Credential=AKIA-FIXTURE'); expect(url).toContain('X-Amz-Date=20260502T200000Z'); expect(url).toContain('X-Amz-Expires=600'); expect(url).toContain('X-Amz-SignedHeaders=host'); expect(url).toMatch(/X-Amz-Signature=[a-f0-9]{64}$/); }); it('produces a presigned PUT URL with PUT method scope', () => { const url = presignUpload( 'site/host/eventlogs_20260502_120000.json.gz', 1800, FIXTURE_CFG ); expect(url).toContain('X-Amz-Expires=1800'); expect(url).toMatch(/X-Amz-Signature=[a-f0-9]{64}$/); }); it('rejects path-traversal object keys', () => { expect(() => presignDownload('../etc/eventlogs_1.json.gz', 600, FIXTURE_CFG) ).toThrow(B2InvalidObjectKeyError); }); it('different methods produce different signatures (sanity check)', () => { const get = presignDownload( 'site/host/eventlogs_20260502_120000.json.gz', 600, FIXTURE_CFG ); const put = presignUpload( 'site/host/eventlogs_20260502_120000.json.gz', 600, FIXTURE_CFG ); const sigGet = get.split('X-Amz-Signature=')[1]; const sigPut = put.split('X-Amz-Signature=')[1]; expect(sigGet).not.toBe(sigPut); }); }); describe('EML_OBJECT_KEY_REGEX', () => { it('accepts phishing//.eml', () => { expect( EML_OBJECT_KEY_REGEX.test('phishing/ba03268b-5528-4dde-ad76-867523446ecd/555.eml') ).toBe(true); expect(EML_OBJECT_KEY_REGEX.test('phishing/report_1/attachment_1.eml')).toBe(true); }); it('rejects path traversal', () => { expect(EML_OBJECT_KEY_REGEX.test('phishing/../evil.eml')).toBe(false); expect(EML_OBJECT_KEY_REGEX.test('phishing/report/../../escape.eml')).toBe(false); }); it('rejects wrong extension and the LogLift shape', () => { expect(EML_OBJECT_KEY_REGEX.test('phishing/a/b.json')).toBe(false); expect( EML_OBJECT_KEY_REGEX.test( 'ba03268b-5528-4dde-ad76-867523446ecd/unknown-server/eventlogs_20251202_173301.json.gz' ) ).toBe(false); }); }); describe('presignUpload with a custom keyRegex', () => { it('succeeds for a valid .eml key validated against EML_OBJECT_KEY_REGEX', () => { const url = presignUpload( 'phishing/report_1/attachment_1.eml', 1800, FIXTURE_CFG, EML_OBJECT_KEY_REGEX ); expect(url).toContain('X-Amz-Expires=1800'); }); it('throws B2InvalidObjectKeyError for a LogLift-shaped key when validated against EML_OBJECT_KEY_REGEX', () => { expect(() => presignUpload( 'site/host/eventlogs_20260502_120000.json.gz', 1800, FIXTURE_CFG, EML_OBJECT_KEY_REGEX ) ).toThrow(B2InvalidObjectKeyError); }); it('still validates against OBJECT_KEY_REGEX by default (existing LogLift call sites unchanged)', () => { expect(() => presignUpload('phishing/report_1/attachment_1.eml', 1800, FIXTURE_CFG) ).toThrow(B2InvalidObjectKeyError); }); }); describe('deriveSigningKey', () => { it('produces a 32-byte HMAC-SHA256 chain', () => { const k = _B2_INTERNALS.deriveSigningKey( 'sec-fixture', '20260502', 'us-west-002', 's3' ); expect(k.length).toBe(32); }); });