authsome is the local auth layer for AI agents — it answers which agent, acting on behalf of whom, accessed what credential, and was that allowed.
authsome is single-tenant by design. There is no concept of an organisation, team, or multi-tenant namespace. A Principal represents a single human or agent identity, not a group. Features that require shared vaults, org-level permissions, or team credential distribution are outside the current architecture and would require introducing multi-tenancy as a first-class primitive before they could be built.
Each module has one job. identity/, auth/, vault/, and audit/ are leaf modules — they import nothing from this codebase and can be used and tested in isolation. server/ is the only composition root.
identity/ ←─┐
auth/ ←─┤
vault/ ←─┤ server/ ←── cli/ (via HTTP, not Python import)
audit/ ←─┘ ←── proxy/ (via HTTP, not Python import)
Think of this as the OpenID Connect layer. Handles key material, DIDs, and proof-of-possession tokens.
Owns:
- Ed25519 key pair generation and serialization (
local.py) did:keyDID derivation from public keys (local.py)IdentityMetadatamodel — client-side cached state for a local identityIdentityRegistrationmodel — the server's record of a registered handle/DID binding- PoP JWT creation and validation (
proof.py) ClaimStatus,PrincipalRecord,VaultRecord,IdentityClaimRecord,PrincipalVaultBindingRecord— shared domain models
Does not own:
- Filesystem-backed registries (those are server state, not identity primitives)
- Client config management (that is
cli/territory) - Principal/vault lifecycle decisions (that is
server/territory)
Imports nothing from this codebase. Used by: server/, cli/
Think of this as the OAuth 2.0 protocol library. Each flow takes provider config and credentials in, returns tokens out. No storage, no audit, no identity imports.
Owns:
- OAuth 2.0 flows: PKCE, Device Code, DCR+PKCE (
flows/) - API key collection flow (
flows/api_key.py) - Flow base class and token refresh logic
- Provider models:
ProviderDefinition,OAuthConfig,ApiKeyConfig, bundled provider JSON - Credential models:
ConnectionRecord,ProviderClientRecord,ProviderMetadataRecord,ProviderStateRecord AuthSession— transient flow session state
Does not own:
- Credential persistence (that is
vault/+server/territory) - Audit logging (that is
audit/+server/territory) - Proxy route catalog building
- Server registry reads
Imports nothing from this codebase. Used by: server/
server/ owns CredentialService (server/credential_service.py) — the stateful coordinator that wires auth/ flows with vault/ storage and audit/ logging. It is the only place where flows, storage, and audit are combined.
CredentialService is constructed per-request by the server with keyword-only (credentials, providers, identity, principal_id, principal_role, vault_id) and calls auth/ flows to execute protocols, vault/ (through the injected CredentialRepository) to persist results, and audit/ to record events.
Think of this as the secrets layer. Encrypts and decrypts credential blobs transparently.
Owns:
Vault— AES-256-GCM encrypted KV wrapper overAsyncKeyValueVaultCrypto— key management (local file, OS keyring)- Encrypted get/put/delete/list over named collections
Does not own:
- Server filesystem layout or path resolution
- Registry lookups
- Business logic about which vault belongs to which principal
Imports nothing from this codebase. Imported by: auth/, server/
Think of this as the audit instrumentation layer. Defines what happened; server/ decides where it goes.
Owns:
AuditEventdomain model — mandatory fields:identity,principal_id,provider,connection; optional:method,path,status,metadatalog()/alog()— emit anAuditEventas an OTelLogRecordviaget_logger_provider()- Translation from
AuditEvent→ OTelLogRecord
Does not own:
- Storage — no file I/O, no database
- Provider lifecycle (
setup()/clear()removed — owned byserver/) - Knowledge of where events are routed
Imports: opentelemetry-api only (no SDK, no storage). Imports nothing from this codebase. Imported by: server/, proxy/
Think of this as the daemon process. Wires identity + auth + vault + audit together. Owns all server-side persistence.
Owns:
server/store/repositories.py— all relational (SQLite/Postgres) registry implementations:IdentityRegistry(handle → DID)PrincipalRegistry(principal_id → email)VaultRegistry(vault_id → handle)IdentityClaimRegistry(identity → principal + ClaimStatus)PrincipalVaultBindingRegistry(principal → default vault)
server/ownership.py—OwnershipResolver(local and hosted variants),ResolvedOwnershipserver/identity_bootstrap.py— deployment-specific identity registration behaviorserver/dependencies.py— infrastructure wiring (paths, store, vault, config)server/app.py— FastAPI application factory and lifespanserver/routes/— HTTP API surfaceserver/schemas.py— API response schemasserver/audit_store.py—SQLiteLogExporter(OTelLogExporterimpl) +AuditStorequery interface;LoggerProviderlifecycle (setup at startup, shutdown at teardown)server/routes/audit.py—GET /audit/events(admins see all events; other principals see only their own PrincipalId)POST /audit/events— ingest endpoint for proxy-side external AuditEvents; server enrichesprincipal_idfrom PoP JWT
All filesystem interaction for server-owned state lives here. No other module writes to server-owned paths.
Imported by: nothing (top of the import graph)
A mitmproxy-based HTTPS proxy. Intercepts outgoing agent requests and injects auth headers.
Owns:
proxy/server.py— mitmproxy addon that intercepts requestsproxy/runner.py— background thread lifecycleproxy/router.py—RouteMatch/RouteResolutiontypesproxy/certs.py— CA certificate management
Does not own:
- Credential loading (asks the server)
- Route catalog construction (asks the server)
- Provider definitions
- Audit storage — ships External AuditEvents to server via
POST /audit/events(fire-and-forget); does not callaudit.log()directly
Imported by: cli/
Click-based CLI and HTTP client. Everything here is a client to the server HTTP API.
Owns:
cli/main.py— Click command treecli/client.py—RuntimeClient(async HTTP client for daemon requests, attaches PoP JWT)cli/client_config.py— client-owned config (active_identity,vault_id, proxy settings)cli/context.py—CliRuntimewiring containercli/daemon_control.py— start/stop the daemon process
Does not own:
- Server registry operations
- Direct vault or store access
- Identity key generation (delegates to
identity/, result stored by CLI viaidentity/local.py)
Imported by: nothing (entry point)
Identity: The cryptographic agent — Ed25519 key pair, did:key DID, and human-readable Handle. Created locally; registered with the daemon. Is not a credential namespace.
Handle: Human-readable name for an Identity (e.g., brisk-boldly-clearly-1234). Used as sub in PoP JWTs.
DID: did:key Ed25519 identifier derived from the Identity's public key. Used as iss in PoP JWTs.
PoP JWT: Short-lived (60 s) Proof-of-Possession token signed with the Identity's Ed25519 private key. Bound to htm, htu, body_sha256. Sent as Authorization: PoP <token>.
Principal: Non-cryptographic logical partition (human or team) that owns Vaults. Identified by an opaque PrincipalId (e.g., principal_abc123def456). Has no cryptographic key. Carries exactly one PrincipalRole.
Avoid: User, account, PrincipalHandle, profile
PrincipalId: Opaque stable identifier for a Principal. Never the email or handle — those can change; the PrincipalId cannot. Avoid: principal_handle, principal_name, username
PrincipalRole: Authorization tier for a Principal. Either admin or user. The first Principal created on a server is always admin; all subsequent Principals are user. Stored as a column on the Principal record — not in environment variables or a separate table.
Avoid: permission level, access level, user type
Vault: Named credential store owned by exactly one Principal. Identified by an opaque VaultId (e.g., vault_a1b2c3d4e5f6). All credential store keys are prefixed vault:<vault_id>:....
Avoid: credential store, token store, secret store, profile store
VaultId: Opaque stable identifier for a Vault. Used as the storage key segment. Stable across naming changes. Avoid: vault_name, vault_handle
VaultHandle: Human-readable name for a Vault (e.g., default). Used in UIs and CLI; the VaultId is authoritative in storage.
IdentityClaimRecord: Binding from an Identity (Handle) to a Principal (PrincipalId) with a ClaimStatus. Created when an authenticated Principal confirms the browser claim that authsome onboard initiates. Vault access is gated until the claim is accepted.
Avoid: Claim, IdentityRegistration (as claim), join request
ClaimStatus: Lifecycle state: pending → accepted | rejected.
There is a single flow for every deployment — no deployment mode (see ADR 0007). authsome onboard creates an Identity and registers it; the daemon returns registration_status = "claim_required" with a browser claim URL. The user opens the URL and registers (or logs in) with email + password: the first Principal created on a server becomes admin, all subsequent Principals are user. The authenticated Principal then confirms the claim, which binds the Identity to the Principal and creates the Principal's default Vault. Until the claim is accepted, all vault operations return 403. The CLI opens the claim URL automatically and polls for completion (and prints the URL to stderr for headless use).
- An Identity is a cryptographic agent. It does not own credentials directly.
- An Identity claims a Principal via an IdentityClaimRecord. Claim must be
acceptedfor vault access. - A Principal owns one or more Vaults via PrincipalVaultBindingRecords. The server resolves the default Vault before constructing
CredentialService. - A Vault contains zero or more Connections, each scoped to one Provider.
- Multiple Identities may share one Vault by claiming the same Principal.
- A ConnectionRecord belongs to exactly one Vault.
vault:<vault_id>:...is the key prefix. - ClientCredentials are server-scoped — one
ProviderClientRecordper Provider, shared across all Vaults.
CredentialService is a per-request credential lifecycle object constructed by the server:
CredentialService(
credentials=CredentialRepository(vault, identity=handle, principal_id=pid, vault_id=vid),
providers=provider_repository,
identity=handle,
principal_id=pid,
principal_role=role,
vault_id=vid,
)identity— agent Handle, used for audit logging onlyprincipal_id— resolved byOwnershipResolverfrom the PoP JWT subjectprincipal_role— the Principal's role (admin/user); gates provider-config and revoke operationsvault_id— resolved fromPrincipalVaultBindingRegistryby the server before constructing CredentialServicecredentials— aCredentialRepositoryover the encrypted KV store; CredentialService reads/writes only through this
CredentialService does not query registries, does not know about server filesystem paths, and does not build proxy route catalogs.
Every AuditEvent carries identity (the agent Handle) and principal_id (the PrincipalId). Both are required — every auditable action has an acting agent and an owning principal.
External AuditEvent: An event produced by the proxy layer — records an outbound HTTP call an agent made through the proxy to a third-party API (e.g., a call to api.github.com). Classified by provider and connection. Mandatory fields: identity, principal_id, provider, connection. Optional fields: HTTP method, path, response status.
Avoid: proxy event, API event, outbound event
Internal AuditEvent: An event produced by the server layer — records credential lifecycle operations (login, logout, token refresh, revocation) and auth flow steps. Avoid: server event, auth event, lifecycle event
Audit delivery: External AuditEvents are shipped from the proxy to the server via POST /audit/events (fire-and-forget, best-effort). The proxy does not write to a local audit file. The server is the single source of truth for all audit events. principal_id is resolved server-side from the PoP JWT on the ingest request — the proxy does not need to supply it.
- "PrincipalHandle" — retired. The Principal is now identified by an opaque
PrincipalId. Do not use PrincipalHandle in new code. - "VaultHandle" — the human-readable display name. Do not use VaultHandle as a storage key; use VaultId.
- "Claim" — use
IdentityClaimRecordfor the binding object; use "claim" (lowercase) only as a verb. - "identity=server" — retired. Previously a startup hack where the credential service was instantiated without a real identity;
CredentialServiceis now built per-request inroutes/_deps.py. - "credential" — use Connection for the full authenticated session; use access token / API key for the individual secret.