CRUMB a card from devarno-cloud

NFT-Style Capability Token System

iris advanced 6 min read

ELI5

A capability token is like a VIP backstage pass at a concert. It says exactly which areas you can access (capabilities), which concert it’s for (council scope), when it expires (TTL), and it’s signed by the venue so security knows it’s real. If you lose it or misbehave, the venue can cancel it immediately (revocation).

Technical Deep Dive

CapabilityToken Data Model

classDiagram
class CapabilityToken {
+UUID token_id
+UUID iris_sprite_id
+UUID meridian_council_id
+string[] capabilities
+datetime issued_at
+datetime expires_at
+dict metadata
+boolean is_active
+to_dict() dict
+compute_signature() string
+to_string() string
}

Token Generation Flow

flowchart TD
A["TokenGenerator.generate_token()"] --> B["Validate TTL ≤ max_token_ttl_seconds"]
B --> C["Fetch sprite from iris-service"]
C --> D{"Requested capabilities ⊆ sprite capabilities?"}
D -->|No| E["ValueError: Invalid capabilities"]
D -->|Yes| F["Create CapabilityToken"]
F --> G["Serialize to canonical dict"]
G --> H["Sign with HMAC-SHA256"]
H --> I["Format: {token_id}.{sig_32chars}"]
I --> J["Return TokenResponse"]

Token Properties

PropertyValueDescription
Token IDUUID v4Unique identifier for this token instance
Sprite IDUUIDThe IRIS sprite this token authorises
Council IDUUIDThe MERIDIAN council this token is scoped to
Capabilitiesstring[]Subset of the sprite’s declared capabilities
Issued AtISO 8601Token creation timestamp
Expires AtISO 8601Calculated as issued_at + ttl_seconds
Max TTL86,400s (24h)Configurable via AdapterConfig.max_token_ttl_seconds
Default TTL3,600s (1h)Used when no TTL specified

Token Serialization (Deterministic)

def to_dict(self):
return {
"token_id": str(self.token_id),
"iris_sprite_id": str(self.iris_sprite_id),
"meridian_council_id": str(self.meridian_council_id),
"capabilities": sorted(self.capabilities), # Sorted for determinism
"issued_at": self.issued_at.isoformat(),
"expires_at": self.expires_at.isoformat(),
"metadata": self.metadata,
}

Signing Algorithm

import hmac
import hashlib
import json
def compute_signature(self):
payload = json.dumps(self.to_dict(), separators=(",", ":"), sort_keys=True)
# ⚠️ Production: replace with secure key management
key = b"hardcoded-signing-key-replace-in-production"
return hmac.new(key, payload.encode(), hashlib.sha256).hexdigest()

Token string format: {token_id}.{first_32_chars_of_signature}

Example: 550e8400-e29b-41d4-a716-446655440000.a3f5c7d9e1b2f4a6c8d0e2f4a6b8c0d2

Token Verification

sequenceDiagram
participant Client
participant Adapter as iris-meridian-adapter
participant Store as MappingStore
Client->>Adapter: verify_token(token_string, token_id?)
Adapter->>Adapter: Parse "{id}.{sig}" format
Adapter->>Store: Lookup token by token_id
alt Token not found
Adapter-->>Client: Invalid
else Token found
Adapter->>Adapter: Recompute signature from stored payload
Adapter->>Adapter: Compare with provided signature
alt Signatures match AND not expired AND active
Adapter-->>Client: Valid
else
Adapter-->>Client: Invalid
end
end

Revocation

Tokens can be revoked by:

  1. Setting active=False in the mapping store
  2. The is_active property checks both active flag and expires_at time
@property
def is_active(self):
return self.active and datetime.now(timezone.utc) < self.expires_at

Token Fingerprint (Integrity)

The TokenResponse protobuf includes a token_fingerprint field — a Blake3 hash of the token’s canonical JSON payload:

token_fingerprint = blake3.blake3(payload_json.encode()).hexdigest()

This provides an additional integrity check independent of the HMAC signature.

Key Terms

  • Capability token → A time-limited, signed, council-scoped authorization credential
  • Capability-based security → Access control based on what actions are permitted (capabilities), not who you are (identity)
  • HMAC-SHA256 → Hash-based Message Authentication Code using SHA-256; provides integrity and authenticity
  • Token revocation → Invalidating a token before its natural expiration by setting active=False
  • Deterministic serialization → Sorting keys and capabilities so the same logical token always produces the same bytes for signing
  • Token fingerprint → Blake3 hash of token payload for additional integrity verification

Q&A

Q: Why HMAC-SHA256 instead of Blake3 for signing? A: HMAC is a standard construction for message authentication. While Blake3 has a built-in keyed mode (blake3(key=...)), HMAC-SHA256 is more widely supported and audited. The token’s integrity is additionally protected by a Blake3 fingerprint in the response.

Q: Can a token grant capabilities the sprite doesn’t have? A: No. TokenGenerator.validate_capabilities() ensures requested capabilities are a subset of the sprite’s declared capabilities. This prevents privilege escalation.

Q: What happens if the signing key is compromised? A: All tokens signed with that key could be forged. Production deployments must use a secure key management system with key rotation. The current hardcoded key is a placeholder.

Q: How long should tokens live? A: The default is 1 hour, max is 24 hours. Short-lived tokens reduce the window of compromise. For long-running operations, clients should request fresh tokens or implement refresh logic.

Q: Can tokens be renewed? A: Not directly. The current implementation issues new tokens rather than renewing existing ones. A refresh mechanism could be added by extending TokenGenerator.

Examples

Capability tokens are like ski resort lift tickets:

  • Token ID = The unique barcode on your ticket
  • Sprite = You (the skier)
  • Council = The specific ski resort (not valid at other resorts)
  • Capabilities = What you’re allowed to do: “green runs”, “blue runs”, “ski rental” (but not “helicopter skiing” unless you paid for that)
  • Expiration = End of day (ticket stops working at 5 PM)
  • Signature = The holographic stamp that proves the ticket is real, not photocopied
  • Revocation = If you violate safety rules, the resort cancels your ticket at the gate
  • Fingerprint = A microscopic security thread woven into the ticket paper

neighbors on the map