Skip to content

feat: Stage 0/1 soundness — compatibility, origin policy, emit authority - #65

Open
mikeyobrien wants to merge 20 commits into
mainfrom
feat/stage0-soundness-ultra
Open

feat: Stage 0/1 soundness — compatibility, origin policy, emit authority#65
mikeyobrien wants to merge 20 commits into
mainfrom
feat/stage0-soundness-ultra

Conversation

@mikeyobrien

Copy link
Copy Markdown
Owner

Summary

Stage 0/1 soundness work from repaired Ultra plus independent verification-driven security repairs:

  • Compatibility (Tier A/B/C): docs/COMPATIBILITY.md, golden journal/registry fixtures, package-wide lint/typecheck/export gates, fixture drift CI, and a pinned Tier A surface (test/fixtures/contracts/tier-a-surface.json) with documented named-import checks.
  • Ultra architecture diagram: editable .tldr + PNG under docs/assets/, linked from platform docs.
  • WebSocket + HTTP origin: true same-origin (Host + scheme); forged X-Forwarded-Proto rejected by default; opt-in --trust-proxy only behind header-stripping proxies; HTTP/WS parity with direct TLS precedence.
  • Harness-owned emit completion authority: agent emits enter a request queue; parent validates/canonicalizes and stamps authority_id; routing/required/completion use authoritative projection only.
  • Parent-stamped synthetic events: *.parallel.joined, wave.join.finish, and parent event.invalid carry parent-issued authority ids + MAC ledger outside AUTOLOOP_STATE_DIR (resume loads ledger, not journal-forged ids).
  • CI: fetch-depth: 0 and two-dot base→HEAD whitespace check so PR bytes are actually inspected.

Independent review (exact head)

  • Emit authority final review: PASS on 286fc85079f8314d13864a4f418c77383e9da360 / tree 0479d7d7d66f9f9c1c15c0faef6ff7512c710d84
  • Origin + Tier-A/CI local gates: PASS (remote CI receipt expected after push)
  • Prior review iterations closed: cross-scheme origin, untrusted XFP, journal completion forgery, resume authority forge, system routing forge, join-resume metadata forge

Verification (local, frozen tip)

  • npm run check — 206 files, 2325 passed, 4 skipped
  • npm run docs:build — pass
  • npm run check:exports — 107 export/bin targets + Tier A pin + 2 named imports
  • git diff --check origin/main HEAD — clean
  • Focused origin suites 23/23; emit-authority adversarial + harness suites green

Intentional non-goals / residual

  • Same-user unrestricted filesystem adversary remaining residual for durable crypto separation (documented in COMPATIBILITY).
  • Pre-existing dependency advisories identical to origin/main lockfile (not introduced here).
  • Do not merge autonomously — human gate.

Test plan

  • CI green on this PR
  • Spot-check WebSocket origin tests and emit-validation adversarial suite in CI logs
  • Confirm no unexpected dependency lockfile churn

pi-agent added 20 commits July 29, 2026 06:06
Implement harness-side re-validation of agent-emitted events to prevent
backend-forged AUTOLOOP_ALLOWED_EVENTS from satisfying required conditions,
corrupting routing, or falsely completing runs.

Changes:
- prompt.ts: Add validateAgentEventsForTurn() to re-check agent events
  against harness-computed allowed events. Coordination/ask topics excluded.
- iteration.ts: Call validateAgentEventsForTurn() on each turn, pass
  validated agent topics to resolveOutcome(). Only validated topics count
  toward required-event and completion logic.
- Emit subprocess env hint remains (UX feedback) but harness is authoritative.

Tests:
- Adversarial fixtures: forged-required-event, forged-completion-event,
  valid-completion-seq, emit-task-complete.
- Test cases: verify harness rejects forged required/completion events,
  marks them invalid (audit trail), and only counts valid events toward
  completion.

