Skip to content

Phone-authorized, user-custodied secret release for local agents and CI #286

Description

@bsutton

Summary

Build user-custodied, phone-authorized secret release for reVault. A desktop process, AI agent, or CI job can request an operation, but neither the requester, developer filesystem, platform credential store, relay, nor CI provider can approve itself.

The differentiator is independent decryption authority: an enrolled phone verifies the source and approved scope. For an unlock it returns a request-bound decryption factor over an end-to-end encrypted channel; for a brokered operation it authorizes execution without releasing the reusable credential to the requester.

This is a reVault feature, not a general HCP Vault replacement. Unattended workloads should normally use OIDC-to-Vault or a user-operated service/HSM.

Related: #201 considers reVault issuing workload identity. This issue instead consumes provider OIDC to authenticate requesters and uses the phone for independent authorization/decryption.

Product boundary

Proceed with a narrow local-agent plus GitHub Actions MVP, starting with purpose-specific lockbox unlocking. Treat brokered operations as a separate capability to validate with one concrete deployment/release workflow.

Product requirements:

  • Use a phone the developer already owns. Hardware security keys are out of scope; users must not need to buy a device.
  • Developers must be able to approve CI while away from their desktop, on a different network or mobile data.
  • Phone-held cryptographic authority is mandatory for protected access. A desktop approval flag or PIN-wrapped stored vault password does not satisfy this boundary.
  • Keep enrollment simple and prompts meaningful. Allow ordinary development lockboxes to remain convenient while sensitive lockboxes require explicit approval.

A process with complete access to the developer filesystem cannot use a protected lockbox or credential without independently authenticated phone approval.

One lockbox is one authorization boundary initially. Releasing its content key permits the requester to read that lockbox, so the MVP recommends small purpose-specific lockboxes.

Two distinct capabilities

  • Approve unlock: release access to one lockbox to the desktop or CI runner. The requester can retain its content key and plaintext after approval. Grant expiry prevents late acceptance; it cannot revoke a key or plaintext already received. Do not describe this as approval for every subsequent read.
  • Approve operation: a phone or isolated broker uses a credential for one defined action and returns a filtered result. The requester receives neither the reusable credential nor the lockbox content key. This requires operation-specific policy and an enforced execution boundary.

Protected-mode enrollment must eliminate unattended alternative access paths, including platform-stored vault passwords, session-agent caches, and locally accessible recipient keys that can independently unlock the protected content. Report cleanup failures and fail closed. Previously copied keys or plaintext cannot be recalled; migration must account for prior exposure and necessary key rotation.

Recovery must be explicit: a second enrolled phone or offline recovery material. Do not store recovery/decryption authority in developer files or CI secrets. Revocation prevents future authorization, not use of previously released keys.

Threat model

Assume an AI or compromised same-user process can read and modify all developer files, inspect normal configuration/environment/platform credentials, initiate arbitrary requests and attempt replay. CI runners, workflows and the relay may be compromised. Provider OIDC is trusted only for signed workload identity. After approval, the requester can observe deliberately released values.

Protect against:

  • approval keys stored on the desktop or in CI;
  • relay access to context, OIDC tokens, keys or values;
  • request/response replay and binding substitution;
  • misleading requester-supplied names/context;
  • GitHub jobs outside enrolled repository/workflow/ref/environment policy;
  • proxy credential exfiltration to an unrestricted destination.

Not protected initially:

  • misuse after the user approves the disclosed scope;
  • compromise of an authenticated enrolled phone;
  • a compromised OIDC issuer;
  • dynamic credentials, PKI, enterprise PAM or unattended exclusive phone custody.

Architecture

Devices and sources

  • Support multiple independently revocable phones.
  • Devices are first-class records rather than contacts: id, user name, recipient key, transport key, response verification key, opaque mailbox id, platform, capabilities and lifecycle state.
  • Pairing transfers public material over an authenticated QR/pairing transcript.
  • Phone private material is Keychain/Keystore encrypted and released after local authentication. Use Secure Enclave/StrongBox when supported, but do not require exportable hardware-backed keys; current phones generally cannot hardware-back the hybrid PQ recipient key.
  • Store local desktop and CI sources separately, with an owner-assigned display name, exact allowed lockbox ids/actions, provider identity and lifecycle state.
  • Only verified claims determine identity displayed as trusted.

Approval protocol

Bind every request to protocol version, random 256-bit request id, independent challenge, device/source/lockbox ids, operation, canonical parameter digest, two-minute expiry, one-time reply key, candidate recipient slot, and authenticated desktop signature or encrypted OIDC token.

