feat: Stage 0/1 soundness — compatibility, origin policy, emit authority - #65
Open
mikeyobrien wants to merge 20 commits into
Open
feat: Stage 0/1 soundness — compatibility, origin policy, emit authority#65mikeyobrien wants to merge 20 commits into
mikeyobrien wants to merge 20 commits into
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stage 0/1 soundness work from repaired Ultra plus independent verification-driven security repairs:
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..tldr+ PNG underdocs/assets/, linked from platform docs.Host+ scheme); forgedX-Forwarded-Protorejected by default; opt-in--trust-proxyonly behind header-stripping proxies; HTTP/WS parity with direct TLS precedence.authority_id; routing/required/completion use authoritative projection only.*.parallel.joined,wave.join.finish, and parentevent.invalidcarry parent-issued authority ids + MAC ledger outsideAUTOLOOP_STATE_DIR(resume loads ledger, not journal-forged ids).fetch-depth: 0and two-dot base→HEAD whitespace check so PR bytes are actually inspected.Independent review (exact head)
286fc85079f8314d13864a4f418c77383e9da360/ tree0479d7d7d66f9f9c1c15c0faef6ff7512c710d84Verification (local, frozen tip)
npm run check— 206 files, 2325 passed, 4 skippednpm run docs:build— passnpm run check:exports— 107 export/bin targets + Tier A pin + 2 named importsgit diff --check origin/main HEAD— cleanIntentional non-goals / residual
origin/mainlockfile (not introduced here).Test plan