Repository credentials
The OpenClaw Control Plane (OCC) saves repository grants with the Agent revision. Each binding grants HTTPS access to a named repository under an approved profile. See the operator guide for setup. For OpenShell, see the Git setup and request diagrams.
Native Git from the Agent to GitHub
Prepared native Git requires a dedicated Codex Harness (API key or ChatGPT service account) and Kubernetes Compute without a Sandbox Driver. Git presents the Agent's repository session bearer to the broker. The broker presents a repository-scoped installation token to GitHub.
sequenceDiagram
participant A as Agent / Git
participant B as OCE broker
participant W as OCC worker
participant G as GitHub
Note over W,G: Background preparation
W->>G: Signed App JWT and repository scope
G->>W: Installation token
W->>W: Verify and store encrypted token
Note over A,G: A push to the approved branch
A->>B: Git request with session bearer
B->>B: Check session, repository and refs
B->>W: Request one use over private socket
W->>B: Token and deadline for this send
B->>B: Verify TLS, then check send deadline and cancellation
B->>G: Push with installation tokenOnly the worker holds the App signing key. Git requests require an eligible prepared token. Reads cover the repository. The broker checks every pushed ref against the approved branch. Protect important branches at GitHub. See withdrawal and draft PR creation.
Metadata reads and
OpenShell Git require their
own sender and Sandbox selections. OCC rejects unsupported combinations.
Helm uses Recreate to prevent overlapping worker or credential service owners.
Model credentials stay with their consumer and dedicated Slack tokens with the
gateway. The repository-bound Codex proxy allows the exact broker hostname.
allow_local_binding = true disables the private-address guard. mode = "full"
allows all HTTP methods at allowed destinations. Explicit denies, Kubernetes
NetworkPolicy, TLS and broker authorization still apply. See
consumer networking
and anonymous public reads.
Repo Driver contract
The GitHub Backend selects GitHub repository policy
by ID in drivers.repo. Its contract is separate from
NativeRepositorySender. For protected Git, installation composition
supplies the resolver that checks the admitted grant and selects its destination.
listOptionslists approved repositories and profiles for the Namespace.resolvechecks the selection against Namespace policy and returns the bindings and duration.
For protected delivery, select repository and the installed Sandbox and
Credential Proxy Driver. For native delivery, select repository.nativeSender
with repository.preparedGitHub. Controller startup rejects the legacy
Installation-level drivers.repo in every mode. Before switching, stop new
admissions and drain accepted work on its original owner.
| Native sender operation | Result |
|---|---|
checkAdmissionReady (optional) |
Checks dependencies for fresh admission. Unavailable dependencies block new attempts and worker readiness. |
open |
Returns created with private runtime files, recovered with status alone, or missing. A recovery-only request cannot create a session. |
status |
Returns the current observation or authoritative absence. |
close |
Ends local use and reports closure or absence. Provider revocation and runtime termination have separate outcomes. |
Public status records sessionId, state, deadlineWallMs and binding
(provider instance, repository and grant IDs) in an immutable snapshot.
Configuration decoding and cleanup counters stay private. Status cannot
regenerate Git or gh client files, which contain no signing or service modules.
Deletion and restart
OCC keeps the saved revision's Driver, Backend, profile and grant. After Agent
deletion, it retains the Namespace, Agent, revision, admission and session IDs
and deadlines, without bearers or tokens. Compute can retire while repository
cleanup is pending, missing, unknown or invalidated. Cleanup Work continues
independently and is shared by version and purpose. maintenanceIntervalMs
sets the worker's reconciliation interval. It does not set a withdrawal deadline.
On worker restart, surviving sessions and Compute material may still be usable.
A broker can recover the original broker's committed DISPOSED receipt.
For claimed native Git cleanup, the Worker can finish a closing attempt from
that matching receipt without the sender, recording any missing session ID.
A missing session that was exposed fails the version and keeps its cleanup open.
A closing session blocks replacement until disposal. A new version cannot settle
older cleanup. Deletion, invalidation and elapsed deadlines do not establish
disposal or provider revocation. Evidence pruning and durable recovery of lost token material from
a session are unimplemented. See sessions and closure
for what the original broker can confirm.
For prospective checks, see repository access across worker replacement.
Configuration
Canonical platform registry
The API, worker and service share the immutable, versioned ConfigMap at
configuration.registryPath. This example grants Namespace team one
repository and one writable ref on one App installation.
{ "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-write"], "transport": "git-only", "pushRefAllowlist": ["refs/heads/review/example-123"] } ] } ]}Use platform Namespace IDs. Supply App, installation and repository IDs as positive decimal safe integer strings. Repository references, IDs and lowercased names must each be unique. Registry limits are 1,000 repositories, 128 Namespace policies per repository and 4,096 policies total.
Before admission, the service independently checks the grant fingerprint. It covers provider, App, installation, repository, maximum duration, Namespace, allowed and selected profiles, push-ref policy and permissions. Reusing a reference after its policy changes cannot retain the old grant. The push-ref guardrail covers the client hook and the prepared broker's separate ref check.
Repository options
GET /namespaces/:namespaceId/agents/repository-options requires Agent create.
GET /namespaces/:namespaceId/agents/:agentId/repository-options requires
update on that Agent. Both read the local registry and return repositoryRef,
displayName, allowedProfiles and any configured description. The catalog
allows 1,000 ordinary repositories plus the separately registered Installation
option (1,001 choices total). OCC rejects overflow with
503 DEPENDENCY_UNAVAILABLE instead of truncating the catalog.
| Discovery result | Meaning |
|---|---|
| Success with choices | Select an approved reference and profile. |
| Empty list | No approved repositories. meta.notConfigured marks absent Installation repository settings. |
503 REPOSITORY_OPTIONS_UNAVAILABLE |
Discovery failed after authorization. A new ordinary draft can omit repositories. |
| HTTP 409 | The Namespace is closed. |
Edits require successful discovery. Writes reauthorize and resolve the selection. See Console repository access for creation and deployment rules.
Profiles
Native Git requires git-write and one writable branch. git-only permits
fetch and push. git-and-draft-pr also permits draft creation against an
approved base. Both exclude general REST, GraphQL, issues and arbitrary PR
operations. The OpenShell installation profile
inherits the App installation's repositories and permissions.
Standalone service inputs
Before listening, the service validates a protected JSON file with gateway,
sessionPolicy, backend and optional limits (positive safe integers).
Configuration and private keys must be private regular files owned by root or
the service user. Ancestors require the same owners and forbid group and other
writes. A root-owned sticky directory such as /tmp may sit above the
immediate parent. The loader rejects symlinks and file replacement.
See configuration and TLS ownership.
For production native Git, set backend.kind to github-prepared-git-registry
with backendId and registryFile. The Driver supplies controlSocket,
sessionDurationSeconds and publicCaPath. Kubernetes mounts only configuration,
registry and TLS in the sender. Only the worker mounts the App signing and custody
keys. The service image must trust GitHub's HTTPS certificate chain. For a
private CA, set NODE_EXTRA_CA_CERTS to a readable bundle and keep certificate
and hostname verification enabled.
See Backend configuration and Installation setup.
The service refuses the legacy github-app and github-app-registry
backends. Leave uncertain issuance or PR creation with the original owner.
Keep the App key there and retain the existing approval scope. The development
github-token backend requires both its configuration opt-in and
--development-authority. See development token authority.
Token Service lifecycle
The Token Service signs an App JWT, validates the issued token’s scope and expiry, and stores it encrypted in PostgreSQL. An authorized Credential Proxy metadata GET resolves a github_app_installation reference. Its provider generation and registered grant determine which installation token can serve the request. Git sessions use background preparation until the session expires. See issuance, renewal and withdrawal.
PREPARED_GITHUB_RECOVERY_REQUIRED stops minting. Recover the issuing owner
with its original attempt and custody. Wait until 72 minutes after intent or
verified provider expiry plus clock skew, whichever is later. A GET cannot
settle uncertain issuance.
Sessions and closure
Only the trusted worker or operator has access to the private mode-0600 Unix control socket. Its parent directory stays outside the client. The HTTPS client listener exposes no admission or close endpoint. Control bodies may be at most 16 KiB.
| Control request | Result |
|---|---|
GET /v1/capabilities |
Registry-backed service's durable admission version |
POST /v1/sessions with durationSeconds and optional profile |
Private status, client configuration and bearer on first creation |
GET /v1/sessions/{id} |
Private session and cleanup status |
POST /v1/sessions/{id}/close |
Immediate local closure status, with cleanup reported separately |
Recover an admission
Before dispatch, the CLI prints a nonsecret X-Admission-Id (13-digit Unix
timestamp in milliseconds, a hyphen and a lowercase UUIDv4). A new ID must be
under 60 seconds old and cannot be future-dated. HTTP 201 returns the bearer
once. Reusing the ID with the same duration and profile returns HTTP 200 with
status alone. Conflicts fail. Follow the lost-response procedure
before requesting replacement material.
For platform admission the worker requires durableAdmissionVersion: 1 before
new attempts and readiness. Checking this version does not inspect the journal.
Recovery, closure and runtime retirement can proceed without that check.
The saved attempt binds namespaceId, repositoryRef, normalized profile,
expectedBinding and deadlineWallMs. The broker reserves through the worker's
private receipt socket before it constructs the route or releases the
bearer. Recovery can durably fence a missing admission. For a reservation or
active session without a confirmed terminal receipt, disposal remains unknown.
Only the original broker records its DISPOSED result. Credential-free receipts
stay with the attempt. If the journal fails, new bound admissions stop and
absence or disposal cannot be confirmed.
Standalone admission correlations live in the process. At twice the session
limit the broker can return overloaded. Unresolved cleanup outlives that
window. Restart loses the correlations, and none can recover a bearer.
End a session
Closing or expiring a session prevents use and cancels its exchanges. A status read observes expiry without starting cleanup or cancellation. Clock rollback cannot restore even a reserved exchange. The bearer requires a live broker and upstream authorization.
An exchange retains its original credential through replacement. Replacement must cover the remaining budget and safety margin. After TLS verification, the sender rechecks the owned lease before writing. Prepared sends instead check the original one-use permit, cancellation and deadline. Withdrawal blocks new permits; an issued permit can still start one send before its deadline. Reconcile uncertain GitHub writes without replaying them.
Authentication and cleanup have separate monotonic deadlines from capture. Delayed settlement extends neither. Authentication stops 60 seconds before reported token expiry. Cleanup retains its one-hour bound from local receipt. A forward wall-clock jump can deny authentication without proving remote expiry.
CLOSED ends local use. Private status tracks pending, revoked, expired and
uncertain credentials and auxiliary cleanup. DISPOSED requires all actions,
token obligations and auxiliary cleanup to finish. Historical revoked or
expired counts may remain. Failed construction closes renewal immediately.
Retained material counts against capacity and shutdown's pendingAuxiliary
until callbacks and disposal finish.
GitHub response data
A protected metadata read
releases only { id, full_name } after checking repository identity and reaching
stream end without actual trailers. Joined OpenShell enforcement still needs
deployment proof.
For profiles that allow broader API access, bounded REST JSON responses omit
temp_clone_token from the repository, its parent and source, and a pull
request's head.repo and base.repo. GraphQL requests selecting tempCloneToken
receive HTTP 400. The broker rewrites pagination and followed links only after
checking origin, repository, route and profile. It translates
/repositories/<id> for the configured ID to the admitted /repos/OWNER/REPO
route. Direct ID-route requests remain unsupported. Pagination checks the length
and format of after and before. Other links, human text and unrelated
metadata pass through.
Client routing and limits
Git and GitHub CLI
The client runs /usr/bin/git with the user's arguments, HOME, identity, hooks,
aliases, remotes and worktrees. Git and gh preserve exit codes or return
128 + signal number.
At startup, Kubernetes copies the client material and prepares its private
subPath. Preparation checks generation, manifest, session metadata, paths and
ownership before writing gitconfig. It never reads a bearer or opens a session.
Each HTTPS host maps to one gateway origin and CA policy. Rewrites preserve
repository casing and an optional final .git. Other repositories on that
host also reach the gateway, but receive no bearer. Separate authentication
requires a native override. SSH and cross-repository submodules are unsupported.
The generated Git configuration resets helpers, sets credential.useHttpPath=true,
verifies TLS with optional CA trust and disables redirects for the exact gateway
origin. The helper checks the effective protocol, host, port, username and
repository path after Git decodes percent-escapes. An escaped spelling can
match the admitted host and path. The gateway rejects any % in a request
path. Dot segments, extra components, unmatched names or ambiguous .git
spellings yield no bearer. The broker constructs the canonical upstream path
for discovery, upload-pack and receive-pack. Endpoint, method, media-type,
service-query and profile checks still apply.
Select among duplicate bindings with OCE_REPOSITORY_REF. The gh launcher also
propagates OCE_REPOSITORY_SELECTION with generation, reference and session
ID. A pin cannot change the gateway origin. Conflicting or stale pins fail
remote authentication. The helper checks its prepared generation and original
deadline before reading the bearer. The gateway can close the session earlier.
Local Git commands skip the helper and still work. Pins do not freeze arbitrary
subprocesses. There is no command-wide preflight. Native settings can override these defaults and other helpers can
retain credentials. OCE's helper ignores store and erase.
For the API launcher, use GitHub CLI 2.103.2, GH_HOST=github.com and a
gateway hostname with verified TLS on port 443. Its private hosts.yml uses the
experimental api_host setting and stores the gateway bearer in oauth_token.
The launcher admits selected commands
and relative API paths. It rejects absolute destinations. Network controls must
separately enforce egress restrictions.
Service limits
| Resource | Default |
|---|---|
| Sessions and credentials | 16 sessions including pending cleanup, two credential slots per session |
| Provider actions | One active, 64 queued, 30 seconds per action |
| Sockets and exchanges | 64 sockets per listener, 32 exchanges total, four per session |
| Request head | 32 KiB and 64 header pairs, 8 KiB request target |
| Git data | 1 MiB fetch input, 256 MiB push input and Git output. Gzip input has separate wire and decoded limits. |
| API data | 1 MiB input, 8 MiB response |
| Exchange timing | Five minutes total, 30 seconds for response headers, five seconds without progress, 60 seconds of credential margin |
For a larger upload-pack response, set the fetch-specific bounds in protected
service configuration. For example, gitFetchResponseBytes: 17179869184 allows
16 GiB. Set gitFetchFirstHeaderMs and gitFetchStallMs to 60000 and
gitFetchExchangeMs and maxExchangeMs to 1800000 for a 30-minute ceiling.
Absent a fetch-specific limit, the corresponding gitResponseBytes,
firstHeaderMs, stallMs or exchangeMs override applies. maxExchangeMs
caps every exchange and defaults to the exchangeMs override when one is set.
Each stream occupies a socket and an exchange slot until completion or session expiry. Credential validity must cover the deadline plus the margin. Development static-token upload-pack is capped at its lease less two margins. Overrides must be positive and finite. See dispatch deadlines.
On SIGTERM or SIGINT, the service allows 60 seconds for cleanup. Unsettled
actions retain capacity until exit. On timeout it reports outstanding
obligations and terminates. Restart loses provider cleanup inventory.
Verify disposal or revocation separately.
The service flow and Agent flow trace the implementation. The testing guide distinguishes fixture coverage from live-provider qualification.
