# Security Model

## Trust boundaries

OMS treats the following as untrusted input:

- GitHub issue titles, bodies, comments, labels, and branch suggestions;
- model, subagent, grader, and reflection output;
- tmux pane content and captured terminal output;
- raw native-hook input and external session metadata;
- repository content from a ticket branch;
- environment variables and runtime files not created through the current
  validated authority.

Untrusted content is data, never command source or a tmux keystroke sequence.
Typed outbox events and canonical records become trusted only after exact schema,
identity, version, policy, path, and branded-authority checks.

## Authentication and billing safety

Authentication mode, billing class, overage permission, and unattended
eligibility are explicitly declared per role. OMS fails closed when a value is
absent, unknown, or incompatible with an operation.

- Interactive panes must not be used to evade provider billing or automation
  restrictions.
- Subscription credentials are used only through provider-supported native
  application workflows.
- Programmatic or unattended traffic requires the provider-supported
  credential and policy for that surface.
- Secrets are supplied at runtime and are never committed to `.agents/`, logs,
  evidence, ticket files, Slack request events, or generated service plans.
- Local verification, tests, HUD, release assessment, and the trace fixture make
  no paid or live model call.

Provider policy changes over time. Operators must confirm current official
guidance before a live run; the existence of a local adapter or launch plan is
not authorization to contact a provider.

## Implemented side-effect boundary

The human starts OMS with bare `oms` and keeps the tmux client attached while
subscription-backed work runs. Detaching the final client kills the exact OMS
tmux session; the journal and worktrees remain available for an attended
restart. OMS does not install a daemon or background service.

The conductor is an interactive Claude session with exactly four typed tools:
accept a goal, read status, request stop, and advance the campaign. It has no
repository read/write tool, shell, raw tmux capability, worker lifecycle tool,
or direct result/delivery authority. A factory-bound TypeScript campaign loop
selects deterministic transitions from journal state.

Before worker launch, OMS binds fresh Codex advice to the exact immutable
assignment and source digest. An approved unchanged assignment may create one
attempt worktree and an inert tmux placeholder carrying a random physical
nonce. OMS journals and binds that exact pane, session, launch identity, and
nonce to the task before recording activation intent; only then may the
placeholder start interactive Claude or native Codex Goal. Activation replay
must match the same nonce and cannot stage a second worker. Physical creation
and its durable receipt share a per-attempt creation lock with campaign stop,
so a delayed create cannot race an absence decision. Normal completion,
operator stop, and deadline expiry all replay every launch intent and fail
closed without exact bound-worker or lock-certified unbound-stop evidence.
Ticket text, model output, and pane capture never become shell or tmux command
source. Workers acknowledge and complete through the attempt-bound OMS worker
MCP.

GitHub polling has no routing authority. It materializes deduplicated typed
issue, pull-request, and check events. Removal or content change of an active
issue first records a cancellation request and wake; the task remains working
until its exact launch-bound pane has been stopped and confirmed.

Claude workers auto-run only sandboxed Bash and the exact attempt-bound worker
MCP tools. Built-in Read/Edit/Write are authorized only for the attempt
worktree; Glob and Grep are disabled in favor of sandboxed `rg`. The OS sandbox
denies the broad user-home boundary and root home, then reopens only the exact
attempt worktree and required Git paths. Common credential paths receive
explicit built-in Read denies. Web, subagents, raw tmux, `.oms`, the main
checkout, and unsandboxed commands are denied. Worker subprocess environments
are default-deny allowlists; arbitrary host credentials and injection variables
are not inherited. Provider settings, hook definitions, MCP configuration, and
the exact launch binding live in a private OMS provider-control directory
outside every worker-writable root, so a worker cannot hot-reload a weaker
policy.

Codex Goal workers use a named permission profile rather than legacy
`--sandbox`. Each worker has a private OMS-owned `CODEX_HOME` containing only a
validated symlink to the existing private subscription auth file. The exact
project is forced untrusted, hook bypass is allowed only with that isolated
home, and apps/plugins are disabled. Capability evidence seals the read-only
Codex runtime root. Writes are limited to the attempt worktree, linked worktree
Git directory, object store, and the task-scoped
`refs/heads/oms/<task>/` namespace; the main checkout, user home, `.oms`, and
ambient temporary roots are not exposed to sandboxed commands.

Verification runs outside the worker through the protected deterministic gate
and has final authority. Supplemental grading cannot override it. Passing
repository-backed work may create a non-force branch push and draft pull
request; merge, ready-for-review, publish, tag, release, and branch-protection
mutation have no OMS surface.

## Command and content safety

- Pass opaque ticket IDs and absolute file paths across subprocess and future
  tmux boundaries.
- Never interpolate issue text, model output, pane content, or hook input into
  `eval`, `sh -c`, command substitutions, branch names, paths, service-manager
  directives, or tmux key sequences.
- Construct provider and verification subprocesses with executable-plus-argument
  arrays and no shell.
- Require the accepted ticket worktree to match its root, branch, base commit,
  and a clean tracked/untracked status immediately before provider launch.
- Use `--` before externally derived positional arguments where supported.
- Reject identifiers containing path separators, controls, shell
  metacharacters, or values outside their exact schema.