The phone verifies policy before approval. For approve-unlock, its response contains only the scoped factor/content key, encrypted to the one-time reply key, signed by the phone, bound to all request fields and expiry, and protected by atomic relay consumption plus client replay caches. Never release the reusable vault password or a master private key that unlocks other lockboxes.

For approve-operation, bind approval to the exact transaction and intended executor. A phone executor returns the filtered operation result; a separate trusted executor receives only what it needs through an authenticated encrypted channel. The requester must not be the recipient of the credential or decryption factor. Define distinct message types and reject mode/executor substitution.

Relay

Use push plus an opaque HTTPS mailbox as the default transport. Both desktop/CI and phone make outbound connections, so no inbound desktop ports, same-network requirement, or P2P/NAT traversal is needed. The relay stores only bounded ciphertext indexed by opaque capability hashes.

An external relay is a practical transport choice, not decryption authority. A malicious relay must be unable to decrypt, forge approval, or rebind a response, although it can observe connection metadata and delay/drop traffic. Support a configurable relay endpoint for self-hosting. Direct LAN/Bluetooth transport is optional future work, not a prerequisite and not inherently stronger than authenticated end-to-end encryption.

Push is a wake-up hint, not a source of truth or guaranteed delivery. Opening the phone app must fetch pending requests directly. Handle expired requests and relay outages clearly; never fall back to unattended unlock. Keep sensitive context out of notifications and provide denial, rate limits, and cancellation to reduce approval fatigue.

Require a 16 KiB envelope limit, two-minute lifetime, per-source minute/hour quotas, per-source/device pending limits, push quotas, atomic response consumption and 429 Retry-After. Push contains only a wake-up hint. Production multi-replica use requires shared persistent pending/rate state.

GitHub Actions

The job obtains a short-lived GitHub OIDC token; no approval key is stored in GitHub or on the desktop. The phone verifies issuer discovery/JWKS, signature, issuer, audience, times and stable policy claims such as repository_id, job_workflow_ref/workflow SHA, ref, environment and necessary run context.

The prompt shows the owner-assigned source plus verified repository, workflow, ref/environment and commit. Free-form text is visibly unverified. Generic OIDC and other providers are deferred.

Credential/operation broker

A broker owns the outbound authenticated connection and performs a defined operation. It never returns reusable credentials through prompts, environment variables, command arguments, headers handed to the requester, logs, or unfiltered responses.

Example: the requester asks to deploy artifact digest abc… to staging. The phone displays verified requester information, destination, action and artifact digest. Approval binds the exact transaction. The executor calls the allowed service and returns a deployment identifier and status.

Prefer a small operation vocabulary such as “deploy this artifact to staging” over arbitrary HTTP with a production token. A versioned opaque credential handle identifies the secret; do not scan or rewrite arbitrary prompt text.

Execution boundary

Executor Security boundary and tradeoff
Ordinary same-user desktop process Helps avoid accidental prompt exposure, but does not protect against the assumed same-user adversary that can modify code/policy or inspect credentials.
Separate OS account or isolated service Viable only if requester permissions cannot access executor memory, keys, code, policy or service administration. A process/container label alone is insufficient.
Separate hosted broker Isolates secret use from developer machines and CI runners; the service becomes trusted for released credentials and must be secured independently.
Phone itself Keeps credential use off desktop and CI. The phone approves and makes the HTTPS call, returning a filtered result through the encrypted relay. Requires endpoint reachability and practical mobile execution.

Explore phone execution first for one short deployment/release API operation. This is a design/prototype direction, not a settled requirement that every operation execute on the phone. Large uploads, long-running work and private-network endpoints may require a separately hosted executor or explicitly approved unlock to CI. Do not silently switch between these security modes.

A broker inside the same compromised CI job does not establish isolation. Likewise, desktop signatures identify an enrolled source, not a trustworthy individual process when that source's signing key is accessible to the AI.

Transaction policy

Bind credential/lockbox, intended executor, exact HTTPS origin, method/path constraints, injection location, relevant payload/artifact digest, redirect policy (normally disabled), expiry and one-transaction approval. The executor must enforce the approved request, not let the requester modify it after approval.

Require SSRF/DNS-rebinding resistance, canonical URL parsing, TLS verification, no credential forwarding on redirects, bounded bodies, credential redaction and response filtering. Exclude operations that can return the reusable credential or mint equivalent unrestricted access. Filter results according to an explicit operation schema, not just string replacement.

Handle ambiguous network outcomes and retries with operation-specific idempotency/reconciliation. A consumed grant alone does not guarantee exactly-once execution at an external API.

The phone should show enough meaningful, authenticated context to approve the actual action; free-form requester descriptions remain visibly unverified. Neither broker isolation nor phone approval makes an excessively broad authorized operation safe.

Reduced implementation plan

