## Summary
Add the required parameter-name comment for the None authentication-context argument in the macOS device-key lookup path. CI macOS argument-comment lint enforces these comments for positional literal-like arguments.
## Validation
- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- bazel build --config=argument-comment-lint //codex-rs/device-key:device-key-unit-tests-bin
- git diff --check
Note: a full local just argument-comment-lint run reached the fixed device-key target, then failed later in an unrelated webrtc-sys build script with a stale sandbox path outside this PR changes.
## Summary
Load LocalAuthentication.framework at runtime before constructing LAContext instead of linking the framework into every macOS target. The previous direct framework link made unrelated macOS Bazel targets link against an SDK-backed framework path, which caused the macOS CI jobs to fail during linking.
The signing flow still uses LAContext for authenticated private-key operations; this only changes how the framework becomes available to the Objective-C runtime.
## Validation
- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- git diff --check
- bazel build //codex-rs/device-key:device-key
Require macOS-created device keys to prove user presence before private-key use by adding the UserPresence access-control flag at key creation time. This keeps the provider aligned with the device-ownership goal: a valid signature should require both the device-local private key and a successful local biometric or password authentication.
Thread a process-local LAContext into signing lookups and reuse it across sign operations. Security.framework still owns the authentication policy and validity window, while the reusable context lets macOS reuse a recent successful authentication when policy permits instead of prompting on every connection.
Public-key lookup and binding reads continue to avoid the authentication context because they do not use the private key.
- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- git diff --check
The device-key crate needs a platform provider that can keep private keys non-extractable on macOS while preserving the crate-level protection policy and structured signing boundary.
macOS has two useful local protection classes for this API: hardware-backed Secure Enclave keys when available, and OS-protected non-extractable keys as an explicit fallback. Reporting which class was selected keeps that distinction visible to callers.
The Security.framework access-control flags used here provide device-local non-exportability and continuity while the user session is unlocked. They do not require UserPresence or Biometry for each signature, so the key itself is not treated as a complete same-controller compromise boundary; the API still relies on local-transport restriction, app-server authorization, and structured payload validation.
- Added the macOS DeviceKeyProvider implementation backed by Security.framework.
- Created and loaded P-256 private keys by stable device-key ID.
- Preferred Secure Enclave keys by default and allowed OS-protected non-extractable fallback only when requested by policy.
- Returned SPKI DER public keys and DER-encoded ECDSA signatures through the existing crate API.
- Documented the macOS provider threat-model boundary around this-device-only keys without UserPresence or Biometry flags.
- Added macOS-only Security.framework and CoreFoundation dependencies.
- just fmt
- cargo test -p codex-device-key
- just fix -p codex-device-key
- git diff --check
- just bazel-lock-update
- just bazel-lock-check
## Why
Device-key providers should only own platform key material. The
account/client binding used to authorize a signing payload is app-server
state, and keeping that state in provider-specific metadata makes the
same check harder to audit and harder to share across platform
implementations.
Persisting the binding in the shared state database gives the device-key
crate a platform-neutral source of truth before it asks a provider to
sign. It also lets app-server move potentially blocking key operations
off the main message processor path, which matters once providers may
wait for OS authentication prompts.
## What changed
- Add a `device_key_bindings` state migration plus `StateRuntime`
helpers keyed by `key_id`.
- Add an async `DeviceKeyBindingStore` abstraction to `codex-device-key`
and use it from `DeviceKeyStore::create` and `DeviceKeyStore::sign`.
- Keep provider calls behind async store methods and run the synchronous
provider work through `spawn_blocking`.
- Wire app-server device-key RPC handling to the SQLite-backed binding
store and spawn response/error delivery tasks for device-key requests.
- Run the turn-start tracing test on the existing larger current-thread
test harness after the larger async surface made the default test stack
too small locally.
## Validation
- `cargo test -p codex-device-key`
- `cargo test -p codex-state device_key`
- `cargo test -p codex-state`
- `cargo test -p codex-app-server device_key`
- `cargo test -p codex-app-server
message_processor::tracing_tests::turn_start_jsonrpc_span_parents_core_turn_spans`
- `cargo test -p codex-app-server`
- `just fix -p codex-device-key`
- `just fix -p codex-state`
- `just fix -p codex-app-server`
- `just bazel-lock-update`
- `just bazel-lock-check`
- `git diff --check`
## Why
Device-key storage and signing are local security-sensitive operations
with platform-specific behavior. Keeping the core API in
`codex-device-key` keeps app-server focused on routing and business
logic instead of owning key-management details.
The crate keeps the signing surface intentionally narrow: callers can
create a bound key, fetch its public key, or sign one of the structured
payloads accepted by the crate. It does not expose a generic
arbitrary-byte signing API.
Key IDs cross into platform-specific labels, tags, and metadata paths,
so externally supplied IDs are constrained to the same auditable
namespace created by the crate: `dk_` followed by unpadded base64url for
32 bytes. Remote-control target paths are also tied to each signed
payload shape so connection proofs cannot be reused for enrollment
endpoints, or vice versa.
## What changed
- Added the `codex-device-key` workspace crate.
- Added account/client-bound key creation with stable `dk_` key IDs.
- Added strict `key_id` validation before public-key lookup or signing
reaches a provider.
- Added public-key lookup and structured signing APIs.
- Split remote-control client endpoint allowlists by connection vs
enrollment payload shape.
- Added validation for key bindings, accepted payload fields, token
expiration, and payload/key binding mismatches.
- Added flow-oriented docs on the validation helpers that gate provider
signing.
- Added protection policy and protection-class types without wiring a
platform provider yet.
- Added an unsupported default provider so platforms without an
implementation fail explicitly instead of silently falling back to
software-backed keys.
- Updated Cargo and Bazel lock metadata for the new crate and
non-platform-specific dependencies.
## Stack
This is stacked on #18428.
## Validation
- `cargo test -p codex-device-key`
- Added unit coverage for strict `key_id` validation before provider
use.
- Added unit coverage that rejects remote-control paths from the wrong
signed payload shape.
- `just bazel-lock-update`
- `just bazel-lock-check`