- Bound stdin, file, payload, and log sizes and redact credential-shaped values.
- Do not execute repository-provided hooks or scripts merely because a ticket
  requests them; verification commands come from reviewed tracked policy.

Prompt injection is an authorization problem: issue or model text cannot widen
filesystem, network, merge, credential, provider, service, or command authority.

Fresh advisor output is untrusted model data. It may contain only the exact
intent digest, verdict, summary, recommendations, and risks. OMS owns and binds
the provider request, fresh-session, contact-journal, and human-authorization
identities before its protected writer can mint advisor evidence.

## Hook, event, session, and quota safety

Provider-hook entrypoints accept one bounded JSON object from stdin and compute
its source digest internally. Callers cannot assert their own trusted digest. The projector
matches session role, engine, mode, run, ticket, native reference, pane ID,
version, state, timestamp, and event identity before mutation.

The stdin transport is not cryptographically authenticated to a provider in
this release. It is therefore a local operator/control-plane surface only and
does not establish live provider provenance or certification.

Event kinds have exact schemas. Immutable outbox events are idempotent per
identity and consumer receipts bind the operation nonce. A replay returns the
stored outcome; copied or foreign event buses and authorities are rejected.

Quota evidence is session-bound and freshness-bounded. A quota exhaustion event
must carry the active ticket's original version, attempt, actor, and lease
authority. Availability must be newer than the limiting observation and session
version. Stale, ambiguous, cross-policy, future-dated, or unrelated evidence
fails closed.

The watchdog can recover an expired lease, mark a missed-heartbeat session
stalled, supersede a dead waiter, repair the typed receipt/notification for an
exact waiting structured-role result, explicitly recover that request's exact
dead projector-lock generation, and resume the same proven dead conductor. It
may wake only a proven idle pane with an opaque notification ID;
it cannot interrupt working state, perform ordinary routing, send raw model
content, send Slack, or turn a stalled report into successful external
delivery.

## Filesystem and concurrency safety

- Resolve and validate `OMS_STATE_ROOT` as an absolute path before use.
- Reject empty, root, home, repository-root, or otherwise unsafe state roots.
- Prevent traversal, no-follow violations, and symlink swaps at canonical runtime
  paths. The sole deliberate runtime symlink is the private Codex auth reference;
  its exact regular-file target is revalidated before launch and it is never read
  as OMS state or evidence.
- Create sensitive runtime directories and files with restrictive permissions.
- Use same-filesystem temporary files, atomic rename, expected versions,
  nonces, and exclusive locks for mutable state.
- Validate durable JSON against its schema before and after a transition.
- Write evidence, manifests, decisions, plans, and receipts immutably; a replay
  must match existing bytes.
- Treat stale-lock recovery and lease expiry as explicit auditable operations.

Cleanup commands must prove that their target is below the validated state root
and must never follow an untrusted path recursively.

## Git and worktree safety

- One traced ticket uses one dedicated opaque branch and worktree.
- Branch names derive from validated identifiers, not issue titles.
- Preserve unrelated workspace changes.
- Do not force-push, rewrite shared history, publish a remote, or merge without
  explicit operator authority.
- Deterministic verification runs before supplemental grading.
- A completed local ticket remains `human_hold`; trust reporting cannot change
  that disposition.

A future merge gate would require deterministic checks, provenance, trust
evidence, executor-group evidence, and an explicit
idempotent reviewed transition. None is inferred from local test success.

Passing live GitHub results may enter one fixed review-delivery authority. It
accepts only the exact protected result chain, validates the local `origin`,
runs pinned redacted Gitleaks scans over the commit range, tracked worktree,
immutable evidence, canonical issue content, fixed argv, and draft-PR content,
then pushes the verified commit without force and creates at most one draft PR.
Fresh GraphQL readback must show the exact remote branch and draft PR still bind
the verified commit; a typed check event must show the named deterministic check passed. Raw scanner
reports are always removed; durable scan and contact records retain digests,
not secrets or response bodies.

## Doctor, campaign, and release evidence

The installed public surface is exactly bare `oms`, `oms status`, `oms doctor`,
and `oms stop`. Offline `doctor` checks local executables, configuration,
schemas, paths, permissions, and Git prerequisites without contacting a
provider. Bare `oms` is the sole start/repair entrypoint.

Campaign admission binds immutable task content and policy, not only IDs.
Provider hooks, GitHub polling, watchdog recovery, and terminal operations
serialize through the kernel lock and append continuity-aware journal records.
Terminal status is accepted only after exact launch-bound workers are
physically stopped and the required deterministic evidence is durable.

No public release, merge, schedule, raw hook-ingest, or lower-level routing
command exists in v2. A 24-hour deadline is a campaign stop bound, not evidence
that an elapsed live canary has been completed. This repository contains no
claim of an unattended or completed live 24-hour provider proof.

## Incident posture

On invalid state, a lease conflict, stale authority, unknown authentication,
suspected injection, credential exposure, outbox conflict, goal mismatch, or
verification ambiguity, OMS stops the affected transition and preserves
evidence. Recovery uses a new audited operation or explicit reconciliation; it
does not rewrite history, fabricate completion, certify a canary, or mark the
release stable.
