import { cp, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import path from 'node:path' import type { AuthPersistence, AuthSessionRecord, AuthUserRecord, CreateAuthSessionRecord, } from '../../packages/application/src/auth/auth-service' import { AuthService } from '../../packages/application/src/auth/auth-service' import { TokenDigester } from '../../packages/application/src/auth/token-digest' import { loadPlaybookPackage } from '../../packages/content/src/loader' import { createLogger } from '../../packages/observability/src/index' import { handleAuthRequest } from '../../apps/web/src/auth/csrf' import { assertNonSecretConfiguration, authorizeBootstrap, } from '../../apps/web/src/setup/setup-policy' import { afterEach, describe, expect, it, vi } from 'vitest' const temporaryDirectories: string[] = [] afterEach(async () => { await Promise.all( temporaryDirectories.splice(0).map((directory) => rm(directory, { recursive: true, force: true, }), ), ) vi.restoreAllMocks() }) async function maliciousPackage(): Promise { const directory = await mkdtemp( path.join(tmpdir(), 'devrunbook-security-content-'), ) temporaryDirectories.push(directory) await cp(path.resolve('content/playbooks/root-cause-bugfix'), directory, { recursive: true, }) const manifestPath = path.join(directory, 'playbook.yaml') const manifest = (await readFile(manifestPath, 'utf8')).replace( ' sensitive: false\n includeInOutput: true', ' sensitive: true\n includeInOutput: true', ) await writeFile(manifestPath, manifest) return directory } class CapturingAuthPersistence implements AuthPersistence { readonly user: AuthUserRecord = { id: 'user-1', email: 'owner@example.test', displayName: 'Owner', passwordHash: '[password-hash]', emailVerified: true, image: null, status: 'active', createdAt: new Date('2026-07-27T00:00:00.000Z'), updatedAt: new Date('2026-07-27T00:00:00.000Z'), } sessionInput: CreateAuthSessionRecord | undefined async findUserById(id: string) { return id === this.user.id ? this.user : null } async findUserByEmail(email: string) { return email === this.user.email ? this.user : null } async updateUser() { return this.user } async createSession(input: CreateAuthSessionRecord) { this.sessionInput = input return { id: 'session-1', revokedAt: null, ...input, } } async findSessionByTokenHash(): Promise { return null } async touchSession(): Promise { return null } async revokeSessionByTokenHash() { return false } async revokeSessionsForUser() { return 0 } } describe('production security boundaries', () => { it('uses the database workspace migration runner copied into the image', async () => { const dockerfile = await readFile(path.resolve('Dockerfile'), 'utf8') expect(dockerfile).toContain( 'CMD ["./packages/db/node_modules/.bin/tsx", "packages/db/src/migrate.ts"]', ) expect(dockerfile).not.toContain( 'CMD ["./node_modules/.bin/tsx", "packages/db/src/migrate.ts"]', ) }) it('ships the worker as a standalone bundle without production node_modules', async () => { const dockerfile = await readFile(path.resolve('Dockerfile'), 'utf8') const workerPackage = JSON.parse( await readFile(path.resolve('apps/worker/package.json'), 'utf8'), ) as { scripts: { build: string } } expect(workerPackage.scripts.build).toContain( 'esbuild src/index.ts src/operator/password-reset.ts', ) expect(workerPackage.scripts.build).toContain('--bundle --platform=node') expect(workerPackage.scripts.build).toContain( 'const require = createRequire(import.meta.url)', ) expect(dockerfile).toContain( 'cp -a /app/apps/worker/dist/. /out/worker/dist/', ) expect(dockerfile).not.toContain( 'pnpm --filter @devrunbook/worker --prod deploy', ) }) it('rejects secret-bearing package output and nested setup configuration', async () => { await expect(loadPlaybookPackage(await maliciousPackage())).rejects.toThrow( 'sensitive input problemStatement cannot be included in output', ) expect(() => assertNonSecretConfiguration({ integration: { apiToken: 'must-never-be-persisted' }, }), ).toThrow( 'configuration.integration.apiToken must not contain secret material', ) expect(() => assertNonSecretConfiguration({ integrationEncryptionKeyVersion: 'v2', }), ).not.toThrow() }) it('persists only a peppered HMAC session digest', async () => { const rawToken = 'raw-session-token-with-high-entropy-0123456789' const persistence = new CapturingAuthPersistence() const digester = new TokenDigester(Buffer.alloc(32, 7)) const service = new AuthService( persistence, digester, () => new Date('2026-07-27T12:00:00.000Z'), ) const active = await service.createSession({ userId: persistence.user.id, rawToken, }) expect(active?.rawToken).toBe(rawToken) expect(JSON.stringify(persistence.sessionInput)).not.toContain(rawToken) expect(persistence.sessionInput?.tokenHash).toMatch( /^hmac-sha256:v1:[a-f0-9]{64}$/u, ) expect( digester.matches(rawToken, persistence.sessionInput!.tokenHash), ).toBe(true) expect(new TokenDigester(Buffer.alloc(32, 8)).digest(rawToken)).not.toBe( persistence.sessionInput?.tokenHash, ) }) it('denies proxy bootstrap bypass and cross-origin credential requests without reflection', async () => { const proxied = new Request('http://127.0.0.1/api/v1/instance/setup', { method: 'POST', headers: { 'x-forwarded-for': '127.0.0.1' }, }) expect(authorizeBootstrap(proxied, '', undefined)).toBe(false) expect( authorizeBootstrap(proxied, 'attacker-token', 'operator-token'), ).toBe(false) const handler = vi.fn(async () => new Response('should not run')) const rawCookie = 'devrunbook.session=raw-session-secret' const request = new Request( 'https://runbook.example.test/api/auth/session', { method: 'POST', headers: { origin: 'https://attacker.example.test', 'sec-fetch-site': 'cross-site', cookie: rawCookie, }, }, ) const response = await handleAuthRequest( request, handler, 'https://runbook.example.test', ) const body = await response.text() expect(response.status).toBe(403) expect(handler).not.toHaveBeenCalled() expect(body).not.toContain('attacker.example.test') expect(body).not.toContain(rawCookie) expect(body).toContain('INVALID_ORIGIN') }) it('redacts root and nested secret fields from structured logs', async () => { let output = '' vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => { output += String(chunk) return true }) const logger = createLogger('info') logger.info( { password: 'root-password-secret', token: 'root-token-secret', req: { headers: { authorization: 'Bearer raw-authorization-secret', cookie: 'session=raw-cookie-secret', }, }, account: { secret: 'nested-account-secret' }, }, 'security redaction probe', ) await new Promise((resolve) => setTimeout(resolve, 20)) expect(output).not.toContain('root-password-secret') expect(output).not.toContain('root-token-secret') expect(output).not.toContain('raw-authorization-secret') expect(output).not.toContain('raw-cookie-secret') expect(output).not.toContain('nested-account-secret') expect(output).toContain('[REDACTED]') }) })