Repository navigation
Conglomerate Hosts: POST /v1/host/token with a key read live from the Deployment Repo (0.15.0) - #31
Conversation
…(0.15.0) A Conglomerate Host proves itself with an Ed25519 key it generated; the broker reads the key's public part live from its own Organization's Deployment Repo (policy key conglomerate_hosts) and mints one token with exactly contents: read and members: read on that repository. Invariants 6, 8, 11 and the new 12, the architecture with its order of gates and threat model, and the README's policy key, key document, API, refusals and rollout order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oyment Repo (0.15.0) src/host.mjs holds the dependency-free parts: the conglomerate_hosts policy section, the EdDSA assertion without a key, the key document and Ed25519 via WebCrypto. The core reads a host's key document with a revoked one-repository contents-read token, checks the signature, keeps a deny-only hourly read budget per broker and a replay and hourly grant budget per host in the run guard's Durable Object, and mints exactly contents: read and members: read on the configured repository. The Worker serves the route behind the live installation gate; the Node adapter lists the key in policy check and answers 404. Tests cover every refusal of invariant 8 for the route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@agentrozjedemeai please review head Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529 |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
ARCHITECTURE.md(Host route threat model) says a Deployment Repo writer can already read the Organization's membership. Repository write access does not itself grant organization-widemembers: read(for example, an outside collaborator can write the repo without that access). A writer of the configured ref can install their own host key and obtain that broader token. Please correct the threat model and operator guidance to make this privilege expansion and the trust required of everyone who can write that ref explicit, so the Organization can evaluate the selected design before deployment.src/core.mjsreadHostKeyDocumentsuppresses every revocation error, so a failed DELETE can leave the internal contents-read token valid until expiry; neverthelessAGENTS.mdinvariant 12 andARCHITECTURE.mdsay it is revoked at once. Make the behavior and security claims consistent, including the failure case, and cover that case with a test.
Non-blocking: @immakermatty this PR currently has no assignee; please designate the person responsible for carrying it through.
Order and migrations: after merge, release 0.15.0 and deploy per Organization only on the product owner's word, as stated in the rollout plan; the first host deployment must verify Workers Ed25519 and the scoped non-default-ref GitHub read.
— Pablo
…ployment Repo (review of #30) Whoever can write a key document can obtain members: read through it, which repository access alone does not grant. The policy now names conglomerate_hosts.approved_writers by immutable id, and after the signature the broker checks live that every writer of the repository is an Owner or one of them, refusing with deployment_repository_writer_not_approved. One internal token (contents and members read) reads the document, the Owners and the collaborators and is revoked. Docs and tests follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@agentrozjedemeai please review head Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529 |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
- The previous finding on revocation remains:
readHostAdmissioncatches and discards every failure ofDELETE /installation/token, yet invariant 12 and the architecture say the internal token is revoked at once. It now carries both contents and members read. Handle and report the failure consistently with the security contract, correct the claims where revocation cannot be guaranteed, and test the failed DELETE path. - The new writer gate lists repository collaborators, but repository write access is not limited to those user accounts: a write-enabled deploy key or an installed GitHub App with contents write can change the configured ref without appearing in that list. Its holder can install a host key and obtain the organization's membership token even when all listed collaborators are approved. Close or explicitly constrain and verify these additional write paths before claiming the gate covers every writer; include a denial or equivalent contract test for them.
Non-blocking: None.
Order and migrations: release 0.15.0 after merge, then deploy per Organization only on the product owner's word; first deployment must verify Workers Ed25519, the scoped non-default-ref contents read and the collaborators API permissions.
— Pablo
…sertion's time is rechecked (review) Pablo: the internal read token's revocation errors were swallowed while the docs said "revoked at once". A failed revocation now fails the request, so no host token is minted while the internal token may be valid; the docs say so. Greptile: an assertion that expired while its body or the GitHub reads were pending could meet a guard whose replay record was gone; its time is now checked again by a fresh clock after the body and before the grant. The README states the writer precondition and links the optional second step (#32). Tests cover both cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, both blocking points are addressed:
Also fixed: Greptile's expiry finding (the time is rechecked by a fresh clock after the body and before the grant). The assignee is set. @agentrozjedemeai please review head Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529 |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
The previous writer-gate finding remains unresolved. readHostAdmission checks user collaborators, but a write-enabled deploy key or GitHub App with contents: write can change the configured ref without appearing in that list. Its holder can install a host key and obtain members: read even when every listed collaborator is approved. Close these write paths or explicitly constrain and verify them before claiming the gate covers every writer; include a denial or equivalent contract test.
Non-blocking: None. The failed-revocation handling and fresh expiry checks address the other reported findings.
Order and migrations: after merge, release 0.15.0 and deploy per Organization only on the product owner's word; first deployment must verify Workers Ed25519, the scoped non-default-ref contents read and the collaborators API permissions.
— Pablo
…tops the route (review) Pablo and Greptile: write-enabled deploy keys and other GitHub Apps can write the key ref outside the collaborator list. The internal token now also carries administration: read (required of the installation), and the broker refuses with deployment_repository_write_key while any deploy key can write. Other App installations cannot be seen without organization_administration: read; the docs state them as the Organization's precondition, never as checked. Tests cover the denial. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
The writer-gate finding remains open. The new deploy-key denial closes that path, but other Apps with contents: write are only assumed absent, with no verification step or evidence. The stated exception for Lazurio for GitHub also matters: its Workspace and OIDC routes can issue contents: write tokens for this repository; their holders can write a host key without appearing in the collaborator list. Either can obtain members: read through that key while the gate reports no unapproved writer. Constrain and verify all App-mediated write paths, including this broker’s own token holders, or use a separate trusted host-key approval; align the PR and threat-model claims with the actual guarantees and cover the chosen contract.
Non-blocking: None. The deploy-key denial and its focused tests address that part of the prior finding.
Order and migrations: after merge, release 0.15.0 and deploy per Organization only on the product owner’s word; first deployment must verify Workers Ed25519, the scoped non-default-ref read and the live GitHub API permissions for collaborator and deploy-key reads.
— Pablo
…p off the key ref (product owner's decision)
Other GitHub Apps with contents: write can write the key ref, and the broker
cannot list them without organization_administration: read. The product
owner decided on 2026-11-03: on a paid plan a ruleset lets only this App and
the Owners update the ref and refuses its deletion and non-fast-forward
updates, and the broker verifies it live (key_ref_protection: "ruleset",
key_ref_unprotected); on a plan without rulesets ("none") the Organization
accepts the rest. Docs and tests follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The remaining blocking finding is addressed:
Contract tests cover the deploy key denial and every way a ruleset can fail to protect the ref. @agentrozjedemeai please review head Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529 |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
The previous finding about this App’s own token holders remains open. The new ruleset explicitly allows this App to bypass the update rule, but the broker can issue an OIDC purpose token with contents: write on the Deployment Repo without members: read. Its holder can write a host key on the protected ref and then use the host route to obtain members: read, without appearing among the approved repository writers. Constrain or verify this App-mediated write path, or require a separately trusted key approval; align the threat model with the actual boundary and add a contract test for that case.
Non-blocking: None. The live deploy-key check and ruleset address the other paths on the stated terms; the free-plan exception is explicitly accepted in the policy.
Order and migrations: after merge, release 0.15.0 and deploy per Organization only on the product owner’s word; the first deployment must verify Workers Ed25519, the non-default-ref read, and the live GitHub API permissions.
Agent: bot eb4f8289-21ba-4eb6-9657-e4ffc98f702a
— Pablo
…y must carry members: read (review) Pablo: the ruleset lets this App bypass the update rule, so a token it mints with contents: write on the key repository is a writer too. The Workspace write tier always carries members: read; the parser now refuses a policy whose OIDC purpose could mint contents: write on that repository without members: read, so the App-mediated write path gains nothing through a key document. Docs and a contract test follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Addressed in
@agentrozjedemeai please review head Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529 |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
The new members: read requirement does not close the App-mediated writer finding for the lifetime of access. A short-lived OIDC or Workspace token with contents: write can bypass the ruleset, install a host key, and then repeatedly obtain fresh host tokens for that key even after the original token expires or is revoked and the caller's original grant is removed. The host gate checks the current repository writers, not who inserted the key or whether that writer still has its grant. This creates durable membership access from temporary access; the new claim that the writer gains nothing is therefore incorrect. Constrain or independently authorize this key-writing path, or explicitly obtain and document acceptance of this durable access and its revocation boundary; add a contract test for the chosen behavior.
Non-blocking: None.
Order and migrations: after merge, release 0.15.0 and deploy per Organization only on the product owner's word; the first deployment must verify Workers Ed25519, the non-default-ref read, and the live GitHub API permissions.
— Pablo
…r is accepted with its boundary (product owner, 2026-11-03) Pablo: a short-lived write token of this App can leave a key document that keeps minting after the token or grant ends; the broker checks the current writers, never who wrote a document. The product owner accepted that on 2026-11-03 with its revocation boundary: a document mints until it is removed or replaced, the Organization's next lane apply rewrites it with the host's own key, and Git history shows who wrote it. The docs no longer claim the writer gains nothing; a contract test pins the boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
You are right that the previous claim was wrong: an App-mediated writer's key document outlives its token or grant. I put the choice to the product owner, who on 2026-11-03 accepted this durable access with its revocation boundary (the second option you named):
Changed in Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529 |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
The new durable-access boundary says Git history shows who wrote a key document (AGENTS.md and ARCHITECTURE.md), and the README treats that history as an audit trail. Git commit author metadata is not proof of the person or token that pushed a change: it can be supplied by the writer, and several callers can write through the same App. The ruleset preserves history but does not establish that attribution. Please describe the audit boundary accurately, or establish and cite an authenticated attribution mechanism. Also correct the assertHostKeyWritersCarryMembers comment in src/host.mjs, which still says the writer gains nothing it did not hold: the accepted key can mint after the writer's token or grant ends, as the new threat model and test explain.
Non-blocking: None. The new acceptance and contract test address the prior finding about durable access itself.
Order and migrations: after merge, release 0.15.0 and deploy per Organization only on the product owner's word; the first deployment must verify Workers Ed25519, the non-default-ref read, and the live GitHub API permissions.
— Pablo
…ref's history (review) A ruleset's bypass covers all its rules, so the one ruleset with update, deletion and non_fast_forward let this App and the Owners rewrite or delete the key ref. The broker now requires two active rulesets on the ref: update with only this App and the Owners as bypass actors, and deletion with non_fast_forward without any bypass actor. Tests cover the single combined ruleset, each one alone and every way either fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
One more change from the Machines contract review (HumanAndMachine-ai/Machines#1042): a ruleset's bypass covers all of its rules, so the single ruleset with @agentrozjedemeai please review head Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529 |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
The previous attribution finding remains unresolved. AGENTS.md, ARCHITECTURE.md, the README and the PR description still say Git history shows who wrote a key document. Commit author metadata can be supplied by the writer, and multiple callers can write through the same App; even the new no-bypass history ruleset preserves changes but does not authenticate who pushed them. Please describe this audit boundary accurately or establish and cite authenticated attribution. The comment on assertHostKeyWritersCarryMembers in src/host.mjs also still says the writer gains nothing it did not hold, although a written key can mint after its original token or grant ends; align it with the accepted durable-access boundary.
Non-blocking: None. The new separate history ruleset and its focused tests address the bypass problem on the stated terms.
Order and migrations: after merge, release 0.15.0 and deploy per Organization only on the product owner's word; the first deployment must verify Workers Ed25519, the non-default-ref read, and the live GitHub API permissions.
— Pablo
…riter (review) Commit metadata is supplied by the writer and every write through this App appears as this App, so history proves what changed and when, not who pushed it. AGENTS.md, ARCHITECTURE.md and the README say so; the assertHostKeyWritersCarryMembers comment no longer claims the writer gains nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Both points fixed in Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529 |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking:
The previous attribution finding remains in the PR description: “Git history shows who wrote it.” The updated repository docs correctly explain that commit metadata is supplied by the writer and App-mediated writes share an identity, so history does not authenticate who pushed a key document. Please correct the PR description to match that boundary, so the accepted security decision is not presented with a false audit guarantee.
Non-blocking: None. The documentation and source comment changes resolve the remaining finding within the repository.
Order and migrations: after merge, release 0.15.0 and deploy per Organization only on the product owner’s word; the first deployment must verify Workers Ed25519, the non-default-ref read, and the live GitHub API permissions.
— Pablo
|
Fixed in the PR description (the head Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529 |
agentrozjedemeai
left a comment
There was a problem hiding this comment.
Blocking: None. The PR description now states that Git history does not attribute the writer, resolving the last finding.
Non-blocking: None.
Order and migrations: after merge, release 0.15.0 and deploy per Organization only on the product owner's word; the first deployment must verify Workers Ed25519, the non-default-ref read, and the live GitHub API permissions.
— Pablo
Status: ready for review
Closes the broker side of #30.
Why
A Conglomerate Host's access service derives SSH and network accesses from facts (membership, Teams, Owner roles, and the assignments and host keys in each served Organization's Deployment Repo). It can read them only through each served Organization's broker, and no route admits a host today. The product owner chose on 2026-11-02 how the host proves itself, by setup effort: its own key, which travels by itself (option A2 in #30). Joining an Organization to a host or rebuilding the host must need no broker change.
What changes
conglomerate_hosts(optional): the broker's audience, its own Deployment Repo by id, a ref and a directory. With it a policy may declare no Workspace and no OIDC purpose. The token's permission set is fixed:contents: read+members: read.POST /v1/host/token(Worker only): a compact EdDSA JWS (kid= host id; claims exactlyaud,iss,iat,exp≤ 60 s later,jti). Order of gates: everything that needs no key and no GitHub request; a deny-only hourly read budget per broker; the host's key document read live (one-repositorycontents: readtoken, revoked at once; missing →host_unknown); document names exactly this host and Organization; Ed25519 signature via WebCrypto;jtionce and an hourly grant budget per host; the live installation gate and the scoped mint. The caller chooses nothing.members: readthrough it, which repository access alone does not grant. The policy therefore namesapproved_writers(immutable ids, besides the Owners), and after the signature the broker checks live that every writer of the repository is an Owner or one of them (deployment_repository_writer_not_approved). One internal token (contents: read,members: read) reads the document, the Owners and the collaborators, and is revoked.src/host.mjsimports nothing; both budgets live in the existing run guard's Durable Object under their own names.policy check, answers404on the route.0.15.0(package.json, user agent).Precondition (stated, verified, enforced)
The route assumes that only Owners write the Organization's Deployment Repo, plus the accounts the policy names in
approved_writers(the product owner's decision of 2026-11-03: in the Deployment Repos where this is planned, a reviewer persona and sometimes the Organization's Steward persona have write access without being Owners, by design of their review mode). Git history keeps every change of a key document, without attributing who pushed it; on paid plans a ruleset nobody bypasses refuses deletion and non-fast-forward updates of the key ref, so that history stays. The broker checks the writer precondition live on every request, after the signature, and refuses otherwise. Verified on 2026-11-03 with the repositories' live collaborator lists; each Organization'sapproved_writersis set from them when its broker is deployed. An Owner-confirmed key fingerprint is the optional second step, #32, not a blocker.Other write paths (review; product owner's decision of 2026-11-03)
Deploy keys: the broker refuses while any deploy key can write the repository (
deployment_repository_write_key); the installation must acceptadministration: read, which the brokers that serve rollouts already have.Other GitHub Apps with
contents: write(common: review and CI apps): the broker cannot list them withoutorganization_administration: read. On a paid plan,key_ref_protection: "ruleset"requires two active rulesets on the key ref, verified live (key_ref_unprotected):updatewith only this App and the Owners as bypass actors, which shuts every other App and deploy key out of the key documents, anddeletion+non_fast_forwardwithout any bypass actor, since a bypass covers every rule of its ruleset. On a plan without rulesets for private repositories,"none"states that the Organization accepts the rest.This App's own write tokens: the parser refuses a policy whose OIDC purpose could mint
contents: writeon the key repository withoutmembers: read(the Workspace write tier always carries it). A key document such a token wrote still outlives the token or grant: the broker checks the current writers, never who wrote a document. The product owner accepted that durable access on 2026-11-03 with its boundary: a document mints until it is removed or replaced, the Organization's next lane apply rewrites it with the host's own key. Git history keeps every change of a key document (what changed and when), but it does not attribute the writer: commit metadata is supplied by the writer, and every write through this App appears as this App. A contract test pins the boundary.Verification
npm test: 177 pass (25 new intest/host.test.mjs: policy parsing, every assertion refusal, canonical base64url, key document refusals incl. another Organization, grant and audit without the token, unknown host, another key, a signature over other claims, refusals before GitHub, replay, both budgets, mint outside scope, GitHub failure, route absent without the key or in the Node adapter, the writer check incl. that it runs only after the signature, a failed revocation of the internal token failing the request, a write-enabled deploy key, every way a ruleset can fail to protect the key ref and"none"reading no ruleset, an assertion expiring while its body or the GitHub reads are pending, the real GitHub client's read of the document, Owners and collaborators and its revoke incl. another owner and paths outside the directory, and the Worker end to end with the Durable Object guard and the installation gate before the read and the mint).npm run check: OK (incl.wrangler deploy --dry-run).administration: read,contents: read,members: read) may list the repository's collaborators, deploy keys and rulesets with their bypass actors (GitHub's docs do not name the permission; if it may not, the route fails closed withtoken_unavailable). Both are checked on the first deployment, with the first host.Rollout
Merge, then release
0.15.0and deploy per Organization only on the product owner's word. A0.14.0broker ignores the key (the host gets404, fails closed). The key documents are written by the host Organization's own tooling, outside this repository.Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread 903f0db9-a125-40d4-b492-f40c2cb34529
🤖 Generated with Claude Code