What if the passing test is the thing that changed?
A small Tamperward experiment produced two green test runs for the wrong reason. Comparing them with the original checks exposed the difference.
You need the repository; the host keeps the reusable key. WardOS checks what the agent asks to do—and whether a passing test still describes the files being reviewed.
You ask a coding agent to fetch a repository. It needs access to the code, so you give it a GitHub token. Job done. Except you’ve now given it something reusable that may open considerably more than the repository you had in mind. “Please fetch this project” has quietly become “Please look after this key.”
WardOS takes a different approach. For its supported GitHub gateway, the host keeps the token and checks requests before using it. Fetch the approved repository? That can proceed. Push changes through a read-only grant? Refused. Try another repository? Refused again. The agent can even edit its Git remote, but the grant still points to the repository fixed when the session began. Changing the address on the envelope doesn’t change where the host is willing to deliver it.[2][3][4]
That’s the useful distinction: the agent gets a way to do an authorised job without receiving the reusable credential behind it. Its instructions can say “read only”. The gateway gives those words something solid to stand on.
WardOS is Hamsa Software’s Linux workstation and execution-runtime project for coding agents.[1] This note follows public commit 1732a0cc: the GitHub gateway and the checks behind its verification indicator. The project’s tests were inspected, rather than independently run for this article.
Take a session working on hexrift/WardOS. The agent needs to fetch code before it can change anything. The host first checks the policy: deny refuses access, ask requires an explicit GitHub grant, and allow can proceed automatically. It also needs a token available. If the adapter cannot identify the approved repository, no grant is created.[2] An unresolved destination is a reason to stop, rather than an invitation to let the agent choose one.
The host fixes that repository when the session starts. Later, the agent edits its Git remote to point somewhere else. It can change the project configuration, but its grant still names the original repository.[3] This is a necessary separation for a coding tool: the files it is allowed to edit cannot also become the place where it rewrites the host’s permission decision.
The fetch travels through a local relay. The host supplies authentication when forwarding an approved request to GitHub, so the agent can obtain the code without receiving the token. Supported API clients have a separate route to ask through. An unrelated HTTPS connection does not gain that credential simply because it came from the same sandbox. The gateway settings below explain which client routes participate; they are supporting configuration, not a promise that every GitHub tool will work unchanged.
Approved repository: SessionMeta::origin_repo
Linked worktree metadata: gitdir and commondir
Permitted Git paths: /hexrift/WardOS and /hexrift/WardOS.git
Permitted REST path: /repos/hexrift/WardOS
Local relay: 127.0.0.1:3128
Git: /github → github.com over TLS; Basic x-access-token
REST: /github-api → api.github.com over TLS; Bearer authentication
Compatible API client setting: GITHUB_API_URLA fetch is not a single request. Git first asks about available references, then uses its upload-pack service to receive objects. That second step uses POST, even though it belongs to the read-only operation.[3][4] Blocking every POST would therefore prevent the ordinary fetch we wanted to allow. The two request examples show why the host needs a more specific rule than a list of supposedly safe methods.
For the approved repository, GET, HEAD and OPTIONS pass. A POST passes only when the path, with its query removed, ends in /git-upload-pack. A push uses /git-receive-pack and is refused, along with PUT, PATCH and DELETE.[4] The proxy returns 403 and records that refusal before resolving or connecting upstream. The agent’s request reaches a decision at the host before it reaches GitHub.
Changing the repository name fails separately from changing the operation. The grant accepts the repository’s path segments, not any name beginning with the same letters. Adding an extra character or placing a permitted operation in the query string cannot turn another repository or operation into the approved one. The proxy removes the local route prefix before these checks; the matrix shows requests sent through the relay. The project’s integration tests exercise allowed fetches and refused requests against a controlled upstream.[9]
I would check these rules against the tool’s actual requests before promising it GitHub access. The gateway does not classify every REST endpoint as issues:read or contents:read; GitHub also applies the token’s permissions. A legitimate read exposed through another POST can be refused. That is a useful constraint to discover during integration, when a failed read is still a tool-compatibility problem rather than a surprise in the middle of the job.
GET /github/hexrift/WardOS.git/info/refs?service=git-upload-pack
POST /github/hexrift/WardOS.git/git-upload-packRequest examples derived from the reviewed public adapter and proxy rules for hexrift/WardOS. The local relay strips its route prefix before scope checking.
Scroll sideways to see all columns.
| Request through local relay | Route decision | Reason |
|---|---|---|
GET /github/hexrift/WardOS.git/info/refs?service=git-upload-pack | Pass route check | Granted repository; GET |
POST /github/hexrift/WardOS.git/git-upload-pack | Pass route check | Fetch POST exception |
POST /github/hexrift/WardOS.git/git-receive-pack | 403 scope refusal | Write not granted |
GET /github/hexrift/WardOS.gitx/info/refs | 403 scope refusal | Different path segment |
GET /github-api/repos/hexrift/WardOS/issues | Pass route check | Granted REST repository; GET |
Now end the session’s GitHub grant. Further requests through that route stop, but its host-held token may remain valid. The built-in route obtains that token from a host variable or vault file.[2] Keeping it away from the agent has reduced who handles the secret. If the host-side copy is exposed, its owner still needs to replace or revoke it at GitHub.
A credential provider can offer a different arrangement. With Vault or OpenBao token-role leases, the provider sets the token’s lifetime and WardOS can request revocation using a reference called an accessor. A KV secret is a stored value: closing its WardOS route leaves the underlying secret valid, as with the local vault.[2] Choose between those arrangements according to whether the job needs an end to the route or an end to the credential itself.
The proxy checks for revoked grants and expired leases before scope checking and again at the relevant sending boundary.[11] Source revocation may remain unconfirmed if the provider is unavailable. A request already handed to a socket cannot be recalled by withdrawing the grant or pausing the agent. The receiving service’s records are still needed to discover what that earlier request did.
Fetching the repository is only the start. The agent changes the code and asks for verification. ward verify captures a candidate snapshot, creates a fresh scratch tree and restores protected verification inputs from the entry snapshot. It runs the verifier offline in isolation.[5] Editing the worktree’s test cannot simply replace the protected starting test and award the candidate a pass.
The writer must stop while that snapshot is taken. Otherwise the capture can collect files from different moments, producing a combination the agent never actually had on disk. WardOS takes a host-owned hold, confirms that changes have stopped and keeps the hold through the snapshot walk. Its integration test compares inconsistent captures without that hold with consistent held captures.[6] Freezing the files still leaves the quality of the protected tests to their author.
After a pass, the agent can edit again. The reading shell compares a digest—a fingerprint of the current worktree—with the candidate named by VerificationPassed. A different digest makes the pass stale. If reading fails or the observation is interrupted, freshness becomes unknown and the current green assurance is removed.[7][10] The old result remains in the history; the person reviewing changed files needs to see why it no longer describes them.
That comparison can itself finish late. An edit may invalidate the workspace while its digest is being calculated. WardOS discards the older result when a newer invalidation has occurred.[10] Otherwise yesterday’s answer could arrive last and restore the green mark beside changed work. The result needs to belong to the version being reviewed, rather than win a race between calculations.
The reviewed WardOS freshness path is separate from its GitHub credential gateway.
Scroll sideways to see all columns.
| Observation | Current assurance |
|---|---|
| Worktree digest matches verified candidate | Pass refers to the observed candidate state |
| Digest differs after an edit | Stale; historical pass remains recorded |
| Read interrupted or unreadable | Unknown freshness; remove current green assurance |
| Late digest after newer invalidation | Discard the outdated observation |
The runtime needs Linux facilities including bubblewrap and unprivileged user namespaces. Its daemon, proxy and snapshot store remain trusted; host root compromise or kernel privilege escalation sits outside this protection.[1][6] An approved destination can also receive information. Limiting where a request goes does not decide whether its contents were sensible to disclose.
The selftest uses CANNOT-MEASURE-HERE when the host cannot check a property. The documented bubblewrap path, for example, reports a missing cgroup resource limit instead of counting it as passed.[6] Before giving an agent a real task, check what that machine actually obtained. A design feature and a successful local check are different reasons to rely on a boundary.
The reviewed documentation also leaves cloud STS backends and newly minted-token delivery for future work.[2] At-rest encryption, update rollback and a distinct verifier user identity remain security-model targets.[6][8] The README warns that main may be newer than a released binary.[1] Installation therefore needs a version decision as well as a list of promising features.
For the session fetching this repository, I would keep two answers visible: which requests the host permits, and which files the verifier passed. The agent can carry on editing. Its ability to do that work should come from a bounded route, while the person reviewing the next change can tell when the earlier pass has stopped applying.
Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.
See the Payment Operations PilotPrimary sources behind this article. Living documentation may change.

Practical engineering. A human perspective.
More about Hamsa Software and the person behind it. Editorial notes.