Identities instead of tokens: agents as the owner's Oxygen delegates, signed requests over a Unix socket, ownership shown issue #4 closed: completed andymccall opened this · 1 comment
andymccall set labels feature, security ·
andymccall set milestone Sprint 1 ·
andymccall edited ·
andymccall set labels feature, security ·
andymccall edited ·
andymccall referenced by change 09752034b5a8 on main ·
Done: spectra init records the owner's Oxygen identity (proven by signing with it) and makes the instance key; spectra agent add makes the agent's key in its own OXY_HOME, prints the oxy identity delegate add to run, waits, and records the agent only once the owner's identity delegates to it (checked against the real oxy, expiry included). Ownership is shown as 'claude-oxygen · Andy McCall's agent'. spectra agent list|disable, spectra identity show|refresh (a successor is accepted only with the same key pair and a signature by it). spectra admin verify now replays identities and delegations in ledger order and checks every signed event; tests cover an agent without delegation, an agent naming another agent or the owner as its actor (a gap found and fixed here), a forged successor, expiry, disable, and a swapped proof. spectra-core's Authenticator checks signature, method, body, a five-minute window, single-use nonces and the uid binding; issue #7 mounts it on the Unix socket with SO_PEERCRED, which is where the 'every interface' test lands. One process writes a ledger at a time (spectra.lock). Spec 0.3a records the decisions.
andymccall closed ·
Nobody is trusted for where they connect from: loopback proves nothing, since any process of any user on the machine can reach the socket or port (spec §5, §8, §21). Instead, every principal is an Oxygen identity and every request is signed.
spectra init: reads the owner's identity (oxy identity exportfrom theirOXY_HOME) into the ledger asidentity.owner; makes the instance identity (keys/instance.secret, 0600;identity.instancein the ledger) and prints theoxy identity delegate addfor the owner to run.spectra agent add NAME --kind K --project P --home DIR [--identity FILE] [--uid N]: makes the agent's key pair in its ownOXY_HOMEthroughoxy-core, prints theoxy identity delegate add DIR/identity --scope changes --expires 8hfor the owner, and refuses to finish until the owner's current identity delegates to the key. Recordsagent.addedwith the identity, the owner and the delegation. There is no agent without an owner.claude-oxygen · Andy McCall's agentin Radar, the CLI and every tool result's text, from the owner identity's name (§8.1).<data>/spectra.sock(0660) withSO_PEERCRED; the loopback TCP port for Radar. Signed requests:Spectra-Key,Spectra-Time,Spectra-Nonce,Spectra-Signature(a composite signature with the domainspectra1 requestovermethod || BLAKE3(body) || time || nonce); a five-minute window, a nonce cache, and the owner's delegation to the key checked at every request, so an expired--expires 8hstops the agent by itself. The signature is the resulting events'auth.proof.[security] allow_tokens(spectra agent add --token) and are level 1. The owner's CLI signs with the owner's key (level 2). The passkey and the[policy]classes are issue #19, in 0.2; until then Radar signs the owner in throughspectra serve --open's one-time link signed by the CLI key, a level-2 session with a banner.--uidor--learn-uidbinds the key; a request with that key from another uid is refused and logged asguard.impersonation; one key on two connections under one uid isguard.key_shared.spectra identity show|refresh(a successor identity of the owner; a removed delegation disables the agent andverifymarks its past events valid but revoked).Done when a test shows: an unsigned request refused on every interface; a request signed by a key the owner has not delegated to refused; one signed by an expired delegation refused; a key from the wrong uid refused and logged; and two agents under different uids each attributed to their own owner-delegated key in the ledger. After issue #3.