Skip to content

bug: key rotation replaces human attribution with system:key_rotation, breaking the human→agent audit chain #281

Description

@KunalJavelin

Summary

Rotating a service key replaces the human attribution on every token it subsequently mints. act.sub becomes the literal string system:key_rotation instead of a user id, so "which human initiated this action" is permanently lost for the rotated credential and every agent identity delegated from it.

Evidence

Before rotation, an api-key-grant token carries the human who created the key — which is correct and by design (internal/service/oauth.go:1398, ActingUserID: sk.CreatedBy):

act.sub    "user_3FX8E1Sd…"

After POST /agents/registry/{id}/rotate-key on the same identity, a fresh exchange of the new key yields:

HUMAN        act.sub on orchestrator token : system:key_rotation
ORCHESTRATOR sub                           : spiffe://highflame.dev/<acct>/<proj>/agent/hf-portal-concierge
SUBAGENT     act.sub (who delegated)       : spiffe://highflame.dev/<acct>/<proj>/agent/hf-portal-concierge
             delegation_depth              : 1

The agent→agent link survives rotation intact. Only the human→agent link is severed, because the rotated key's CreatedBy is the rotation subsystem rather than a user.

Impact

This was found while validating a customer requirement stated as:

"preserve a traceable chain showing which human, workflow, orchestrator, and sub-agent initiated each action"

Every other link in that chain holds up well. This one breaks on a routine operational action — and key rotation is something we actively encourage. The failure is silent: nothing in the token or the guard response indicates that the human attribution was lost rather than never present, so an audit months later cannot distinguish "rotated key" from "no human involved".

It is also unrecoverable for existing credentials: created_by/owner_user_id is fixed at registration and cannot be updated.

Fix options

  1. Preserve the original creator across rotation (recommended). Rotation changes the secret, not who owns or created the identity. Carry the prior key's CreatedBy onto the new key so act.sub continues to name the human.
  2. Or record the rotation actor. If rotation is performed by a human via Studio/API, that human is the better value than system:key_rotation. Only truly automated rotation should fall back to a system principal.
  3. If a system principal is unavoidable, keep the human separately. Emit the identity's owner_user_id as its own claim so the human is still recoverable even when act.sub is a subsystem — and so consumers can tell "rotated" apart from "no human".
  4. Make it visible. Whatever the resolution, a token whose human attribution is a system principal should be distinguishable in Studio's audit view, not just in the raw claim.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions