Summary
PATCH /v1/agents/:name accepts requireWorkspaceKey alone and shallow-merges caller-supplied metadata onto any agent record in the workspace. Nothing restricts a caller to its own record, and nothing reserves the metadata keys the platform relies on for identity.
// packages/engine/src/routes/agent.ts:291
agentRoutes.patch(
'/agents/:name',
requireWorkspaceKey,
rateLimit,
async (c) => {
...
const nextMetadata = body.metadata !== undefined || body.skills !== undefined
? {
...(existing.metadata || {}),
...(body.metadata || {}),
...
}
: undefined;
const updated = await agentEngine.updateAgent(db, workspace.id, name, {
status: body.status,
persona: body.persona,
metadata: nextMetadata,
capabilities: body.capabilities,
});
The :name parameter is whatever the caller sends. The merge is unfiltered.
Why this matters now
relay's v11.4.2 spawn-admission gate (AgentWorkforce/relay#1438) decides whether a name collision is an honest reclaim by comparing a caller-supplied identity key against the hash stored at metadata.identity_key on the incumbent's record:
// relay crates/broker/src/relaycast/auth.rs:990
let reclaims_same_work_unit = matches!(
(identity_key, existing_identity),
(Some(ours), Some(theirs)) if hash_identity_key(ours) == theirs
);
That check reads the very field this endpoint lets any workspace-key holder write. The sequence is:
PATCH /v1/agents/<victim> with {"metadata": {"identity_key": "<sha256 of a key you chose>"}}
- Register under
<victim> presenting that key
- The gate compares the stored hash to yours, they match, and it hands over the incumbent's id with a freshly rotated token
The gate is not bypassed — it is satisfied, with a proof the caller planted. Its own doc comment explains that the raw key is hashed precisely because "metadata is readable by any caller holding the same workspace key… storing the raw key there would let any workspace member replay it verbatim." Hashing stops replay of a read value. It does not stop a write.
POST /agents/:name/rotate-token (packages/engine/src/routes/workspace.ts:415) is also workspace-key-only, and reaches the same outcome in one step. Both need the same decision.
The threat model this actually sits in
On at least one production host the workspace key is readable by any local process: it is passed in broker pty argv and is visible through a process listing. That was independently reconfirmed twice on 2026-08-07 and is flagged for rotation.
I did not reproduce it — doing so requires exactly the full-argv process listing that leaks the key, and running it would surface the key into a transcript. Reported here on the strength of those two independent confirmations, and it should be verified by whoever owns rotation, in a way that does not persist the output.
So this is not a theoretical privilege boundary. On such a host the chain is: read the key from the process table → PATCH an identity_key onto any agent → reclaim that agent's identity through the gate.
Rotating the key does not close this while argv exposure stands. The new key lands in argv the same way the old one did. Rotation and this issue are independent fixes and neither substitutes for the other.
The conclusion worth stating plainly
relay#1438 is a coordination gate against honest collision, not a security boundary against a workspace-key holder. It reliably stops two well-behaved work units from silently stealing each other's name, which is the AR-448 failure it was built for and it does that job. It does not survive an adversary who can write the field it reads. Anything that describes it as an authentication or authorization boundary is describing it wrongly, and decisions resting on that description should be revisited.
Suggested direction
Not prescribing the fix, but the shape of the decision:
- Reserve platform-owned metadata keys.
identity_key, and the fleet block written by registerAgentViaNode, are platform state that happens to live in a caller-writable bag. Reject or strip them on the PATCH merge path. This is the smallest change that breaks the chain and needs no new auth model.
- Decide whether workspace-key auth should be able to mutate an arbitrary agent at all, or whether identity-affecting mutations need the target agent's own token. That is the larger question and it also governs
rotate-token.
- If the metadata bag is to stay open, then the gate cannot read its proof from it, and relay#1438 needs a store the caller cannot write.
Whichever way it goes, metadata being simultaneously a caller-writable bag and the store of record for an identity proof is the thing to resolve.
Verification
Route and merge logic read from main at 08ddec7. The relay gate reference read from origin/main. The attack chain is derived from those two code paths and has not been executed against a live workspace — writing an identity_key onto a real agent record is a destructive act against a running fleet and was not an appropriate way to confirm a finding that is legible from the source.
Related
Context
Filed from an incident investigation on 2026-08-07. Khaliq owns the merge gate — no agent merges.
Summary
PATCH /v1/agents/:nameacceptsrequireWorkspaceKeyalone and shallow-merges caller-suppliedmetadataonto any agent record in the workspace. Nothing restricts a caller to its own record, and nothing reserves the metadata keys the platform relies on for identity.The
:nameparameter is whatever the caller sends. The merge is unfiltered.Why this matters now
relay's v11.4.2 spawn-admission gate (AgentWorkforce/relay#1438) decides whether a name collision is an honest reclaim by comparing a caller-supplied identity key against the hash stored at
metadata.identity_keyon the incumbent's record:That check reads the very field this endpoint lets any workspace-key holder write. The sequence is:
PATCH /v1/agents/<victim>with{"metadata": {"identity_key": "<sha256 of a key you chose>"}}<victim>presenting that keyThe gate is not bypassed — it is satisfied, with a proof the caller planted. Its own doc comment explains that the raw key is hashed precisely because "metadata is readable by any caller holding the same workspace key… storing the raw key there would let any workspace member replay it verbatim." Hashing stops replay of a read value. It does not stop a write.
POST /agents/:name/rotate-token(packages/engine/src/routes/workspace.ts:415) is also workspace-key-only, and reaches the same outcome in one step. Both need the same decision.The threat model this actually sits in
On at least one production host the workspace key is readable by any local process: it is passed in broker
ptyargv and is visible through a process listing. That was independently reconfirmed twice on 2026-08-07 and is flagged for rotation.I did not reproduce it — doing so requires exactly the full-argv process listing that leaks the key, and running it would surface the key into a transcript. Reported here on the strength of those two independent confirmations, and it should be verified by whoever owns rotation, in a way that does not persist the output.
So this is not a theoretical privilege boundary. On such a host the chain is: read the key from the process table → PATCH an
identity_keyonto any agent → reclaim that agent's identity through the gate.Rotating the key does not close this while argv exposure stands. The new key lands in argv the same way the old one did. Rotation and this issue are independent fixes and neither substitutes for the other.
The conclusion worth stating plainly
relay#1438 is a coordination gate against honest collision, not a security boundary against a workspace-key holder. It reliably stops two well-behaved work units from silently stealing each other's name, which is the AR-448 failure it was built for and it does that job. It does not survive an adversary who can write the field it reads. Anything that describes it as an authentication or authorization boundary is describing it wrongly, and decisions resting on that description should be revisited.
Suggested direction
Not prescribing the fix, but the shape of the decision:
identity_key, and thefleetblock written byregisterAgentViaNode, are platform state that happens to live in a caller-writable bag. Reject or strip them on the PATCH merge path. This is the smallest change that breaks the chain and needs no new auth model.rotate-token.Whichever way it goes,
metadatabeing simultaneously a caller-writable bag and the store of record for an identity proof is the thing to resolve.Verification
Route and merge logic read from
mainat08ddec7. The relay gate reference read fromorigin/main. The attack chain is derived from those two code paths and has not been executed against a live workspace — writing anidentity_keyonto a real agent record is a destructive act against a running fleet and was not an appropriate way to confirm a finding that is legible from the source.Related
Context
Filed from an incident investigation on 2026-08-07. Khaliq owns the merge gate — no agent merges.