feat: W1 batch — config, protocol, ring-buffer, origin, mock-pty (TDD)

Parallel module-builder subagents, disjoint file ownership, shared tree:
- T4 src/config.ts        — loadConfig+M1 origins (32 tests)
- T5 src/protocol.ts      — parse/serialize+SESSION_ID_RE+M7 (60 tests)
- T6 src/session/ring-buffer.ts — byte-accurate, no UTF-8/ANSI split+M2 (15 tests)
- T7 src/http/origin.ts   — host+port match, undefined rejected (12 tests)
- T3 test/helpers/mock-pty.ts — IPty double w/ emitData/emitExit (9 tests)

Verified by orchestrator: tsc --noEmit clean; full suite 128 tests pass.
This commit is contained in:
Yaojia Wang
2026-06-16 08:09:40 +02:00
parent 0e10dc21c3
commit 0fa0b69ce9
15 changed files with 1579 additions and 0 deletions

78
test/origin.test.ts Normal file
View File

@@ -0,0 +1,78 @@
/**
* test/origin.test.ts — TDD for src/http/origin.ts (T7)
*
* Covers ARCHITECTURE §3.3 and TECH_DOC §7 (CSWSH defence):
* - whitelist hit → allow
* - wrong hostname → deny
* - wrong port → deny
* - undefined Origin (non-browser client) → deny
* - evil.com → deny
*/
import { describe, it, expect } from 'vitest'
import { isOriginAllowed } from '../src/http/origin.js'
const ALLOWED: readonly string[] = [
'http://localhost:3000',
'http://192.168.1.42:3000',
'http://my-host.local:3000',
]
describe('isOriginAllowed', () => {
// ── whitelist hits ────────────────────────────────────────────────
it('allows an exact localhost origin', () => {
expect(isOriginAllowed('http://localhost:3000', ALLOWED)).toBe(true)
})
it('allows an exact LAN IP origin', () => {
expect(isOriginAllowed('http://192.168.1.42:3000', ALLOWED)).toBe(true)
})
it('allows an exact hostname.local origin', () => {
expect(isOriginAllowed('http://my-host.local:3000', ALLOWED)).toBe(true)
})
// ── hostname mismatch ─────────────────────────────────────────────
it('rejects when the hostname does not match (evil.com)', () => {
expect(isOriginAllowed('http://evil.com:3000', ALLOWED)).toBe(false)
})
it('rejects a subdomain of an allowed hostname', () => {
expect(isOriginAllowed('http://sub.localhost:3000', ALLOWED)).toBe(false)
})
it('rejects a hostname that merely contains an allowed hostname', () => {
// "evillocalhost:3000" should NOT match "localhost:3000"
expect(isOriginAllowed('http://evillocalhost:3000', ALLOWED)).toBe(false)
})
// ── port mismatch ─────────────────────────────────────────────────
it('rejects when the port does not match (correct host, wrong port)', () => {
expect(isOriginAllowed('http://localhost:9999', ALLOWED)).toBe(false)
})
it('rejects when port is absent but allowed origin has a port', () => {
// Browser sends Origin without port only when it is the default (80/443).
// Our server runs on 3000, so no-port origins must not sneak in.
expect(isOriginAllowed('http://localhost', ALLOWED)).toBe(false)
})
// ── undefined Origin (non-browser clients: curl, scripts) ─────────
it('rejects undefined Origin by default', () => {
expect(isOriginAllowed(undefined, ALLOWED)).toBe(false)
})
// ── empty / malformed origins ─────────────────────────────────────
it('rejects an empty string origin', () => {
expect(isOriginAllowed('', ALLOWED)).toBe(false)
})
it('rejects a completely different scheme (https vs http in allowed)', () => {
// Allowed list uses http://; https:// should not silently match.
expect(isOriginAllowed('https://localhost:3000', ALLOWED)).toBe(false)
})
it('rejects when allowed list is empty', () => {
expect(isOriginAllowed('http://localhost:3000', [])).toBe(false)
})
})