Trust boundaries

English | 日本語

Human authority

Humans own material product intent and effects that change secrets, settings, deployment, billing, production state, external publication, destructive ownership, or authority beyond a recorded contract. A model capability never grants effect authority.

Project activation

Codex owns project-only Plugin admission through a disabled user default and explicit trusted project overrides. AI Teams consumes that effective state; its management metadata, cached Skills and independent feature opt-ins never override disablement. See project activation for setup and fresh-session verification.

Provider boundary

Each provider owns authentication, permission consent, transport, and the primitive it performs. AI Teams owns semantic selection and interpretation. Credentials do not move between providers, candidates, or documentation.

Workspace and delivery boundary

Untrusted candidate execution can modify only its Issue-linked worktree. It must not receive GitHub write authority or access the publisher filesystem. Trusted delivery verifies repository, Issue, branch, revision, candidate schema, and exact acceptance evidence before a GitHub effect.

This current data-flow view answers one question: what is allowed to cross from untrusted candidate execution into the GitHub-authorized publisher?

flowchart LR
    subgraph untrusted["Untrusted candidate boundary"]
        runner["Candidate runner<br/>no GitHub write authority"]
        worktree["Fresh exact-SHA checkout<br/>no extra filesystem inputs"]
        runner -->|"runs checks"| worktree
    end

    evidence["Bounded candidate evidence<br/>revision, results, pre/post inventory"]

    subgraph trusted["Trusted publisher boundary"]
        credentials["Publisher credentials"]
        publisher["Publisher<br/>verifies identity and acceptance"]
        credentials -->|"available only here"| publisher
    end

    worktree -->|"structured evidence only"| evidence
    evidence -->|"crosses trust boundary"| publisher
    publisher -->|"authorized exact-revision effect"| github["GitHub"]

Candidate code and its filesystem never cross this boundary as publisher inputs. The structured evidence is accepted only after exact identity and cleanliness checks.

Self-hosted pull-request execution

The CI workflow dispatches its test jobs for every GitHub-delivered pull_request event covered by the workflow's default activity types, and for pushes to main. The aggregate validate job follows successful tests. The workflow does not contain an actor, branch-owner, or fork-origin gate: all of these jobs select the exact self-hosted, Linux, X64, and ai-teams-ci runner labels.

This configuration is accepted only for the private source repository while every account able to open or update an eligible pull request is inside the trusted repository-member or collaborator boundary. Repository access policy, not the workflow, currently supplies that author boundary. Candidate content is still treated as untrusted: a trusted author does not make candidate code a trusted workload.

Two independent defenses apply:

  • read-only workflow permissions limit what GITHUB_TOKEN can change through GitHub APIs;
  • each job runs in a fresh disposable container with no host mount, Docker socket, host credential directory, or delivery secret.

Read-only GitHub permissions do not protect the self-hosted host. Container isolation, resource bounds, disposal, and exact runner cleanup protect that boundary. Neither defense may be cited as a substitute for the other.

Conversion to a public repository, execution of pull requests from forks, granting external contributors a path to create or update eligible pull requests, or weakening the runner isolation model requires review and acceptance of a replacement before such jobs run. Accepted containment options are:

  1. move pull-request jobs to GitHub-hosted runners;
  2. add a fail-closed trusted-author or maintainer-approval gate that prevents self-hosted job dispatch; or
  3. use an equivalently disposable isolated provider with no persistent host, credential, or control-plane exposure.

The current decision and its revisit conditions are recorded in Issue #670. The machine-facing runner isolation contract remains the repository self-hosted CI contract.

Runner credential diagnostics

The private Docker env-file protects the outer launcher only. The container entrypoint also passes JIT configuration through the pinned GitHub runner's ACTIONS_RUNNER_INPUT_JITCONFIG environment input, so no shell, helper or listener receives the value as a command-line argument. Shell tracing is disabled before the entrypoint reads it. The upstream listener consumes this input; the existing one-job registration and cleanup lifecycle remains intact.

Never capture raw process arguments, environment dumps or full Docker inspection output during diagnostics. Read only executable identities and booleans for credential-bearing flags, with redaction before output. The same user, root and Docker administrators may still inspect process environments; removing argv exposure does not make the credential secret from those privileged principals.

Earlier env-file installations may retain the inner argument exposure. Under host-owner authority, refresh through the accepted-main installer after checking the runner is idle, allow for a brief CI interruption, and verify the old runner registration is absent, the replacement is online and its descendant argv has no credential-bearing flag. Do not replay a historical credential to test it. Use exact registration retirement evidence; unresolved validity requires owner review. Keep old immutable artifacts for controlled recovery, but do not treat rollback to the exposed launch path as a completed security correction.

Runner supervisor cadence

The repository supervisor implements the existing runner retry floor without adding a fixed delay after a long successful job. Its admission timing is separate from GitHub's queue delay; neither timing changes test or merge gates.

A successful cycle must preserve at least 15 seconds between lifecycle starts, measured with a monotonic clock. A normal job that already consumed that interval needs no additional sleep. A fast clean exit still waits out the interval: exit zero alone does not prove a GitHub job ran, and repeated empty exits must not create an unthrottled JIT-registration loop. Every failed cycle retains the full minimum 15-second retry delay after failure, regardless of elapsed work.

Single-owner locking, capacity admission, exact container/runner cleanup before replacement, termination handling and --once behavior remain unchanged. Cleanup uncertainty and unavailable capacity retain their existing fail-closed paths. This is one serial supervisor, not additional capacity or a new scheduler. Installation and restart require host-owner authority; source acceptance alone does not authorize either effect. Rollout uses the accepted-main installer and preserves the previous unit and immutable artifacts for rollback and exact service/runner readback. Ambiguous recovery requires an operator handoff.

Existing bounded lifecycle results remain the diagnostic owner; no credentials or additional telemetry are emitted. Tests use a controlled monotonic clock to cover zero-length, short, exact-floor and long success, long/short failures and cleanup failure. Hosted evidence measures the complete unchanged Python matrix after rollout. There is no persisted-state, workflow-name or support migration.

Control-plane boundary

The optional external Qoo257/ai-teams-control surface can start work and therefore is restricted to trusted operators. It owns webhook signatures, exact allowlists, control authentication, origin controls, scheduling, persistence, and worker lifecycle. The Plugin exposes only a bounded client and does not gain Control credentials or runtime ownership. External Control does not become a source of truth for code or Issue state and cannot expand repository effect authority.

Documentation boundary

The public HTML view contains no credentials or mutable project state. Generated references read only tracked machine metadata. Build output is disposable and does not become a trusted input to code, tests, delivery, or release.

Operational detail belongs to the human/AI responsibility contract.

Runner shutdown and replacement readiness

Runner shutdown defers repeated signals while the exact Docker client is terminated and reaped, then removes only its cidfile-bound container and registration. Client TERM/KILL waits total at most four seconds; provider calls take at most five seconds each. The existing bounded cleanup has a 74-second wait budget below the explicit 90-second systemd stop deadline. A timeout is incomplete cleanup, not permission for broad deletion.

An authorized installer verifies a different online runner and running container on the accepted image, plus absence of both prior identities, within 30 seconds. An active service alone is insufficient; failed readback rolls back the existing unit transaction. Confirm the runner is idle and no job can be interrupted before the separately authorized live restart. Preserve the prior revision-bound unit and image for rollback. Never collect raw process arguments or container environment; use only exact IDs, image and state. Process tests prove signal ordering; live systemd/Docker acceptance requires its own readback.