Skip to content

v0.2.0-prerelease — Phase 1: Local Bridge Server

Pre-release
Pre-release

Choose a tag to compare

@Samuel0101010 Samuel0101010 released this 22 May 12:32

Local HTTP+SSE+Long-Poll bridge with token auth and a 5-rule path-traversal guard.
Repo stays private until v1.0.0.

Built by a 4-agent team (architect / coder / security / tester) with disjoint
file ownership — no merge conflicts, no overlapping writes.

What landed

Contracts (src/contracts/bridge.ts, 426 lines)

  • BridgeEventSchema zod discriminatedUnion across 10 event kinds
  • PortLockSchema, BridgeStatusSchema, BridgeHealthSchema, BridgeHttpErrorSchema
  • LongPollRequestSchema (.refine enforces timeout <= LONG_POLL_CAP_MS = 270000)
  • AuthModule interface, BridgeEndpoint discriminated union (11 variants)
  • BridgeServerOptions / BridgeServerHandle for the boot API

Server (src/bridge/server.ts 484 + http-helpers.ts 127 lines)

Plain Node http, no Express/Hono. Binds 127.0.0.1 only. 11 endpoints:

  • GET /health (no auth, liveness)
  • GET /status (auth, BridgeStatus)
  • GET /live.js (no auth, Phase 2 stub)
  • GET /design-system.json (auth, serves .wisp/design-tokens.json)
  • GET /source (auth + guardPath)
  • GET /events SSE (auth, 15s keepalive)
  • POST /events (auth, validates BridgeEventSchema)
  • GET/POST /poll (auth, long-poll with silent cap)
  • POST /annotation (auth, kind-locked)
  • GET /stop (auth, graceful shutdown)

256 KB body cap (413 BODY_TOO_LARGE). Cursor format seq-N-sessionId. Long-poll
silent-clamps oversize timeouts to 270000 ms (no 400 — improves Impeccable).

Port discovery (port-discovery.ts, 123 lines)

  • findFreePort over 31337..31400 (Improvement #8 vs Impeccables fixed :8400)
  • Atomic lockfile (.tmp + rename, mode 0o600)
  • Stale-pid detection via process.kill(pid, 0)

Auth + path guard (src/bridge/auth.ts, 220 lines)

  • generateToken UUIDv4
  • validateToken UUID-format regex + length-mismatch UNAUTHORIZED
    (avoids timing leak) + final crypto.timingSafeEqual
  • guardPath 5-rule pipeline:
    1. reject absolute paths (POSIX + Windows)
    2. reject any surviving .. segment after normalize
    3. path.resolve must prefix-match projectRoot
    4. realpathSync re-check (symlink-escape blocked); ENOENT is OK (caller 404)
    5. hard-deny /.git/, /node_modules/, /.wisp/sessions/, .env*

Tests (tests/bridge/*.test.ts, 77 passing in 6 files)

  • auth.test.ts (26) — token generation, validation edge cases, guardPath all 5 rules
  • path-traversal.test.ts (11) — mixed seps, unicode, mid-segment denies, long paths
  • port-discovery.test.ts (11) — range/exhaustion/stale-pid/atomic-write
  • long-poll-slicing.test.ts (6) — fast-path, wake-up, empty timeout, cursor advance,
    client-disconnect race, silent-clamp
  • server-smoke.test.ts (21) — auth gating, /source traversal, body validation,
    413 cap, SSE keepalive, /stop graceful

Tooling

  • .gitattributes normalizes line endings to LF (Phase 3 needs byte-equivalence
    on inject/remove roundtrip)
  • Version bump to 0.2.0-prerelease in all three manifests

Quality gates

$ npm run check
typecheck:  clean
test:       77 passed in 6 files (1.28s)
build:      9.74 kB

$ node dist/index.js doctor
7/7 OK

Next

Phase 2 — Browser Runtime (src/browser/live.js). Vanilla-JS IIFE, state machine
PICKING → CONFIGURING → GENERATING → CYCLING, floating bar, multi-select via
⌘-click-add, parameter sliders binding to CSS vars.