mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
docs(nips): define agent-key assertions in NIP-FA and NIP-FI (#8384)
Defines how an enterprise identity adapter issues assertions for agent keys and how a NIP-FI relay admits them. - **NIP-FA:** adapters issue agent-key assertions only under an owner-authorized grant, created through the owner's own authentication; revoking the owner invalidates every earlier grant. The "agent keys" out-of-scope bullet is removed. - **NIP-FI:** new `Agent keys` subsection under the assertion contract (`nostr_owner_pubkey` claim, the verifier returns the owner, owner recording for the session or request, ban/deny checks on both keys, owner-derived authorization), `agent_assertions` discovery field, owner deny-set check in WebSocket and HTTP admission, owner-matching disconnect close scan, and the updated out-of-scope delegation lines. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -127,6 +127,30 @@ authority. Signature verification remains mandatory before acceptance.
|
||||
`VerifyAssertion` MUST compare `aud` with the resolved community's expected
|
||||
audience; issuer policy selection by `iss` MUST NOT broaden either binding.
|
||||
|
||||
### Agent keys
|
||||
|
||||
An issuer MAY issue an assertion whose `nostr_pubkey` names an agent key
|
||||
acting for an owner key. Such an assertion MUST carry `nostr_owner_pubkey`:
|
||||
the lowercase hexadecimal encoding of one 32-byte public key, distinct from
|
||||
`nostr_pubkey`. An issuer MUST NOT issue an assertion without
|
||||
`nostr_owner_pubkey` for a key it has issued an agent assertion for, and MUST
|
||||
NOT issue an agent assertion while the owner is not authorized by the issuer.
|
||||
|
||||
A relay advertising `agent_assertions: true` MUST verify `nostr_owner_pubkey`
|
||||
as part of the assertion. A malformed value, or one equal to `nostr_pubkey`,
|
||||
MUST deny `evidence_rejected`. The relay MUST record the value as the
|
||||
owner of the session or request the assertion admits. If the relay already
|
||||
associates a different owner with the asserted key in the resolved community,
|
||||
it MUST deny `authorization_denied`.
|
||||
For every agent assertion, regardless of whether the asserted key is itself
|
||||
authorized in the community, the relay MUST deny when the community bans the
|
||||
asserted key or the owner, or when the issuer has denied either key. Only
|
||||
then, when the asserted key is not itself authorized in the community, the
|
||||
relay MUST authorize it if and only if the issuer is trusted for the community
|
||||
and the owner is authorized in the community when access is decided. A relay
|
||||
not advertising `agent_assertions: true` MUST deny an assertion carrying
|
||||
`nostr_owner_pubkey` `evidence_rejected`.
|
||||
|
||||
### Unverified claims
|
||||
|
||||
Relays MUST ignore claims they do not verify. An unverified claim — including
|
||||
@@ -248,8 +272,14 @@ VerifyAssertion(token, community, D, R_t):
|
||||
AssertAudienceMatch(claims.aud, community.expected_aud) or DENY(evidence_rejected)
|
||||
AssertTimeBounds(claims, policy) or DENY(evidence_rejected) // [FI-TRACE-ASSERTION-VALIDATION]
|
||||
k_claimed := ParseHexKey(claims.nostr_pubkey) or DENY(evidence_rejected)
|
||||
owner := absent
|
||||
if claims.nostr_owner_pubkey is present:
|
||||
AssertAgentAssertionsAdvertised(relay) or DENY(evidence_rejected)
|
||||
owner := ParseHexKey(claims.nostr_owner_pubkey) or DENY(evidence_rejected)
|
||||
AssertNotEqual(owner, k_claimed) or DENY(evidence_rejected)
|
||||
|
||||
return VerifiedAssertion(identity=(claims.iss, claims.sub), asserted_key=k_claimed,
|
||||
owner=owner,
|
||||
authority_deadlines=ComputeDeadlines(claims, snapshot))
|
||||
```
|
||||
|
||||
@@ -275,15 +305,19 @@ On WebSocket upgrade:
|
||||
[FI-TRACE-ASSERTION-KEY-MISMATCH]
|
||||
6. Register the session's proven `k` and admitting issuer `iss` in the
|
||||
relay's session table, making it visible to the disconnect close scan.
|
||||
Registration includes the session's owner, if any.
|
||||
Registration MUST occur before the deny-set check in step 7. This ordering
|
||||
ensures any connection that straddles a concurrent disconnect is caught by
|
||||
one side or the other: either the close scan sees the registered session, or
|
||||
the deny-set check (step 7) sees the inserted entry. Both checks use the
|
||||
same `(iss, k)` key. A concurrent disconnect close scan MAY cancel this
|
||||
registered connection before step 8; cancellation is terminal, so a
|
||||
same keys: `(iss, k)` and, for a session with an owner,
|
||||
`(iss, verified.owner)`. A concurrent disconnect close scan MAY cancel
|
||||
this registered connection before step 8; cancellation is terminal, so a
|
||||
cancelled connection MUST NOT be admitted.
|
||||
7. Check deny set for `(iss, k)`; active entry (`now < until`) → deny
|
||||
`authorization_denied`. [FI-TRACE-DENY-SET]
|
||||
7. Check the deny set for `(iss, k)` and, if `verified.owner` is present, for
|
||||
`(iss, verified.owner)`. Either active entry (`now < until`) denies
|
||||
`authorization_denied`.
|
||||
[FI-TRACE-DENY-SET]
|
||||
8. Admit the connection only if it remains live and uncancelled. The session's
|
||||
authority deadline is the minimum of all `authority_deadlines`; see Session
|
||||
policy.
|
||||
@@ -351,9 +385,10 @@ A disconnect call causes the relay to:
|
||||
capacity and a new entry cannot be inserted, the relay MUST reject the command
|
||||
`503`; no sessions are closed and no replay state is consumed.
|
||||
2. Close all live WebSocket connections registered under the command's issuer
|
||||
whose proven `k` equals the target pubkey, whether or not admission has
|
||||
completed, synchronously. Disconnect cancellation is terminal: a cancelled
|
||||
in-progress connection MUST NOT be subsequently admitted. Connections
|
||||
whose proven `k` equals the target pubkey or whose recorded owner equals
|
||||
the target pubkey, whether or not admission has completed, synchronously.
|
||||
Disconnect cancellation is terminal: a cancelled in-progress connection
|
||||
MUST NOT be subsequently admitted. Connections
|
||||
registered under another issuer are not affected.
|
||||
|
||||
The deny set is held **in relay memory only** — no durable storage, no schema
|
||||
@@ -517,12 +552,14 @@ performs all pure authorization checks and then, as its single atomic admission
|
||||
mutation (step 7), simultaneously reserves the `(iss, jti)` replay identity and
|
||||
inserts the deny entry — both or neither. A capacity failure at that step rejects
|
||||
`503`; neither the jti nor the deny entry is recorded, and the caller may safely
|
||||
retry the same signed command. On success, the relay closes all live connections
|
||||
registered under the command's issuer (`claims.iss`) whose proven `k` equals
|
||||
retry the same signed command. On success, the relay MUST close every live connection
|
||||
registered under the command's issuer (`claims.iss`) whose proven key equals
|
||||
`CommandResult.target_pubkey` or whose recorded owner equals
|
||||
`CommandResult.target_pubkey`, whether or not admission has completed.
|
||||
Disconnect cancellation is terminal: a cancelled in-progress connection MUST NOT
|
||||
be subsequently admitted. This close scan matches the same
|
||||
`(iss, target_pubkey)` key as the deny entry; connections registered under
|
||||
be subsequently admitted. This close scan matches the deny
|
||||
entry's `target_pubkey` against each connection's proven key and recorded
|
||||
owner; connections registered under
|
||||
another issuer are not affected. The `until` expiry is taken exclusively from
|
||||
the signed command JWT claim; the request body carries no `until` field. An
|
||||
unknown or unprovable pubkey is not an error; the relay responds `200`
|
||||
@@ -743,8 +780,10 @@ On each protected HTTP request:
|
||||
`evidence_rejected`. [FI-TRACE-HTTP-INGRESS]
|
||||
5. Assert `verified.asserted_key == k`; mismatch → deny `authorization_denied`.
|
||||
[FI-TRACE-ASSERTION-KEY-MISMATCH]
|
||||
6. Check deny set for `(iss, k)`; active entry (`now < until`) → deny
|
||||
`authorization_denied`. [FI-TRACE-DENY-SET]
|
||||
6. Check the deny set for `(iss, k)` and, if `verified.owner` is present, for
|
||||
`(iss, verified.owner)`. Either active entry (`now < until`) denies
|
||||
`authorization_denied`.
|
||||
[FI-TRACE-DENY-SET]
|
||||
7. Admit the request.
|
||||
|
||||
A body is **authorization-relevant** whenever any body byte influences the
|
||||
@@ -809,14 +848,16 @@ normative behavior for them:
|
||||
- Per-session (`sid`-level) revocation: issuer-side. The relay's revocation
|
||||
primitive is pubkey-scoped and community-wide, keyed `(iss, target_pubkey)`
|
||||
(see Admin disconnect API). If an issuer revokes an entire identity, it
|
||||
enumerates and disconnects each key it has asserted for that identity.
|
||||
disconnects each key it has asserted for that identity. A disconnect of an
|
||||
owner key also covers that owner's agent keys at relays advertising
|
||||
`agent_assertions: true`.
|
||||
Individual session/device logout is handled by assertion expiry within the
|
||||
configured TTL window.
|
||||
- Revocation signaling to the issuer/IdP: issuer-side; the issuer stops
|
||||
issuing assertions, which closes the relay window within assertion TTL.
|
||||
- Directory integration and account-offboarding automation: issuer-side.
|
||||
- Audit logging beyond what the relay operator chooses to retain: issuer-side.
|
||||
- Delegation: out of scope.
|
||||
- Delegation other than "Agent keys": out of scope.
|
||||
|
||||
## Discovery
|
||||
|
||||
@@ -848,6 +889,11 @@ issuer stops issuance after a successful deny, the residual ceiling is
|
||||
issuer-operational property, not a protocol invariant this field can
|
||||
advertise unconditionally.
|
||||
|
||||
`agent_assertions` (boolean, optional). `true` means the relay admits agent
|
||||
assertions as defined in "Agent keys". Absent or `false` means it does not.
|
||||
An issuer MUST NOT issue an agent assertion for a community whose relay does
|
||||
not advertise `agent_assertions: true`.
|
||||
|
||||
Discovery MUST NOT state issuer URLs, audiences, claim names, tenant IDs, or
|
||||
deployment-local identifiers. [FI-TRACE-DISCOVERY-PRIVATE]
|
||||
|
||||
@@ -896,7 +942,8 @@ For the deny-until-TTL disconnect model (issuer issues a successful disconnect
|
||||
call with an `until` timestamp), the relay inserts a deny entry for the target
|
||||
pubkey with expiry `until` and then closes all matching connections synchronously.
|
||||
Here, matching means connections registered under that issuer whose proven `k`
|
||||
equals that target pubkey, whether or not admission has completed; disconnect
|
||||
equals that target pubkey or whose recorded owner equals the target pubkey,
|
||||
whether or not admission has completed; disconnect
|
||||
cancellation is terminal, so a cancelled in-progress connection MUST NOT be
|
||||
subsequently admitted. Connections registered under another issuer are not
|
||||
affected. Any subsequent admission attempt for that pubkey under the same issuer
|
||||
|
||||
Reference in New Issue
Block a user