Close the second round of review findings: GrantedCode, no minting from a name

This commit is contained in:
bigpickle 2026-10-05 21:51:06 +08:00 committed by leora
parent db9a450f0c
commit a7968dd4cb

View File

@ -92,25 +92,50 @@ def issue_agent_session(
} }
def issue_view_session( class GrantedCode:
store: Store, """Proof that a one-time view code was redeemed.
agent: str,
scopes: Optional[List[str]] = None,
ttl: int = 3600,
granted_by: Optional[str] = None,
) -> dict:
"""Mint a human_via_agent session.
Plumbing primitive, not an entry point: it trusts whoever calls it. Code Constructed only by viewcodes.redeem_code(), and only from a code that
reached from outside the process must authenticate first. issue_code() actually matched. Minting a human session now requires one of these, so the
enforces that by requiring an agent session, and redeem_code() may present a session minting path cannot be reached by passing an agent name.
one-time code as its credential.
`granted_by` is the agent that approved the code. It is recorded so the This is a guardrail against wiring mistakes, not a security boundary: any
audit trail can answer "who let this human in" and not merely "which code running in this process can construct it. The boundary that matters is
agent's things did they see". that no network-reachable route reaches this function without either an
agent session (issue_code) or a valid code (redeem_code).
""" """
granted = list(scopes if scopes is not None else HUMAN_VIEW_SCOPES)
__slots__ = ("agent", "scopes", "ttl", "_sigil_internal")
def __init__(self, agent: str, scopes: List[str], ttl: int, _internal=None):
if _internal is not _ISSUER:
raise errors.SigilError(
"GrantedCode cannot be constructed directly; it comes from"
" viewcodes.redeem_code()"
)
self.agent = agent
self.scopes = list(scopes)
self.ttl = ttl
self._sigil_internal = _internal
def __repr__(self) -> str:
return f"<GrantedCode agent={self.agent!r}>"
_ISSUER = object()
def issue_view_session(store: Store, grant: "GrantedCode") -> dict:
"""Mint a human_via_agent session from a redeemed code.
Takes a GrantedCode rather than an agent name, so the only way to reach a
human session is to have redeemed a code. `granted_by` is the agent that
approved it, recorded so the audit trail can answer "who let this human in"
and not merely "which agent's things did they see".
"""
agent = grant.agent
granted = list(grant.scopes)
ttl = grant.ttl
extra = [s for s in granted if s not in HUMAN_VIEW_SCOPES] extra = [s for s in granted if s not in HUMAN_VIEW_SCOPES]
if extra: if extra:
# Defence in depth: even if a caller tries to widen a human session, # Defence in depth: even if a caller tries to widen a human session,
@ -125,7 +150,7 @@ def issue_view_session(
agent, agent,
granted, granted,
expires, expires,
granted_by=granted_by or agent, granted_by=agent,
) )
store.record( store.record(
"session.issued", "session.issued",
@ -135,7 +160,7 @@ def issue_view_session(
"kind": HUMAN_VIA_AGENT, "kind": HUMAN_VIA_AGENT,
"scopes": granted, "scopes": granted,
"expires_at": expires, "expires_at": expires,
"granted_by": granted_by or agent, "granted_by": agent,
}, },
) )
return { return {
@ -143,7 +168,7 @@ def issue_view_session(
"kind": HUMAN_VIA_AGENT, "kind": HUMAN_VIA_AGENT,
"subject": f"{agent}'s human", "subject": f"{agent}'s human",
"on_behalf_of": agent, "on_behalf_of": agent,
"granted_by": granted_by or agent, "granted_by": agent,
"scopes": granted, "scopes": granted,
"expires_at": expires, "expires_at": expires,
"ttl": ttl, "ttl": ttl,
@ -198,4 +223,6 @@ def assert_may_act_as_agent(session: dict) -> str:
def revoke(store: Store, token: str) -> None: def revoke(store: Store, token: str) -> None:
store.revoke_session(hash_token(token)) store.revoke_session(hash_token(token))
store.record("session.revoked", actor="system", detail={"token_prefix": token[:8]}) # A digest, not a prefix of the token: the log outlives the session.
store.record("session.revoked", actor="system",
detail={"token_ref": "tok:" + hash_token(token)[:12]})