/ CHANGELOG.md
CHANGELOG.md
1 # Changelog 2 3 All notable changes to this project will be documented in this file. 4 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 8 ## [Unreleased] 9 10 ### Added (Unified Python SDK) 11 12 - **`auths-python`: Unified Python SDK package** — consolidated `auths-verifier-python` and `auths-agent-python` into a single `packages/auths-python` crate. Shared FFI runtime, module registration, and type definitions in `src/runtime.rs` and `src/types.rs`. 13 - **`auths-python`: `Auths` client class** — Stripe-style client with `repo_path` and `passphrase` constructor, resource services (`auths.identities`, `auths.devices`), and typed error hierarchy (`AuthsError`, `CryptoError`, `KeychainError`, `StorageError`, `NetworkError`, `IdentityError`, `VerificationError`). 14 - **`auths-python`: Identity lifecycle FFI** — `create_identity`, `provision_agent`, `link_device_to_identity`, `revoke_device_from_identity` Rust FFI functions with PyO3 bindings. `IdentityService` and `DeviceService` resource classes expose these as `auths.identities.create()`, `auths.identities.provision_agent()`, `auths.devices.link()`, `auths.devices.revoke()`. 15 - **`auths-python`: Keychain-backed signing** — `sign_as_identity` and `sign_action_as_identity` FFI functions resolve DID-or-alias to a keychain key and sign bytes or action envelopes. Exposed as `auths.sign_as()` and `auths.sign_action_as()`. 16 - **`auths-python`: Capability-scoped verification** — `verify_attestation_with_capability` and `verify_chain_with_capability` FFI functions. Python wrappers on the `Auths` client. 17 - **`auths-python`: Getting Started README** — quickstart example covering identity creation, device linking, signing, and verification. 18 19 ### Added (Python SDK Advanced Lifecycle) 20 21 - **`auths-python`: Time-pinned verification** — `verify_at_time` and `verify_at_time_with_capability` FFI functions accept an ISO 8601 timestamp string, enabling "was this valid at time T?" queries for audit and compliance. 22 - **`auths-python`: Witness chain verification** — `verify_chain_with_witnesses` FFI function accepts witness receipts, threshold, and public keys for k-of-n quorum verification. 23 - **`auths-python`: Key rotation** — `rotate_identity_ffi` FFI function and `auths.identities.rotate()` Python method. `IdentityRotationResult` dataclass with `controller_did`, `new_key_fingerprint`, `previous_key_fingerprint`, `sequence`. 24 - **`auths-python`: Device authorization extension** — `extend_device_authorization_ffi` FFI function and `auths.devices.extend()` Python method. `DeviceExtension` dataclass. 25 - **`auths-python`: Attestation query service** — `list_attestations`, `list_attestations_by_device`, `get_latest_attestation` FFI functions. `AttestationService` resource class with `auths.attestations.list()`, `auths.attestations.by_device()`, `auths.attestations.latest()`. 26 - **`auths-python`: Artifact attestation signing** — `sign_artifact` and `sign_artifact_bytes` FFI functions. `auths.sign_artifact()` and `auths.sign_artifact_bytes()` Python methods. `ArtifactSigningResult` dataclass. 27 - **`auths-python`: Git commit signing** — `sign_commit` FFI function and `auths.sign_commit()` Python method. `CommitSigningResult` dataclass. 28 - **`auths-python`: Policy engine** — `compile_policy` FFI function, `PyCompiledPolicy` and `PyEvalContext` classes. `PolicyBuilder` fluent API for constructing policies in Python. 29 - **`auths-python`: JWT validation** — `AuthsClaims` dataclass and JWKS client helper for validating Auths-issued JWTs. 30 31 ### Changed (Semantic Naming Consistency) 32 33 - **`auths-sdk`: Identity creation function renames** — `setup_developer()` → `create_developer_identity()`, `quick_setup()` → `create_developer_identity_quick()`, `setup_ci()` → `create_ci_identity()`, `setup_agent()` → `create_agent_identity()`, `build_agent_proposal()` → `build_agent_identity_proposal()`. Naming now conveys identity lifecycle semantics ("create" = inception) rather than generic "setup". 34 - **`auths-sdk`: Config type renames** — `DeveloperSetupConfig` → `CreateDeveloperIdentityConfig`, `CiSetupConfig` → `CreateCiIdentityConfig`, `AgentSetupConfig` → `CreateAgentIdentityConfig` (with corresponding builder renames). 35 - **`auths-sdk`: Result type renames** — `SetupResult` → `CreateIdentityResult`, `CiSetupResult` → `CreateCiIdentityResult`, `AgentSetupResult` → `CreateAgentIdentityResult`. 36 - **`auths-sdk`: Rotation type renames** — `RotationConfig` → `IdentityRotationConfig`, `RotationResult` → `IdentityRotationResult`. Clarifies these operate on KERI identities (`did:keri:`), not device keys. 37 - **`auths-python`: Agent operation split** — `provision_agent()` replaced with two distinct operations: `create_agent_identity()` (standalone `did:keri:` identity) and `delegate_agent()` (delegated `did:key:` under a parent). Python API: `auths.identities.create_agent()` and `auths.identities.delegate_agent()`. 38 - **`auths-python`: `Agent` dataclass replaced** — split into `AgentIdentity` (standalone, `did:keri:`) and `DelegatedAgent` (delegated, `did:key:`). 39 - **`auths-python`: `Identity.public_key` → `Identity.key_alias`** — field name now matches the Rust SDK terminology. 40 - **`auths-python`: `RotationResult` → `IdentityRotationResult`** — consistent with Rust SDK rename. 41 - **`auths-cli`: Updated call sites** — `init.rs` and `id/identity.rs` updated to use renamed SDK functions and types. 42 43 ## [0.0.1-rc.4] - 2026-03-04 44 45 ### Changed 46 47 - **`auths-core`: SSH agent abstracted behind `SshAgentPort` trait** — `Command::new("ssh-add")` subprocess calls extracted from `runtime.rs` into a new `ports::ssh_agent` module with `SshAgentPort` trait and `SshAgentError` typed error. `MacOsSshAgentAdapter` in `auths-cli` implements the trait. Enables testing and alternative SSH agent backends without subprocess shell-outs. 48 - **`auths-core`/`auths-sdk`: `#![deny(clippy::unwrap_used, clippy::expect_used)]` enforced** — all `unwrap()`/`expect()` calls in production code replaced with proper error propagation (`?`, `map_err`, `unwrap_or_else` for poisoned mutexes). `allow-unwrap-in-tests` and `allow-expect-in-tests` set in `.clippy.toml` to preserve test ergonomics. `witness/server.rs` `create_receipt` and `sign_payload` now return `Result` instead of panicking. 49 - **`auths-core`: `CommitSigningContext` dependency struct** — lightweight struct holding `key_storage`, `passphrase_provider`, and `agent_signing` trait objects, replacing scattered parameter passing in the signing pipeline. 50 51 ### Fixed 52 53 - **CI: `Cargo.lock` now committed** — the blanket `*.lock` gitignore pattern was preventing `Cargo.lock` from being tracked, causing `cargo audit` in CI to fail. Replaced with specific JS lock file patterns (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`). 54 - **CI: Platform-gated dead code in `passphrase_cache.rs`** — `PASSPHRASE_SERVICE`, `encode_secret`, and `decode_secret` now gated with `#[cfg(any(target_os = "macos", ...))]` to eliminate dead-code warnings on Linux CI builds. 55 56 ## [0.0.1-rc.2] to [0.0.1-rc.3] - 2026-03-04 57 58 > These releases were bumped while resolving circular dev-dependency cycles that blocked `cargo publish --workspace`. Key fixes: distributed the `auths-test-utils` monolith into per-crate `test-utils` features, removed the `auths-infra-git` upward dev-dependency from `auths-id`, and moved git-storage implementations from `auths-id` to `auths-storage`. See `docs/plans/dependency-architecture-refactor.md` for full details. 59 60 ## [0.0.1-rc.1] - 2026-03-04 61 62 ### Added 63 64 - **`auths-crypto`: `CryptoProvider` trait and native/WASM abstraction** — new async `CryptoProvider` trait abstracting Ed25519 operations (sign, verify, generate keypair, derive public key from seed). `RingCryptoProvider` implements the trait for native targets using `ring`. `WebCryptoProvider` stub scaffolded for `wasm32` targets. Feature-gated: `native` enables ring+tokio, `wasm` enables the WASM path. `SecureSeed` (zeroize-on-drop) is now the canonical key representation across all crates. 65 - **`auths-crypto`: `key_material` module** — canonical key parsing functions (`parse_ed25519_seed`, `parse_ed25519_key_material`, `build_ed25519_pkcs8_v2`) consolidated from scattered implementations across auths-core and auths-cli. 66 - **`auths-test-utils`: `MockCryptoProvider`** — deterministic mock for testing crypto operations without ring dependency. 67 68 - **Type Safety Audit** — comprehensive replacement of stringly-typed fields with semantic newtypes across the entire workspace: 69 - `auths-verifier`: Added `ResourceId` and `Role` newtypes; `Ed25519PublicKey` newtype replacing `Vec<u8>` (32-byte fixed array, `Copy`); `Ed25519Signature` newtype replacing `Vec<u8>` (64-byte fixed array) 70 - `auths-id`: Added `SealType` enum, `KeriSequence` newtype (wraps `u64`), `GitRef`/`BlobName` newtypes for storage layout; typed witness and receipt fields 71 - `auths-verifier`: `BridgeError` and `VerifyResult` now use structured reason enums instead of opaque strings 72 - `auths-core`: `ResolvedDid` converted from struct+`DidMethod` enum to a two-variant enum (`Key`/`Keri`) with accessor methods; `DidMethod` deleted. Same pattern applied to `ResolvedIdentity` in network ports 73 - `auths-sdk`: `SetupParams`, `DeviceRegistration`, `SigningConfig` fields use `IdentityDID`, `DeviceDID`, `Vec<Capability>` instead of `String`/`Vec<String>` 74 - `auths-id`: `StoredIdentityData.controller_did`, `AgentIdentityBundle.agent_did` → `IdentityDID`; `MemberView` fields → `Role`, `Vec<Capability>`, `IdentityDID`, `ResourceId`; `MemberFilter` → `HashSet<Role>`/`HashSet<Capability>`; `MemberInvalidReason` fields → `DeviceDID`/`IdentityDID`; `OrgMemberEntry.org` → `IdentityDID` 75 - `auths-core`: Added `Base64UrlEncoded` newtype for pairing types with `encode()`/`decode()`/`Deref<Target=str>`/`#[serde(transparent)]`/`JsonSchema`; `CreateSessionRequest.ephemeral_pubkey` → `Base64UrlEncoded`; `SubmitResponseRequest` fields → `Base64UrlEncoded`/`DeviceDID` 76 - All newtypes use `#[serde(transparent)]` — wire format unchanged, zero migration needed 77 78 ### Changed 79 80 - **`auths-verifier`: Refactored to use `CryptoProvider`** — all Ed25519 verification now routes through the `CryptoProvider` trait instead of calling `ring` directly. `ring` is feature-gated behind `native` (default). WASM builds use `--no-default-features --features wasm` to avoid pulling tokio/ring. 81 - **`auths-core`: Removed `ring` from production dependencies** — `ring` moved to dev-dependencies (test-only). All production crypto operations route through `auths-crypto::CryptoProvider` via a sync bridge (`provider_bridge.rs`). Key storage changed from raw PKCS#8 bytes to `SecureSeed`. 82 - **`auths-core`: `AgentCore` keys stored as `SecureSeed`** — `HashMap<Vec<u8>, SecureSeed>` replaces previous `Zeroizing<Vec<u8>>` storage. PKCS#8 bytes rebuilt on-demand via `build_ed25519_pkcs8_v2` when needed for macOS agent registration. 83 84 - **`auths-registry-server`:** Stripe integration and api endpoints. 85 - **`auths-registry-server`: Repo-per-tenant isolation** — introduced a `TenantResolver` port trait with two adapters: `SingleTenantResolver` (existing single-tenant deployments, unchanged behaviour) and `FilesystemTenantResolver` (multi-tenant SaaS, one Git repository per tenant under `{base}/tenants/{id}/`). `FilesystemTenantResolver` caches open `PackedRegistryBackend` instances in a bounded moka LRU cache (capacity configurable); errors are never cached. `invalidate(tenant_id)` and `invalidate_all()` allow callers to evict stale entries after suspension or deprovisioning. Path traversal is blocked via `canonicalize` of the tenants root followed by a `starts_with` check on the computed tenant path (symlink hardening). A `TenancyModeKind` enum (`Single` / `Multi`) is exposed by every resolver so middleware can gate multi-tenant routes without reaching into config. All existing routes are re-mounted under `/v1/t/:tenant_id` via a `TenantBackend` axum extractor; single-tenant mode routes continue to work as before. `POST /v1/admin/tenants` provisions a new tenant registry; the endpoint is idempotent — first provisioning returns `201 Created`, subsequent calls return `200 OK` with `"already_provisioned"`. The endpoint is protected by `RequireAdminToken` (strict `Bearer ` prefix parsing, constant-time token comparison, `501 Not Implemented` when no admin token is configured). ADR-001 (`docs/adr/ADR-001-repo-per-tenant-isolation.md`) documents the design decisions and rejected alternatives. Edge cases covered in `tests/cases/tenant_http.rs`: reserved tenant IDs → 400, unknown tenant → 404, single-tenant mode rejects `/v1/t/...` → 404, bad/missing admin token → 401/501, moka cache eviction and re-resolution correctness. 86 - **`auths-index`: `identities` and `org_members` SQLite tables** — new schema tables with WAL mode. `IndexedIdentity` (prefix, current keys, sequence, tip SAID) and `IndexedOrgMember` (org/member/issuer DIDs, rid, revoked/expires timestamps) types with `upsert_*`, `query_identity`, and `list_org_members_indexed` methods. 87 - **`auths-id`: `rebuild_identities_from_registry` and `rebuild_org_members_from_registry`** — free functions (feature: `indexed-storage`) that walk the packed Git registry and repopulate a fresh `AttestationIndex`, enabling full index reconstruction from Git without downtime. 88 - **`auths-oidc-bridge`: GitHub Actions OIDC cross-reference (`github-oidc` feature)** — new `github_oidc` module fetches GitHub's JWKS, validates RS256 tokens, and extracts actor/repository claims. `JwksClient` implements in-memory caching with configurable TTL, `tokio::sync::Mutex`-based request coalescing (thundering herd protection), circuit breaker (5 failures → 60s cooldown), exponential backoff with jitter, and stale-cache fallback on fetch failure. 89 - **`auths-oidc-bridge`: Actor cross-reference module** — `cross_reference.rs` verifies the GitHub `actor` claim matches the expected KERI identity holder before JWT issuance, creating a two-factor proof (KERI chain + CI/CD environment). 90 - **`auths-oidc-bridge`: AWS STS integration tests** — `tests/aws_integration.rs` with real `AssumeRoleWithWebIdentity` test (requires `AWS_ROLE_ARN` + `AUTHS_BRIDGE_URL` env vars) and LocalStack fallback test. Both `#[ignore]` by default for CI safety. 91 - **`auths-oidc-bridge`: `claims_supported` in OpenID Configuration** — `/.well-known/openid-configuration` now includes the full list of supported JWT claims (`iss`, `sub`, `aud`, `exp`, `iat`, `jti`, `keri_prefix`, `capabilities`, `witness_quorum`, `github_actor`, `github_repository`). 92 - **`docs/oidc-enterprise-guide.md`** — enterprise security documentation covering trust boundaries, STRIDE threat model (8 attack vectors), key rotation procedure (90-day cadence), Break Glass incident response playbook (<15 min RTO), AWS IAM integration guide, Terraform and CloudFormation IaC templates, and GitHub Actions workflow examples. 93 94 ### Changed 95 96 - **`auths-registry-server`: Migrated pairing and tenant metadata stores from SQLite to PostgreSQL** — `SqlitePairingStore` and `SqliteTenantMetadataStore` and their `rusqlite` dependency have been removed. Replaced by `PostgresPairingStore` and `PostgresTenantMetadataStore` backed by sqlx 0.8 with compile-time query verification. Both `PairingStore` and `TenantMetadataStore` traits are now fully async via `async-trait`. Schema managed via sqlx migrations (`migrations/001_pairing_sessions.sql`, `migrations/002_tenant_metadata.sql`). A single shared `PgPool` (max 10 connections) is injected into both stores at startup; set `REGISTRY_POSTGRES_URL` to a PostgreSQL connection string. sqlx offline query cache (`.sqlx/`) committed for builds without a live database (`SQLX_OFFLINE=true`). 97 98 - **`auths-cli`: `setup` renamed to `init`; `status` promoted to primary surface** — `auths setup` is now `auths init` (same profiles: `developer`, `ci`, `agent`). `auths status` is now a top-level command alongside `init`, `sign`, and `verify` instead of living under `auths advanced`. Witness flags (`--witness`, `--witness-threshold`, `--witness-policy`) are removed from the onboarding path; use `auths advanced witness` for witness management. 99 - **`auths-cli`: `auths advanced` nesting removed** — all subcommands are now top-level: `auths device`, `auths id`, `auths key`, `auths policy`, `auths emergency`, etc. The `auths advanced <cmd>` prefix no longer exists. `auths --help` still shows only the four primary commands; the rest are discoverable via `auths <cmd> --help`. 100 101 - **`auths-oidc-bridge`: Exchange handler refactored for composable validation** — `token_exchange` handler now performs GitHub cross-reference as a decoupled pre-step (SRP) before calling `OidcIssuer::exchange()`. Structured tracing events emitted for every exchange: `auths.exchange.github_cross_reference.success`, `.failure`, and `keri_only`. `exchange()` accepts an optional `CrossReferenceResult` to populate `github_actor` and `github_repository` claims in the minted JWT. 102 - **`auths-oidc-bridge`: `ExchangeRequest` expanded** — accepts optional `github_oidc_token` and `github_actor` fields (feature-gated behind `github-oidc`). 103 - **`auths-oidc-bridge`: `BridgeConfig` expanded** — new fields `github_oidc_issuer`, `github_expected_audience`, and `github_jwks_cache_ttl_secs` for GitHub OIDC configuration. 104 - **`auths-index`: Renamed `rebuild_from_git` → `rebuild_attestations_from_git`** — updated all callers in `auths-id` and `auths-cli`. 105 - **`auths-id`: `PackedRegistryBackend` now wires SQLite index writes on every mutation** — `store_attestation`, `store_org_member`, and `append_event` perform best-effort write-through to `Arc<Mutex<AttestationIndex>>` (feature-gated: `indexed-storage`). Index is best-effort; Git remains the source of truth. 106 - **`auths-id`: `list_org_members_fast` added to `RegistryBackend` trait** — default delegates to `list_org_members`; `PackedRegistryBackend` override uses the SQLite index for O(1) lookups, falling back to Git when the index is empty or a capability filter requires full attestation data. 107 - **`auths-id`: Replaced redb cache with Redis-backed tiered storage** — removed the embedded `redb` read-through cache (`RegistryCache`) from `PackedRegistryBackend`. Identity resolution now uses a two-tier architecture: Tier 0 (Redis) for sub-millisecond cached lookups and Tier 1 (Git) as the persistent cryptographic ledger. New `auths-cache` crate implements `TierZeroCache` (Redis via `bb8` pool) and `TierOneArchive` (Git via `spawn_blocking`), orchestrated by a `TieredResolver` using Cache-Aside reads and Write-Through writes with a background `ArchivalWorker`. Failed Git writes route to a Redis Stream dead letter queue to protect KERI hash chain integrity. Redis is optional — when `AUTHS_REDIS_URL` is not set, the system falls back to direct Git reads. 108 - **`auths-sdk`/`auths-cli`: Extracted business logic from CLI to SDK** — signing pipeline (`sign_artifact`, `validate_freeze_state`), device pairing orchestration (`validate_short_code`, `verify_device_did`, `create_pairing_attestation`), git audit engine (`AuditWorkflow` + `GitLogProvider` port), artifact digest abstraction (`ArtifactSource` port + `LocalFileArtifact` adapter), system diagnostics (`DiagnosticProvider` port), SSH crypto ops, and capability parsing all moved behind trait-based ports in `auths-sdk`. CLI is now a thin presentation layer delegating to SDK workflows. 109 - **`auths-core`: `EnvironmentConfig` / `KeychainConfig`** — new structs that collect all environment-variable reads (`AUTHS_KEYCHAIN_BACKEND`, `AUTHS_HOME`, `AUTHS_KEYCHAIN_FILE`, `AUTHS_PASSPHRASE`) at the process boundary. `get_platform_keychain_with_config()` and `auths_home_with_config()` replace zero-argument functions that read env vars internally. `EnvironmentConfig::from_env()` is the single permitted I/O site; all downstream logic receives the config by value. Eliminates hidden I/O coupling and makes the keychain backend fully injectable for tests. 110 - **`auths-core`: `ClockProvider` trait and `SystemClock` / `MockClock` implementations** — abstracts `Utc::now()` calls across `auths-sdk` (setup, device, pairing, signing, rotation), `auths-id`, and `auths-verifier`. `MockClock` in `auths-test-utils` enables deterministic time in tests without `unsafe` env-var mutation. 111 - **Clock injection completed (Epic 2)** — zero `Utc::now()` calls remain in `auths-core/src/` or `auths-id/src/` outside `#[cfg(test)]`. All time-sensitive functions (`create_signed_attestation`, `verify_with_resolver`, `extend_expiration`, `try_incremental_validation`, `provision_agent_identity`, `resolve_trust`, `PairingToken::generate*`, `WitnessStorage::store_receipt`, `GitKel::get_state`, `RegistryMetadata::new`) accept `now: DateTime<Utc>` as an explicit parameter. `auths-sdk` passes `clock.now()`; CLI passes `Utc::now()` at the presentation boundary. 112 - **`auths-core`: `EventSink` telemetry port** — new `EventSink` trait decouples structured event emission from stdout. `StdoutSink` (async MPSC worker, non-blocking `emit()`, blocking `flush()`) and `MemorySink` (in-process test capture) are the two provided implementations. `init_telemetry()` / `init_telemetry_with_sink()` set the global sink once at startup. `DROPPED_AUDIT_EVENTS` counter surfaces backpressure in the SIEM pipeline. 113 - **`auths-sdk`: `GitConfigProvider` port trait** — `set(key, value)` abstraction removes `std::process::Command::new("git")` and `which::which` from `auths-sdk`. `SystemGitConfigProvider` in `auths-cli` implements the trait via the system `git` binary. `DeveloperSetupConfig` gains an optional `sign_binary_path` field; the CLI resolves the path via `which::which("auths-sign")` and passes it at the presentation boundary. 114 - **`auths-sdk`: `SdkStorageError` typed enum** — replaces `anyhow::Error` in `SetupError::StorageError` and `DeviceError::StorageError`. `RegistrationError::NetworkError` now wraps `auths_core::ports::network::NetworkError` (typed). `RegistrationError::LocalDataError` carries a `String`. `map_storage_err()` and `map_device_storage_err()` helper functions removed; callers use inline `.map_err(|e| ...StorageError(SdkStorageError::OperationFailed(e.to_string())))`. `anyhow` removed from `auths-sdk/Cargo.toml`. 115 116 --- 117 118 > Note: notes on release `0.0.1-rc.11` and prior come from an earlier repository that (a) lived on the my personal account and (b) included crates that have since been stripped out. I've decided to leave them intact for documenting the development. 119 120 ## [0.0.1-rc.11] - 2026-02-18 121 122 ### Changed 123 124 - **`auths-auth-server`: Migrated session store from SQLite to PostgreSQL** — `SqliteSessionStore` and its `rusqlite` dependency have been removed (hard cut, no backward compatibility). Replaced by `PostgresSessionStore` (`crates/auths-auth-server/src/adapters/postgres_session_store.rs`) backed by sqlx 0.8 with compile-time query verification. Schema managed via sqlx migrations (`migrations/001_init.sql`). The `SessionStore` trait is now fully async via `async-trait`, making it dyn-compatible as `Box<dyn SessionStore>`. Set `DATABASE_URL` to a PostgreSQL connection string to enable; falls back to `InMemorySessionStore` when unset. sqlx offline query cache (`.sqlx/`) committed for builds without a live database (`SQLX_OFFLINE=true`). 125 126 ### Security 127 128 - **`auths-auth-server`: Atomic CAS nonce invalidation in `verify_auth`** — `SessionStore::update_status` now takes `from` + `to` parameters and returns `Ok(bool)`. `SqliteSessionStore` uses `UPDATE … WHERE status = ?` (evaluated atomically by SQLite); `InMemorySessionStore` uses `std::mem::discriminant` comparison inside its write lock. Concurrent requests racing to verify the same session now get exactly one `200 OK`; the rest receive `409 CONFLICT` (`SessionAlreadyVerified`). Also adds `PRAGMA synchronous=NORMAL` alongside WAL mode. 129 130 ### Added 131 132 - **`auths setup --profile developer`: Step 6/6 — signing pipeline verification** — after git config, runs a test commit in a throwaway temp repo to confirm `auths-sign` is wired end-to-end. Reports `Skipped` gracefully if `auths-sign` is not yet on PATH. 133 - **`UnifiedPassphraseProvider` (`auths-core`):** New passphrase provider that prompts exactly once regardless of how many distinct prompt messages are presented. Used in `auths device link` so the entire link operation requires only one passphrase entry instead of two. 134 - **`auths doctor`: exact runnable fix commands** — all suggestion strings now start with `Run:` and are copy-pasteable. `check_git_signing_config` expanded from checking only `gpg.format` to all 5 required signing configs (`gpg.format`,`commit.gpgsign`, `tag.gpgsign`, `user.signingkey`, `gpg.ssh.program`). 135 - **`auths-cli` README: CI setup section** — copy-pasteable GitHub Actions workflows for signed commits (`auths setup --profile ci`) and commit signature verification (`auths verify-commit HEAD`). 136 - **`LocalGitResolver` for air-gapped identity resolution (`auths-auth-server`):** New adapter reads KERI key state directly from a local git registry (`refs/auths/registry`) via `PackedRegistryBackend`, requiring zero network access. Eliminates the single point of failure from `RegistryIdentityResolver` requiring a live HTTP endpoint. 137 - **`ResolverMode` config enum (`auths-auth-server`):** `AuthServerConfig.registry_url: String` replaced with `resolver_mode: ResolverMode` (`RegistryHttp { url }` | `LocalGit { repo_path }`). Builder methods: `with_registry_url()` and `with_local_git_resolver()`. 138 - **`AUTH_SERVER_LOCAL_GIT_REPO` env var (`auths-auth-server`):** When set, the server uses `LocalGitResolver` backed by the given path instead of the HTTP registry. Overrides `AUTH_SERVER_REGISTRY_URL` if both are set. 139 - **Air-gapped integration tests (`auths-auth-server`):** `tests/air_gapped.rs` covers full auth flow, unknown-DID rejection, and wrong-key rejection using a temp-dir git repo — no registry server needed. 140 141 - **Bundle TTL:** `IdentityBundle` now requires `bundle_timestamp` and `max_valid_for_secs` fields. Bundles fail verification once stale, preventing revoked keys from passing CI indefinitely. 142 - `auths id export-bundle` gains a required `--max-age-secs` flag to set bundle TTL at export time. 143 - New `AttestationError::BundleExpired` variant (`AUTHS_BUNDLE_EXPIRED`) with a re-export suggestion. 144 - GitHub Action enforces bundle age before invoking CLI verification. 145 146 ### Fixed 147 148 - **PKCS#8 magic offset removed from `extract_seed_from_pkcs8`** — replaced the brittle `key_bytes[16..48]` fallback with a direct `PrivateKeyInfo::from_der` call and an exhaustive slice-pattern match on inner key bytes. Handles both the RFC 8410 DER OCTET STRING wrapping (`04 20 <seed>`) that ring produces and bare 32-byte seeds. Fails loudly with a descriptive error on unexpected formats. 149 - **`auths verify-commit` always failing with empty error** — `ssh-keygen -Y verify -I "*"` does not treat `*` as a wildcard; it searches for a literal `*` entry in the allowed_signers file. Fixed by running `find-principals` first to resolve the actual signer identity, then passing it to `verify`. Also fixed error capture: `ssh-keygen` writes `"Could not verify signature."` to stdout, not stderr. 150 151 > **TODO:** Cut a new release (`v0.0.1-rc.12`) so CI downloads the fixed binary and commit verification passes in GitHub Actions. 152 153 ## [0.0.1-rc.10] - 2026-02-18 154 155 ### Added 156 157 - Support for homebrew install via `brew install auths-dev/auths-cli/auths` 158 159 ## [0.0.1-rc.8] to [0.0.1-rc.9] - 2026-02-17 160 161 > Note: these releases were a series of trial and error to get homebrew install working, which required several quick releases with nominal changes to configurations. 162 163 ## [0.0.1-rc.7] - 2026-02-17 164 165 > Note: these releases were a series of trial and error to get homebrew install working, which required several quick releases with nominal changes to configurations. 166 167 ### Added 168 169 - Improved CLI error messages with actionable suggestions. 170 - New `auths doctor` command to diagnose setup issues. 171 - **ci:** Release workflow builds a native `auths` binary on the host and signs release artifacts using a device-only key from the encrypted CI keychain (`AUTHS_CI_KEYCHAIN`), with no identity/root key present in CI. `.auths.json` attestation files are included in the release upload. 172 - **xtask:** New `xtask` workspace crate (`publish = false`) replaces the 187-line shell `ci-setup` recipe in the justfile. Uses the idiomatic Rust xtask pattern for project-internal CI tooling. Invokable via `cargo xt ci-setup` or `just ci-setup`. 173 - **xtask:** Native base64 encoding (`base64` crate) eliminates the macOS `base64` 76-char line-wrapping bug that caused `base64: invalid input` on Linux CI runners. 174 - **xtask:** `TempDir`-based cleanup replaces manual `rm -f` — seed and keychain temp files are cleaned up automatically even on error. 175 - **xtask:** Passphrase prompting via `rpassword` — passphrase never appears in process arguments (unlike shell `read -s` piped through variable expansion). 176 - **xtask:** `GH_TOKEN`/`GITHUB_TOKEN` env vars are cleared via `env_remove()` on every `gh` invocation, fixing the silent auth failure when a stale token overrides the keyring account. 177 - **xtask:** Native tar/gzip archiving with `tar`+`flate2`+`walkdir` crates — excludes `*.sock` files, no `rsync` dependency. 178 - **docs:** adds docs on GitHub action and tarball signing workflows. 179 - **docs:** adds commit signing troubleshooting guide covering agent, keychain, and git config issues. 180 - **auths-cli:** New `auths key copy-backend` subcommand — copies a key from the current keychain backend to a file-based keychain without exposing raw key material. Accepts `--dst-backend`, `--dst-file`, and `--dst-passphrase` (or `AUTHS_PASSPHRASE` env var). Replaces the fragile PEM-export → seed-parse → re-import pipeline previously used by `just ci-setup`. 181 182 ### Fixed 183 184 - **auths-core:** Implement `add_identity`, `remove_identity`, and `remove_all_identities` on the SSH agent session. Previously the agent rejected all key-loading requests with `UnsupportedCommand`, making `auths agent unlock` fail and breaking Git commit signing via the agent. 185 - **auths-cli:** `auths-sign` Tier 2 keychain failure no longer causes an early return. When keychain or passphrase prompts fail (common in subprocess contexts), the error message now includes actionable instructions to start the agent and unlock the key. 186 - **ci:** Fix WASM build check — qualify feature with package name (`--features auths_verifier/wasm`) as required by Rust 1.93 / resolver 3 when running from a workspace root. 187 - **ci:** Fix Windows build — gate `agent::client` module and all consumers behind `#[cfg(unix)]` since `std::os::unix::net::UnixStream` does not exist on Windows. 188 - **ci:** Fix `cargo test` invocation — split `--doc` into a separate step because cargo cannot mix `--doc` with `--lib`/`--bins` target selectors. 189 - **auths-core:** `AUTHS_KEYCHAIN_FILE` env var is now implemented — the file keychain backend uses the specified path instead of the default `~/.auths/keys.enc`. 190 - **auths-core:** `AUTHS_PASSPHRASE` env var is now wired to the file keychain password, enabling fully headless CI artifact signing without interactive prompts. 191 - **auths-core:** `EncryptedFileStorage::get_password()` now falls back to the `AUTHS_PASSPHRASE` environment variable when no password has been set via `set_password()`. This fixes a "Missing Passphrase" error that occurred when a new `EncryptedFileStorage` instance was created after the initial one had the password wired in. 192 - **release.yml:** Homebrew step secrets error secrets is only valid inside `${{ }}` interpolation, not as a bare named value in `if:` expressions. Solution: expose a boolean `HAS_HOMEBREW_TOKEN` env var at the job level (where `${{ }}` is valid), then gate the step on `env.HAS_HOMEBREW_TOKEN == 'true'`. 193 194 ### Changed 195 196 - **auths-verifier:** `identity_signature` on `Attestation` is now optional — serialized with `skip_serializing_if = "Vec::is_empty"` and deserialized with `default`. Dual-signed attestations are unchanged; device-only attestations omit the field (backward-compatible). 197 - **auths-cli:** `--identity-key-alias` on `auths artifact sign` is now optional. Omitting it produces a device-only attestation; the identity key never needs to enter CI. 198 - **auths-id:** `create_signed_attestation`, `resign_attestation`, and `extend_expiration` accept `identity_alias: Option<&str>`. Passing `None` skips identity signing. 199 - **auths-core:** `EncryptedFileStorage::set_password()` now takes `Zeroizing<String>` instead of `String`, enforcing secure handling of the passphrase from the point of construction. All callers updated. 200 - **justfile:** `ci-setup` recipe now delegates to `cargo xt ci-setup` (was 187 lines of shell). 201 - **docs:** Release process guide leads with justfile (`just release`, `just ci-setup`) and moves manual steps to a secondary section. 202 203 ## [0.0.1-rc.6] - 2026-02-16 204 205 ### Fixed 206 207 - **auths-cli:** `auths emergency report` now loads real identity and device data from storage instead of returning hardcoded mock DIDs. 208 - **auths-cli:** `auths agent lock` / `unlock` wired end-to-end — lock removes keys from agent memory, unlock reloads from keychain. 209 - **auths-cli:** `--schema` flag on `auths device link` now validates the payload against the JSON schema instead of being silently ignored. 210 - **auths-cli:** `auths migrate status` distinguishes GPG vs SSH signatures using `%GS` signer format instead of assuming GPG. 211 - **auths-id:** `extend_expiration()` and `resign_attestation()` rewritten to use `SecureSigner` instead of raw seeds. 212 - **auths-id:** Revocation attestations now carry the real device public key (looked up from existing attestations) instead of a zeroed placeholder. 213 - **auths-nostr:** `run_with_permission_callback()` now checks the callback before signing requests instead of ignoring it. 214 - **auths-core:** Invalid custom policy actions return an error instead of silently falling back to `sign_commit`. 215 - **auths-core:** Socket timeout errors in the agent client are now propagated instead of silently discarded. 216 - **auths-core:** Replaced commented-out HTTP server code with a clean placeholder module. 217 218 ### Security 219 220 - **auths-id:** Auto-install pre-receive hook during `auths init` — rejects non-fast-forward pushes and ref deletions for `refs/keri/`, `refs/auths/`, and `refs/did/keri/`, preventing Git-level KEL rewrites that bypass the Rust registry. 221 - **auths-id:** Add replay attack prevention to `store_attestation()` — timestamp monotonicity and `rid`-based duplicate detection. 222 - **auths-verifier:** Introduce `VerifiedAttestation` newtype; attestations must pass verification before storage. 223 - **auths-verifier:** Add `MAX_ATTESTATION_JSON_SIZE` (64 KiB) and `MAX_JSON_BATCH_SIZE` (1 MiB) limits — all JSON deserialization points across Rust, FFI, WASM, Python, Swift, and Go reject oversized inputs before parsing. 224 225 ### Added 226 227 - **auths-cli:** `auths artifact sign` and `auths artifact verify` commands for signing and verifying arbitrary files (tarballs, binaries). Uses dual-signed attestations with `sign_release` capability, SHA-256 content addressing, and optional witness quorum verification. Hexagonal `ArtifactSource` trait enables future Docker/NPM/Cargo adapters. 228 - **auths-id:** End-to-end witness integration — `WitnessConfig` and `WitnessPolicy` (Enforce/Warn/Skip) structs for identity-level witness configuration; `create_keri_identity()`, `rotate_keys()`, and `abandon_identity()` now populate KERI event `bt`/`b` fields from config; witness receipts are automatically collected and stored after inception and rotation events when the `witness-client` feature is enabled. 229 - **auths-id:** `witness_integration` module (feature-gated behind `witness-client`) — `collect_and_store_receipts()` wires `HttpWitnessClient`, `ReceiptCollector`, and `GitReceiptStorage` into the identity lifecycle with policy-based degradation. 230 - **auths-cli:** `auths init` gains `--witness`, `--witness-threshold`, and `--witness-policy` flags for configuring witnesses at identity creation time. 231 - **auths-cli:** `auths witness add`, `auths witness remove`, and `auths witness list` subcommands for managing witness URLs in identity metadata post-init. 232 - **auths-cli:** `auths id rotate` now loads witness config from identity metadata and threads it through to KERI rotation, automatically collecting receipts for rotation events. 233 - **auths-auth-server:** SQLite-backed `SqliteSessionStore` as default session store — sessions persist across restarts with WAL mode for read concurrency, background cleanup task evicts expired sessions every 60s. `SessionStore` trait expanded with `delete()`, `list_active()`, and `cleanup_expired()` methods. 234 - **auths-registry-server:** Extract `PairingStore` trait and add SQLite-backed `SqlitePairingStore` as default pairing session store — sessions persist across restarts, background cleanup task evicts expired sessions every 60s. WebSocket notifiers remain ephemeral (in-memory). 235 - **auths-oidc-bridge:** New crate that exchanges KERI attestation chains for short-lived RS256 JWTs consumable by cloud providers (AWS STS, GCP, Azure AD). 236 - **auths-core (witness):** Optional TLS via `tls` feature flag (`axum-server` + `rustls`). 237 - **auths-id:** `freeze` module — time-bounded identity freeze with `auths emergency freeze` / `unfreeze`. 238 - **auths-cli:** `auths-sign` refuses to sign while identity is frozen. 239 - **auths-verifier:** FFI exports expanded to 4 functions: added `ffi_verify_chain_json()` and `ffi_verify_device_authorization_json()`. 240 - **auths-registry-server:** OpenAPI 3.0 spec served at `/api-docs/openapi.json`. 241 - Dockerfiles for `auths-registry-server` and `auths-auth-server`, plus `docker-compose.yml`. 242 - GitHub Actions release workflow for cross-platform binaries (linux, macOS, Windows). 243 - CI: code coverage via `cargo-llvm-cov` + Codecov, mobile FFI tests, Go bindings tests. 244 245 ### Changed 246 247 - **auths-cli:** `auths verify-commit --identity-bundle` now verifies the full attestation chain (revocation, expiry, signature integrity) instead of only extracting the public key for SSH verification. `--witness-receipts`, `--witness-threshold`, and `--witness-keys` flags are now functional, enabling quorum-based witness verification for commit signatures. Refactored internals to eliminate duplicated range/single dispatch and unified JSON/text output with new `ssh_valid`, `chain_valid`, `chain_report`, `witness_quorum`, and `warnings` fields. Extracted shared `parse_witness_keys()` helper into `verify_helpers` module. 248 - **auths-verifier:** Replace `revoked: bool` with `revoked_at: Option<DateTime<Utc>>` on `Attestation` — enables time-aware revocation checks ("was this attestation valid at time T?") for audit and compliance. `None` = active, `Some(t)` = revoked at time `t`. Adds `is_revoked()` helper. Propagated across all crates, SQLite index, Go bindings, CLI, and registry server. 249 - **auths-verifier:** `verify_with_keys_at()` now performs time-aware revocation — attestations revoked after the reference time are still considered valid at that point. 250 - **auths-verifier:** `is_device_listed()` now requires `&[VerifiedAttestation]` instead of `&[Attestation]` — enforces signature verification at the type level. 251 - **auths-verifier:** Remove deprecated `is_device_authorized()` from Rust, Go, Python, and Swift bindings. 252 - **auths-registry-server:** Wire org management endpoints (`add_member`, `revoke_member`, `update_capabilities`) to real storage with Ed25519 signature verification and admin authorization. 253 - **auths-verifier:** Add witness receipt verification with k-of-n quorum support, FFI/WASM bindings, and CLI `--witness-receipts` flags. 254 255 ### Fixed 256 257 - **GitHub Action:** `getAuthsDownloadUrl()` now returns correct release asset URLs. 258 259 ## [0.0.1-rc.5] - 2026-02-14 260 261 ### Security 262 263 - **auths-policy:** Replace non-cryptographic `DefaultHasher` (SipHash) with `blake3` for policy 264 source hashing. The previous implementation used `std::collections::hash_map::DefaultHasher` 265 with a comment acknowledging it was a placeholder. Policy hashes are now computed with 266 `blake3::hash()`, a cryptographic hash function. 267 268 - **auths-core (witness):** Fix receipt SAID computation to use proper Blake3 hash instead of a 269 truncated string slice (`format!("E{}", &event_said[1..].chars().take(20)...)`). Receipts now 270 compute SAID via `compute_said()` over the canonical signing payload and sign that payload 271 rather than a `format!("{}:{}:{}", ...)` string. 272 273 - **auths-core (witness):** Add event verification to `submit_event` handler. The witness server 274 previously issued receipts for any submitted event without validation. Now verifies: 275 - SAID integrity (zeroes `d` field, recomputes Blake3 hash, compares) 276 - Structural requirements (required fields per event type) 277 - Signature format (`x` field must be valid hex encoding 64 bytes) 278 - Inception self-signature (Ed25519 verification of `k[0]` over the event) 279 280 - **auths-registry-server:** Add real Ed25519 signature verification for authenticated API 281 requests. The `VerifiedSignature` extractor (renamed to `SignatureHeaders`) previously extracted 282 headers but had a TODO where verification should occur. Added 283 `verify_request_signature(headers, body)` which decodes hex public key and signature, validates 284 timestamp within a 300-second window, constructs the signing payload, and verifies via 285 `ring::signature::UnparsedPublicKey`. 286 287 - **auths-cli:** Remove fake success messages from emergency commands. `revoke-device` now calls 288 `create_signed_revocation()` and exports via `AttestationSink`. `rotate-now` now calls 289 `rotate_keri_identity()`. `freeze` now returns an honest error stating the feature is not yet 290 implemented, instead of printing a fake "Identity frozen" success message. 291 292 ### Changed 293 294 - **auths-registry-server:** `ring` moved from dev-dependencies to dependencies. 295 - **auths-registry-server:** `VerifiedSignature` renamed to `SignatureHeaders`; a type alias 296 preserves backward compatibility. 297 - **auths-core (witness):** `submit_event` handler now accepts `Json<serde_json::Value>` instead 298 of `Json<SubmitEventRequest>` to enable full-event validation. 299 - **auths-cli:** `emergency revoke-device` now requires `--identity-key-alias` (or interactive 300 prompt). `emergency rotate-now` now requires `--current-alias` and `--next-alias` (or 301 interactive prompts). 302 303 ### Added 304 305 - **auths-policy:** `blake3` dependency (`1.5`). 306 - **auths-registry-server:** `verify_request_signature()` public function for verifying Ed25519 307 request signatures with timestamp replay protection. 308 - **auths-core (witness):** `verify_event_said()`, `validate_event_structure()`, 309 `validate_signature_format()`, and `verify_inception_self_signature()` functions. 310 311 ## 0.0.1-rc.1 to 0.0.1-rc.4 [YANKED] 312 313 These pre-release versions were yanked from crates.io.