Phase 0 — design gate

  • Express local-agent and GitHub threats as testable invariants.
  • Specify purpose-specific lockbox unlock as the initial flow; keep approve-operation a distinct follow-on capability.
  • Select one deployment/release operation to evaluate phone execution versus an isolated hosted executor.
  • Define executor trust, recovery, protected-mode migration and cached/alternate-key removal.
  • Define which prompt fields are cryptographically verified.
  • Review existing feature/phone-device-approval work; retain only MVP code and separate unrelated format work if needed.

Phase 1 — protocol and administration

  • Finalize versioned multi-device/source records.
  • Finalize E2E request/grant, expiry, signature and replay handling.
  • Test tampering, wrong bindings, reply-key substitution, expiry and duplicate use.
  • Add CLI enroll/list/revoke device and add/list/revoke source.
  • Complete Dart bindings for new Rust APIs only; other language facades remain unchanged.

Phase 2 — single-instance relay

  • Complete opaque request/response mailboxes and rate/pending limits.
  • Add HTTP integration and abuse tests.
  • Keep deployment explicitly single-instance pending shared persistence.
  • Add APNs/FCM wake-up with no sensitive push payload.
  • Support manual pending-request fetch, configurable relay endpoint, cancellation, and clear outage/expiry behavior.

Phase 3 — minimal phone app

  • Enroll and protect phone keys.
  • Fetch/decrypt and validate requests.
  • Separate verified and unverified prompt context.
  • Require local biometric/PIN confirmation.
  • Sign/upload one-time encrypted responses.
  • Test sleeping/background behavior on iOS and Android, including manual fetch after missed push.
  • Demonstrate approval over mobile data with the desktop on a different network.
  • Implement second-device/offline recovery and verify revocation semantics.

Phase 4 — local AI-agent unlock vertical slice

  • Complete one purpose-specific protected lockbox unlock using phone approval.
  • Verify platform credentials, session caches and alternate local recipient keys cannot bypass approval.
  • Test rejection, expiry, cancellation, replay, offline phone and relay failure.
  • Document that the approved requester receives and can retain the lockbox content key/plaintext.

Phase 5 — GitHub Actions vertical slice

  • Implement GitHub OIDC validation on the phone.
  • Enroll one stable repository/workflow/environment policy.
  • Request one purpose-specific lockbox from a workflow.
  • Display verified repository/workflow/ref/environment/commit.
  • Demonstrate no desktop file or GitHub secret holds approval/decryption material.
  • Document exactly what the approved runner receives.
  • Demonstrate CI approval while the developer is away from the desktop, using mobile data.

Phase 6 — one brokered operation prototype

  • Prototype one short deployment/release API operation executed by the phone; assess endpoint reachability and mobile lifecycle limits.
  • If a hosted executor is needed, define and test its enforced boundary from the desktop and CI requester before claiming credential secrecy.
  • Bind the exact operation, executor and payload/artifact digest to phone approval.
  • Own outbound TLS, enforce origin/method/path policy, disable redirects and return an explicit filtered result schema.
  • Demonstrate that requester prompts, environment, files, logs and responses never contain reusable credentials or content keys.
  • Test executor/mode substitution, request mutation, alternate hosts, redirects, DNS rebinding, header injection, response leakage and replay.
  • Specify idempotency and retry/reconciliation for ambiguous external API outcomes.

Deferred

Generic/GitLab OIDC; replicated relay; quorum approval; finer-grained per-entry encryption; other request-signing schemes; unattended broker/HSM; Vault/cloud dynamic credential integration; enterprise administration; and OIDC assertion issuance from #201.

Acceptance criteria

  • A process with full developer-filesystem access cannot approve itself.
  • Relay cannot learn context, tokens, keys or values.
  • Captures cannot be replayed or rebound.
  • Phone shows enough verified information for an informed decision.
  • GitHub jobs outside exact policy fail.
  • Broker cannot send a credential to another origin or return it to Codex.
  • Setup for one phone/source is operationally reasonable and requires no purchased hardware.
  • Remote CI approval works across different networks; manual app fetch works without timely push.
  • Protected-mode cleanup failures cannot silently leave unattended access enabled.
  • Approve-operation has a tested execution boundary, and does not release credentials or content keys to the requester.
  • Documentation distinguishes expiring grants from irrevocably released keys/plaintext and states recovery limitations.
  • Documentation states what becomes visible after approval.

Go/no-go validation

Before expanding, validate that enrollment takes roughly ten minutes, users understand approval scope, latency is acceptable, the broker solves a workflow GitHub environments/OIDC-to-Vault do not, and purpose-specific lockboxes are workable. If users mainly need unattended dynamic credentials and central administration, integrate with Vault instead of recreating it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions