// SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2026 Bryan Joshua Pedini /** * PLAN §12 core item 10: audit rows verified directly in SQLite — exact token/origin/operation/ * time values, one row per authenticated GET only, written before the Docker call (so Docker * failures are audited), origin = TCP peer (forwarding headers ignored), IPv4/IPv6 origins. */ import { test, expect, assertDbInvariants, expectContainersResponse, expectErrorResponse, msToNanos, readOperations, rfc3339ToNanos, RFC3339NANO_UTC_RE, type DockerBackend, type FakeDocker, } from '../../harness'; test('each authenticated GET creates exactly one row with exact values, in request order', async ({ cli, startServer, http }) => { const a = await cli.tokenNew('a'); const b = await cli.tokenNew('b'); const server = await startServer(); const plan = [a, b, a, a, b, a]; const windows: Array<[bigint, bigint]> = []; for (const [i, tok] of plan.entries()) { const t0 = msToNanos(Date.now() - 1); const r = i % 2 ? await http.curl({ url: server.url(), token: tok }) : await http.get(server.url(), { token: tok }); windows.push([t0, msToNanos(Date.now() + 1)]); expectContainersResponse(r); } expect((await server.stop()).exitCode).toBe(0); const ops = readOperations(server.dbPath); expect(ops.map((o) => [o.token, o.origin, o.operation])).toEqual(plan.map((t) => [t, '127.0.0.1', 'GET containers'])); for (const [i, o] of ops.entries()) { expect(o.time).toMatch(RFC3339NANO_UTC_RE); const t = rfc3339ToNanos(o.time); expect(t >= windows[i][0] && t <= windows[i][1], `row ${i} time ${o.time} inside its request window`).toBe(true); if (i > 0) expect(o.rowid).toBeGreaterThan(ops[i - 1].rowid); } assertDbInvariants(server.dbPath, { tokenCount: 2, operationCount: plan.length, auditBeforeDeletion: true }); }); test('unsuccessful requests never create rows (401 variants, 404, 405, Go-level rejects)', async ({ cli, startServer, http }) => { const tok = await cli.tokenNew(); const dead = await cli.tokenNew(); await cli.tokenDelete(dead); const server = await startServer(); expectErrorResponse(await http.get(server.url()), 401); expectErrorResponse(await http.get(server.url(), { token: dead }), 401); expectErrorResponse(await http.get(server.url(), { token: tok + 'x' }), 401); expectErrorResponse(await http.get(server.url(), { headers: { Authorization: `Basic ${tok}` } }), 401); expectErrorResponse(await http.curl({ url: server.url(), headers: [`Authorization: Bearer ${tok}`, `Authorization: Bearer ${tok}`] }), 401); expectErrorResponse(await http.get(server.url('/api/v1/containers/'), { token: tok }), 404); expectErrorResponse(await http.fetch(server.url(), { method: 'POST', token: tok }), 405); const bad = await http.raw({ host: server.connectHost, port: server.port, data: `GET /api/v1/containers HTTP/1.1\r\nAuthorization: Bearer ${tok}\r\n\r\n` }); // no Host → 400 expect(bad.response!.status).toBe(400); expect((await server.stop()).exitCode).toBe(0); expect(readOperations(server.dbPath)).toEqual([]); assertDbInvariants(server.dbPath, { operationCount: 0 }); }); test('authenticated requests are audited even when Docker fails (row written before the Docker call)', async ({ cli, startServer, http, ws }) => { const tok = await cli.tokenNew(); const backends: Array<[string, DockerBackend | ((f: FakeDocker) => void)]> = [ ['engine 500', (f) => f.error500()], ['connection dropped', (f) => f.destroy()], ['malformed JSON', (f) => f.badJson()], ['engine 404', (f) => f.error(404, 'page not found')], ['socket inaccessible (EACCES)', 'inaccessible'], ['socket missing (ENOENT)', 'missing'], ]; let expected = 0; for (const [name, b] of backends) { await test.step(name, async () => { const server = typeof b === 'function' ? await startServer({ logLevel: 'DEBUG' }) : await startServer({ docker: b, logLevel: 'DEBUG' }); if (typeof b === 'function') b(server.fakeDocker!); const r = await http.get(server.url(), { token: tok }); expectErrorResponse(r, 500); expected++; expect((await server.stop()).exitCode).toBe(0); const logs = server.logs(); const audit = logs.find({ msg: 'audit row inserted' }); const dockerStart = logs.find({ msg: 'docker request started' }); expect(audit && dockerStart && audit.line < dockerStart.line, 'audit insert logged before the Docker request').toBe(true); expect(logs.has({ level: 'ERROR', msg: 'docker request failed' })).toBe(true); expect(readOperations(server.dbPath)).toHaveLength(expected); }); } assertDbInvariants(ws.dbPath, { operationCount: expected }); }); test('forwarding headers cannot spoof the audited origin', async ({ cli, startServer, http }) => { const tok = await cli.tokenNew(); const server = await startServer(); const spoof: Array> = [ { 'X-Forwarded-For': '203.0.113.7' }, { 'X-Forwarded-For': '203.0.113.7, 198.51.100.1' }, { Forwarded: 'for=203.0.113.7;proto=https' }, { 'X-Real-IP': '203.0.113.7' }, { 'True-Client-IP': '203.0.113.7' }, { 'CF-Connecting-IP': '203.0.113.7' }, { 'X-Client-IP': '203.0.113.7', 'X-Originating-IP': '203.0.113.7', 'X-Remote-Addr': '203.0.113.7' }, ]; for (const h of spoof) expectContainersResponse(await http.get(server.url(), { token: tok, headers: h })); expect((await server.stop()).exitCode).toBe(0); expect(readOperations(server.dbPath).map((o) => o.origin)).toEqual(spoof.map(() => '127.0.0.1')); expect(server.logs().requests().map((r) => r.attrs.origin)).toEqual(spoof.map(() => '127.0.0.1')); expect(server.stderr).not.toContain('203.0.113.7'); }); test('IPv6 and dual-stack listeners record the peer address family correctly', async ({ cli, startServer, http }) => { const tok = await cli.tokenNew(); const v6 = await startServer({ addr: '::1' }); expect(v6.baseURL).toBe(`http://[::1]:${v6.port}`); expect(v6.logs().find({ msg: 'server listening' })!.attrs.addr).toBe(`[::1]:${v6.port}`); expectContainersResponse(await http.get(v6.url(), { token: tok })); expectContainersResponse(await http.curl({ url: v6.url(), token: tok })); expect((await v6.stop()).exitCode).toBe(0); const dual = await startServer({ addr: '::' }); expectContainersResponse(await http.get(`http://127.0.0.1:${dual.port}/api/v1/containers`, { token: tok })); expectContainersResponse(await http.get(`http://[::1]:${dual.port}/api/v1/containers`, { token: tok })); expect((await dual.stop()).exitCode).toBe(0); expect(readOperations(v6.dbPath).map((o) => o.origin)).toEqual(['::1', '::1', '127.0.0.1', '::1']); assertDbInvariants(v6.dbPath, { operationCount: 4 }); }); test('audit history survives restarts and is append-only', async ({ cli, startServer, http }) => { const tok = await cli.tokenNew(); const server = await startServer(); for (let i = 0; i < 3; i++) expectContainersResponse(await http.get(server.url(), { token: tok })); const before = readOperations(server.dbPath); await server.restart(); for (let i = 0; i < 2; i++) expectContainersResponse(await http.get(server.url(), { token: tok })); expect((await server.stop()).exitCode).toBe(0); const after = readOperations(server.dbPath); expect(after.slice(0, 3)).toEqual(before); expect(after).toHaveLength(5); assertDbInvariants(server.dbPath, { operationCount: 5 }); });