Backward compatible: existing valid emit paths unaffected.
Implement origin boundary enforcement for WebSocket upgrades equivalent to
HTTP /api/* request protection. Prevents cross-origin browser pages from
injecting keystrokes into task PTY terminals (RCE).

Changes:
- ws.ts: Add isOriginAllowed() to validate Origin header.
  - Allowed: matching http://host:port or localhost forms.
  - Disallowed: Origin present but not in allowed set.
  - Missing origin: allowed (CLI/test clients omit header; matches HTTP policy).
  - Malformed origin: rejected (invalid URL parse).
- installKanbanWs() extracts server bind address, validates origin before
  upgrade, destroys socket on mismatch.

Tests:
- ws.test.ts: 6 new origin enforcement tests.
  - Localhost no-origin allowed.
  - Cross-origin rejected.
  - Matching localhost origin accepted.
  - Malformed origin rejected.
  - Non-WS requests unaffected.

Backward compatible: CLI/test clients without Origin header pass through.
Establish versioning tiers, stability expectations, and CI enforcement for
public API contracts. Enable automated detection of schema drift and maintain
compatibility awareness across releases.

Changes:
- docs/COMPATIBILITY.md: Define Tier A (stable, v1+, 2-minor deprecation),
  Tier B (semi-stable, breaking changes in minor versions), Tier C (internal).
  Detail versioning scheme, deprecation process, example surfaces, and
  consumer usage patterns.
- test/fixtures/contracts/journal-v1-minimal.jsonl: Deterministic golden
  fixture with fixed timestamps (2021-01-01T00:00:00Z), run ID test-golden-run-1.
  Demonstrates journal v1 contract, routing progression, source field.
- test/integration/compat-contract.test.ts: 5 contract tests validating
  journal schema, routing events, source fields, core API consumption,
  schema stability.
- .github/workflows/ci.yml: Add gates for full npm run check (lint, tsc,
  coverage), explicit contract tests, trailing-whitespace check.

No performance impact; gates run in sequence with other CI checks.
Fixtures are hand-authored once per contract version; no automated generation
currently needed but documented for future use.
Adds failing tests for plan A2 gaps found by verify.1 dissent:
- registry-v1-minimal.jsonl fixture does not exist
- no deterministic `npm run fixtures:contracts` regeneration command

Registry contract coverage pins current behavior:
schema/required keys, RegistryStatus enum, determinism (no pid/wall-clock/
host paths), last-write-wins dedupe (read.ts:7-18), activeRuns filter,
findRunByPrefix, malformed-line tolerance, additive-field forward compat.
Regeneration proof asserts byte equality with committed fixtures and
stability across two consecutive generator runs.
…tract

- scripts/gen-contract-fixtures.mjs generates journal and registry fixtures
  through the real writers (encodeEvent / appendRegistryEntry) with frozen
  timestamps, no PID and no host paths
- package.json: npm run fixtures:contracts (AUTOLOOP_FIXTURE_OUT_DIR to
  redirect output for byte-equality proofs)
- test/fixtures/contracts/registry-v1-minimal.jsonl pins RunRecord schema,
  RegistryStatus enum, last-write-wins dedupe and activeRuns filtering
- docs/COMPATIBILITY.md: exact regeneration command, determinism guarantees,
  maintainer review/classification procedure
- ci.yml: regenerate fixtures and fail on drift
Ignore non-system journal claims without parent agent authority, harden
resume authority rehydration, and require true same-origin WebSocket
upgrades including scheme via TLS or X-Forwarded-Proto.
Persist accepted agent emits in a MAC'd harness-owned ledger used on resume
instead of reconstructing authority from the backend-writable journal. Align
HTTP /api origin checks with WebSocket same-origin Host+scheme rules.
Do not honor client-supplied X-Forwarded-Proto by default on HTTP or
WebSocket origin checks. Opt in with trustProxy/--trust-proxy only behind a
header-stripping reverse proxy, and cover the forged-header negative path.
Store MAC'd acceptance materials under XDG_RUNTIME_DIR/temp with O_EXCL key
creation so backends cannot forge resume authority via AUTOLOOP_STATE_DIR.
Also make CI trailing-whitespace checks compare the PR base range.
When no ledger exists yet, mint a fresh parent key so attacker-precreated
private key files cannot become issuance material before the first accept.
Direct HTTPS wins over X-Forwarded-Proto when trustProxy is enabled, matching
WebSocket behavior. Replace the non-existent kanban/runtime Tier-B export example.
Use fetch-depth 0 and two-dot base HEAD diffs so trailing-whitespace gates
resolve without requiring a three-dot merge-base on shallow clones.
Ignore routing-capable system journal topics without parent agent authority,
stop event.invalid from steering routing, and enforce a pinned Tier A export
plus documented named-import surface in check-exports.
Accept parallel.joined and event.invalid only when parent-issued authority
ids are present, so forged harness/operator journal rows cannot steer routing
or block completion, while genuine parent fan-out joins still resume.
Require parent-issued authority_id on wave.join.finish so forged unstamped
join metadata cannot overwrite resume roles/events after a genuine join.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant