Repository credentials
Repository bindings grant bounded Git HTTPS and GitHub API access. OCC freezes grants into a revision; the worker prepares material. The credential service retains App keys, JWTs and installation tokens. Agents receive gateway bearers, client configuration and CA trust. Start with the operator guide.
Kubernetes supports embedded OpenClaw (api_key) or dedicated Codex (API key or
ChatGPT service account), without a Sandbox Driver. Other combinations reject
repository-bearing revisions. Helm's Recreate strategy prevents overlapping
worker/credential-service owners.
Only the consumer receives repository and model credentials. Dedicated Slack tokens stay in the gateway. Repository profiles and model authentication are independent. Kubernetes policies allow consumer access to the credential sidecar.
For repository-bound Codex consumers, Compute permits the exact broker hostname
with allow_local_binding = true and mode = "full". This disables Codex's
additional private-address guard and permits every HTTP method at otherwise
allowed destinations. Explicit denies, NetworkPolicy, TLS and broker authorization
still apply. Unbound Agents retain their policy; see the
networking contract.
The separate service owns protected configuration and private sender callbacks.
Its controls are open, status, close, and shutdown. Separate Git/gh artifacts
exclude signing and service modules. SIGTERM and SIGINT start bounded cleanup.
Repo Driver contract
The optional repo capability uses RepoDriver extends Driver, with the bundled
GitHubRepoDriver. Trusted Installation drivers.repo and GitHub Backend
drivers.repo select the same configured Driver ID. The
shared contract exposes these operations:
listOptionsreturns Namespace-approved opaque references, names, profiles and optional descriptions.resolvechecks Namespace policy and returns admitted bindings and duration.checkAdmissionReady, when provided, verifies that fresh admissions can be attempted; unavailable dependencies block new attempts and worker readiness.openreturnscreatedwith private runtime files,recoveredwith status only, ormissing.recoverOnlycannot create authority.statusreturns the current observation or authoritative absence.closestops local authority and reports closure or absence; it does not promise remote revocation or runtime termination.
Public status contains only sessionId, state, deadlineWallMs and binding
(providerInstanceId, repositoryId, grantId). Each response is an immutable
snapshot after complete private validation. Cleanup counters and configuration
decoding remain private. Status cannot regenerate the closed-schema Git/gh files.
maintenanceIntervalMs schedules worker reconciliation and retries of incomplete
repository cleanup; it is not a measured withdrawal bound. Configured IDs, AgentRevision.repositoryCredentials and
persisted admitted_spec.repository_credentials retain their meaning.
State derives immutable Driver, Backend, profile and grant context from the admitted revision. It retains original Namespace, Agent, revision, admission and session identities and deadlines after Agent deletion, without bearers or tokens.
Agent deletion retires Compute without waiting for repository-session cleanup.
Pending, missing, unknown and invalidated sessions block neither deletion
admission nor completion. Retained attempts and cleanup Work survive deletion;
cleanup continues independently. New requests share Work by revision and purpose.
Deletion, invalidation and elapsed deadlines do not prove disposal or provider
revocation. Evidence pruning and durable token recovery are unimplemented.
Worker restart can retain surviving sessions and Compute material. A broker can
recover the original broker's committed DISPOSED observation; active or uncertain
sessions without one remain unknown. Missing exposed sessions fail the revision
and retain cleanup; closing sessions block replacement until disposal. A new
authorized revision neither settles old cleanup nor replays Git/API mutations.
Configuration
Canonical platform registry
The GitHub Backend selects configuration.registryPath and names its Driver
in drivers.repo. API, worker and
service load the same immutable, versioned ConfigMap. The registry contains
nonsecret identity and Namespace policy for one App installation and multiple
repositories:
{ "version": 1, "backendId": "repository-backend", "providerInstanceId": "github-production", "appId": "123456", "githubInstallationId": "789012", "maximumDurationSeconds": 86400, "repositories": [ { "repositoryRef": "application", "repositoryId": "345678", "repository": "example/project", "namespaces": [{ "namespaceId": "team", "profiles": ["git-read", "git-write", "git-full"] }] } ]}Use platform Namespace IDs. App, installation and repository IDs are positive decimal safe integers as strings. Repository names are canonicalized to lowercase. The registry admits at most 1,000 repositories, 128 Namespace policies per repository and 4,096 overall. References, numeric IDs and canonical names must be unique.
The grant fingerprint covers provider, App, installation, repository, maximum duration, Namespace, allowed profiles, optional push-ref policy, selected profile and permission contract. The service independently compares it before admission; a retained reference cannot preserve a grant after policy changes.
Each Namespace policy may set an optional
pushRefAllowlist to prevent
accidental native Git pushes outside selected branches. This is not server-side
branch authorization.
Driver configuration supplies controlSocket, sessionDurationSeconds and
publicCaPath, never the App key. See Backend configuration and
the installation procedure.
Repository options
GET /namespaces/:namespaceId/agents/repository-options requires Agent create;
the exact-Agent editing route requires update. Both return approved
repositoryRef, displayName, allowedProfiles and optional description.
descriptionRefs accepts up to 20 unique, comma-separated refs;
meta.descriptionsPending signals background work, and missing descriptions never
block selection. Authorized discovery failure yields
503 REPOSITORY_OPTIONS_UNAVAILABLE; no approvals yields []; a closed Namespace
yields 409. Only success or that outage permits a fresh ordinary draft; editing
requires success. Writes reauthorize and resolve.
The service rechecks approved refs and fetches metadata with a private, repository-scoped Metadata-read token, validating GitHub's numeric repository ID; the Driver checks provider, App, installation and repository IDs. Lookups share provider capacity and token cleanup with sessions and are cached for five minutes. Descriptions never grant access.
Profiles
The Console offers Read-only (git-read) and Contributor (git-full).
Contributor includes pushes, PRs and issue management; Customize access can
disable issue management (git-write). Push and PR access stay bundled. Direct
bindings default to git-write; repositoryAccess records inheritance. All
profiles include GitHub API access.
The access-level reference defines the exact permissions, supported commands and GraphQL boundary. Every session selects one repository. Writable levels are not a promise that an Agent cannot merge: GitHub rules still govern protected branches. Administration, workflow editing, Actions control and secrets permissions are not requested. Missing App permissions fail without widening the grant.
Standalone service inputs
A protected JSON file supplies gateway, sessionPolicy, backend and optional
positive safe-integer limits. The service validates configuration before
listening. Configuration and private keys must be regular files owned by root or
the service user, with private permissions. Every directory ancestor must have
one of those owners and reject group/other writes. A root-owned sticky ancestor
such as /tmp is allowed above the immediate parent; the immediate parent must
always reject group/other writes. Symlinks and file replacement during loading
are rejected. See the configuration flow
for validation and key ownership. For standalone single-repository operation:
{ "gateway": { "publicOrigin": "https://credentials.example.internal", "listen": "0.0.0.0:8443", "tlsCertFile": "/run/repository-credentials/tls.crt", "tlsKeyFile": "/run/repository-credentials/tls.key", "controlSocket": "/run/repository-control/control.sock" }, "sessionPolicy": { "maximumDurationSeconds": 172800, "defaultProfile": "git-write", "allowedProfiles": ["git-read", "git-write", "git-full"] }, "backend": { "kind": "github-app", "providerInstanceId": "github-production", "configVersion": "1", "appId": "123456", "installationId": "789012", "repositoryId": "345678", "repository": "example/project", "privateKeyFile": "/run/repository-credentials/app.pem" }}The identifiers are examples. Production upstream origins are fixed to
github.com and api.github.com. Registry mode instead uses backend fields
kind: "github-app-registry", backendId, registryFile and privateKeyFile;
all repository policy comes from that registry, and unbound admission is refused.
Kubernetes composition copies selected projection generations into service-owned private files before protected-path validation. API and worker receive registry/public CA inputs; only the service receives App and TLS private keys.
The privileged GitHub transport captures installation, repository and permission profile at construction. Its operations issue scoped tokens and revoke owned tokens; callers cannot supply HTTP requests. Extending those operations changes a credential boundary and requires security review.
The service image must trust GitHub's HTTPS certificate chain. For a private CA,
supply a readable bundle and NODE_EXTRA_CA_CERTS; keep certificate and hostname
verification enabled.
Git discovery, upload-pack and receive-pack accept case differences in the
admitted owner/repository and an optional .git suffix. The backend constructs
a canonical upstream path; a literal .git repository name remains part of the
admitted identity. Endpoint names, methods, media types, service queries and
profile restrictions still apply. API request paths and repository authority
remain unchanged.
Sessions and closure
The trusted worker or local operator uses HTTP over a private mode-0600 Unix socket:
| Request | Response |
|---|---|
GET /v1/capabilities |
Durable admission version for the registry-backed service |
POST /v1/sessions with durationSeconds and optional profile |
Private status, client configuration and bearer once |
GET /v1/sessions/{id} |
Private session and cleanup status |
POST /v1/sessions/{id}/close |
Immediate local closure status; cleanup reported separately |
The socket's parent is private to the service/operator. It is never mounted into the client. Control bodies are limited to 16 KiB. The HTTPS client listener has no admission or close endpoint.
X-Admission-Id combines a 13-digit Unix-millisecond timestamp, hyphen and
lowercase UUIDv4. The CLI prints this nonsecret ID before dispatch. HTTP 201
returns the bearer once; matching ID/duration/profile returns HTTP 200 with
status only. Conflicts fail. Follow
lost-response recovery
before explicitly requesting replacement material.
Registry-backed brokers advertise durableAdmissionVersion: 1 privately. The
worker checks it before new attempts and readiness; an unavailable or older broker
blocks both. This does not prove journal availability, absence or disposal.
Recovery, closure and runtime retirement do not depend on the check.
Platform admission binds namespaceId, repositoryRef, normalized profile,
expectedBinding and deadlineWallMs to the persisted attempt. The worker's
private receipt socket commits a reservation before the broker releases a bearer.
A recovery-only lookup can durably fence a missing admission; a reservation or
active session without a confirmed terminal receipt remains unknown. Only the
original broker can record its exact DISPOSED result. Receipts contain no
credentials and remain with attempt history. Journal failure prevents new bound
admission and cannot establish absence or disposal.
Unseen IDs must be less than 60 seconds old and never future-dated. Process-local
correlations are bounded to twice the session limit and can return overloaded;
unresolved cleanup survives that window. Standalone correlations do not survive
restart. No correlation recovers a bearer.
Session duration is independent of token lifetime. Replacement uses the original grant and must cover the remaining exchange budget plus safety margin. Idle sessions need no periodic mint. The bearer works while its session process and upstream authorization survive.
Authentication eligibility and terminal cleanup expiry are separate deadlines. Both use elapsed monotonic time from the original capture; delayed acquisition settlement cannot extend either. The GitHub adapter allows 60 seconds of provider clock skew and conservatively stops authentication before the reported expiry. Cleanup retains the one-hour bound from local receipt. A forward wall-clock change can deny authentication but cannot establish remote expiration.
Closing or expiring a session prevents new use immediately and cancels owned
exchanges. CLOSED does not imply confirmed revocation. Private control status
distinguishes pending, revoked, expired and uncertain credentials, plus auxiliary cleanup.
DISPOSED requires settled actions, resolved access-token obligations and
completed auxiliary finalization; historical revoked/expired counters may remain
nonzero. An uncertain issuance blocks automatic minting.
An uncertain push or API mutation is never automatically replayed.
Failed admission can also retain cleanup work. If session construction fails,
renewal access closes immediately; retained material remains counted against
session capacity and shutdown's pendingAuxiliary until admitted callbacks
finish and their material is disposed.
GitHub response data
Bounded REST JSON responses omit the provider's temp_clone_token from the
repository object, its parent and source repository relationships, and
pull-request head.repo and base.repo objects. Human text and unrelated
metadata remain unchanged. Qualified machine links still pass through the
existing origin, repository, route, and profile checks before gateway rewriting;
other informational links remain data.
Client routing and limits
Ordinary Git uses /usr/bin/git and native configuration. Git owns commands,
identity, hooks, aliases, remotes, push URLs, worktrees, and user settings.
The client does not parse Git arguments or create a temporary HOME.
For each generation, native preparation validates public manifest/session metadata,
identities, paths and file custody, then writes private aggregate gitconfig.
Kubernetes invokes it through the private subPath after material copying; both
init steps gate startup. It neither reads bearers nor admits sessions. Each
canonical HTTPS host maps to one gateway origin; conflicting origins fail
preparation. Pins cannot change an already-chosen connection origin. Same-origin
public CA inputs must agree.
A host-prefix rewrite preserves owner/repository casing and an optional terminal
.git. It also routes unadmitted repositories on that host to the gateway, where
the helper releases no bearer. Separately authenticated same-host use needs an
explicit native configuration override. SSH and additional-repository submodule
workflows are outside supported acceptance.
The generated defaults scope helper reset, credential.useHttpPath=true, verified
TLS, optional CA trust and disabled redirects to the exact gateway HTTPS origin.
The helper checks effective protocol, host/port, username and repository path;
escaped paths, dot segments, extra components and unmatched names receive no
bearer. A literal name ending in .git can overlap another admitted identity;
the helper compares both spellings and refuses ambiguity or alternate grants.
Duplicate repository bindings remain valid. Select one with OCE_REPOSITORY_REF;
gh also propagates OCE_REPOSITORY_SELECTION containing generation, repository
reference and session ID. Conflicting or stale pins fail remote authentication.
The helper command embeds the prepared generation and refuses material from a
new generation. It validates the selected original deadline before reading its
bearer; gateway closure can deny use earlier. Local commands continue after
expiry or with stale pins because they do not consult the helper. Generation
pinning does not promise a command-wide snapshot across arbitrary subprocesses.
Native configuration can override these defaults, and additional helpers or
stores can retain credentials. The installed helper's store and erase are
inert. There is no whole-command preflight, and the client never retries an
uncertain mutation to obtain success.
The API launcher requires GitHub CLI 2.100.0, GH_HOST=github.com, a gateway
hostname with verified TLS, and HTTPS port 443. Its private hosts.yml uses the
experimental api_host routing option and stores only the gateway bearer in
oauth_token. Supported API calls use relative endpoint paths. The launcher
admits the selected read and contribution commands;
browser flows, extensions, absolute API destinations and arbitrary command
compatibility are excluded.
Response rewriting is limited to validated pagination links and explicitly
followed resource fields. Native /repositories/<id> response URLs must match
the configured repository ID and are rewritten to its admitted /repos/OWNER/REPO
route. Issue collection pagination accepts bounded after and before cursors;
direct requests to repository-ID routes remain unsupported.
Informational labels, milestones, nested repository
metadata and human-authored content remain unchanged. Each listener and sender
captures its permitted upstream origins at construction. The sender rejects
ambiguous or malformed adapter headers before dispatch, then supplies canonical
authority, framing and connection headers within the configured header bounds.
Routing configuration is not network egress confinement.
Default service bounds are 16 sessions including pending cleanup, two credential
slots per session, one provider action and 64 queued actions, 64 sockets per
listener, and 32 exchanges total and four per session. Headers are limited to 32 KiB/64 pairs;
request targets to 8 KiB. Git fetch input is 1 MiB; push input and Git output are
256 MiB. API input is 1 MiB and response data 8 MiB. Git gzip input has independent
wire and decoded limits. Exchanges have a five-minute total bound and 60-second
credential margin. HTTPS client header timing begins on the TLS socket after
the handshake and ends when an authenticated request reserves exchange capacity;
the exchange deadline bounds acquisition and forwarding. The upstream
response-header deadline starts after upload finishes, unless the response
headers already arrived. Connection, input and stall deadlines remain independent.
Provider actions have at most 30 seconds. Shutdown allows
60 seconds for cleanup before reporting unresolved obligations and terminating.
Unsettled actions retain capacity until exit; grace expiry does not establish
DISPOSED or confirmed revocation. Restart cannot recover the lost provider
cleanup inventory. Overrides remain positive and finite.
The testing guide separates source, artifact, container and live-provider proof. Local fixtures establish neither live GitHub compatibility nor release readiness. See the service flow and Agent flow for implementation ownership.
