8 min read

Credential lifecycle and execution need separate owners

Keep credential lifecycle and execution separate while preserving expiration, revocation, and attribution across a desktop action gateway.

Credential lifecycle and execution need separate owners

A secret manager should decide which credential exists, when it expires, and how it dies. A desktop gateway should decide whether this process may perform this action now, execute the action without handing the credential to the process, and record what happened. Combining those jobs feels simpler until the first rotation, revocation, or incident asks which component was actually in charge.

The dividing line is possession at the moment of use, not storage location. HashiCorp Vault or a workflow backed by 1Password can remain the lifecycle authority while a gateway controls execution, but only if the handoff carries lifecycle state and identity without turning the gateway into an untracked cache. If the gateway copies a value, forgets its lease, and keeps using it, the diagram has two boxes while the system has two secret managers.

This pattern is worth the extra plumbing when autonomous agents can reach production APIs or SSH targets. Those agents need narrow actions and human control. They do not need another way to read credentials.

The boundary sits before the authenticated action

The lifecycle system owns credential creation, rotation, expiration, and revocation; the execution gateway owns release of each authenticated action. That sentence is the architecture test. Every field, cache, retry, and log entry should have one side that can answer for it.

Lifecycle ownership includes more than keeping an encrypted string. For a dynamic database account, it includes the role that creates the account, the lease ID, the TTL, renewal rules, and the operation that deletes or disables it. For a static API token, it includes the upstream issuer, the current generation, the activation time, any overlap window, and proof that the old generation stopped working. A password manager can hold the authoritative record, but the remote API still decides whether that token is accepted.

Execution ownership begins after a caller asks for an effect such as GET /billing/invoices, POST /deployments, or an SSH command on a named host. The gateway authenticates the caller, checks the local approval state, resolves the authorized credential, injects it into the outbound protocol, and returns only the result. It should not offer a general read secret action to an agent. That would collapse execution back into secret distribution.

The clean interface therefore names an action and a credential reference, not a credential value. It also names the expected lifecycle generation so that a delayed request cannot quietly cross a rotation boundary. A useful request envelope looks like this:

{
  "action_id": "01J...",
  "credential_ref": "vault:database/creds/agent-readonly",
  "expected_generation": "lease:database/creds/agent-readonly/2f6a...",
  "purpose": "read migration status",
  "target": "db-admin.internal",
  "caller": {
    "session_id": "sess_7f2...",
    "process_authority": "signed:TEAMID.example.agent"
  }
}

The gateway may obtain the underlying value, because something must put bytes into an Authorization header or SSH handshake. The constraint is that it obtains the value inside the trusted execution path, keeps it out of agent-visible arguments, environment variables, files, tool results, and error text, then discards it according to a documented cache rule. Secretless for the agent does not mean no component ever handles plaintext.

Let the lifecycle authority mint and retire credentials

Use Vault as the lifecycle owner when its secrets engine can create and revoke the credential at the target system. Use a workflow backed by 1Password as the owner for a static credential only when that workflow also updates the issuer, records the new generation, and retires the old one. Storing the latest value alone is inventory management, not rotation.

HashiCorp's Vault lease documentation makes the contract unusually explicit. Every dynamic secret has a lease with a duration and a renewable flag. Vault promises validity for the duration, but after expiration the consumer can no longer assume the credential works. Revoking a lease invalidates the secret and prevents renewal; for supported engines, Vault also performs the underlying cleanup, such as deleting a generated cloud credential or database user.

That behavior makes Vault the obvious owner for supported dynamic credentials. The gateway should request or receive a lease, observe its returned TTL, and stop using it before the lease ends. It should never invent a longer local lifetime. HashiCorp also warns that a requested renewal increment is advisory and that clients must inspect the renewal response. A gateway that asks for another hour and assumes it got one has already broken lifecycle ownership.

Vault's KV engine is different. The Vault documentation says KV does not issue leases, even if a response includes a lease duration. Putting an API token in KV does not make the token dynamic, and deleting an old KV version does not necessarily revoke the token at its issuer. The rotation workflow must call the provider, verify the replacement, update the authoritative record, and disable the old token.

The same qualification applies to 1Password. Its CLI documentation describes secret references and commands such as op run, op read, and op inject. Those mechanisms retrieve a stored secret at runtime. They do not, by themselves, rotate a generic third-party API key at its issuer. A team may still make 1Password the authoritative record, but the automation around it must own the remote update and retirement sequence.

Do not assign lifecycle ownership by vendor category. Assign it by demonstrated control over the issuer and an observable generation change.

A safe handoff passes a reference plus a lease

The gateway needs a resolvable reference, a validity bound, and a revocation path. Passing only a reference solves naming but loses time. Passing only an expiry timestamp loses the authority that can actually revoke. Passing only a secret value loses both.

For Vault dynamic secrets, the natural generation identifier is the lease ID. The response shape from vault read database/creds/my-role includes lease_id, lease_duration, lease_renewable, username, and password. The gateway must bind the credential bytes to that metadata as one object. It must not store the password in one cache and the lease in another cache that can drift.

For a static record in 1Password, create an immutable generation marker in the rotation workflow. That can be an item version identifier exposed by your chosen integration or a rotation event ID stored beside the reference. Do not use a mutable item name such as prod-api-key as the generation. The name tells the gateway where to resolve; it does not prove which value it received.

The handoff contract should include these properties:

  • credential_ref identifies the source without containing secret material.
  • generation changes whenever the usable credential changes.
  • not_after gives the gateway a hard local stop time when one exists.
  • revocation_ref tells incident tooling what object to revoke or retire.
  • issued_for binds the credential to the intended role, target, and environment.

A gateway response should echo the non-secret parts so the caller and audit pipeline can correlate an effect without learning the credential:

{
  "action_id": "01J...",
  "status": 200,
  "credential_ref": "vault:database/creds/agent-readonly",
  "generation": "lease:database/creds/agent-readonly/2f6a...",
  "executed_at": "2026-07-27T14:03:12Z",
  "result_digest": "sha256:9b0..."
}

This contract also exposes an awkward truth: a desktop gateway needs its own machine identity to retrieve from the lifecycle system. That bootstrap credential has a lifecycle too. A Vault token should have a narrow policy and its own TTL or renewal behavior. A 1Password service account token should reach only the required vaults. Hiding the bootstrap token in a local settings file simply moves the original problem down one level.

Expiration must win over caches and retries

Expiration survives the handoff only when every execution path compares current time with the lifecycle authority's returned bound. The gateway should refuse a new action when the remaining lifetime cannot cover resolution, approval, connection setup, execution, and a small clock margin.

Suppose Vault issues a database credential for ten minutes. An agent starts an export at minute nine, the user spends forty seconds reading an approval card, and the gateway retries twice after a network timeout. A cache that checked the TTL only when it fetched the credential may start the final retry after expiry. The target then returns an authentication error, but the audit trail may mislabel it as a network failure or user denial.

Set the usable deadline once from authoritative metadata:

usable_until = min(authority_not_after, fetched_at + local_cache_cap)
latest_start = usable_until - approval_budget - connect_budget - clock_margin

The budgets are operating choices, not extensions of the credential lifetime. If now is later than latest_start, fetch a new generation and show a fresh approval if the approved action would materially change. Never renew a Vault lease merely to rescue a request that waited in a local queue. Renewal is a lifecycle decision and may enlarge the exposure window.

Retries need the same discipline. A retry may reuse a credential only when the generation still matches, the credential remains within its usable deadline, and the remote operation is safe to repeat. HTTP idempotency and credential validity are separate checks. A valid token does not make a duplicate POST harmless.

For static credentials without an expiry provided by the issuer, use a local cache cap to limit stale copies, but call it a cache policy rather than expiration. The lifecycle workflow still needs a generation notification or a forced re-resolution after rotation. Otherwise the gateway can keep a valid old token throughout an overlap window and make the retirement test look successful until the provider finally disables it.

Revocation is an end-to-end event

Keep SSH keys out of prompts
The stateless `sp-ssh` helper executes approved SSH commands without handing keys to the agent.

Revocation works only when the lifecycle authority disables the credential at the issuer and the gateway stops all future use of its cached generation. Clearing one side is incomplete.

Vault gives operators a concrete mechanism. vault lease revoke invalidates one lease, and prefix revocation can invalidate leases under a path. HashiCorp's command documentation also distinguishes normal revocation from force removal. Force removal can make Vault forget a lease even when the secrets engine failed to revoke it, leaving Vault out of sync with the target. Treat that warning as an incident condition, not a successful cleanup message.

A desktop gateway should consume a revocation signal when the lifecycle system can send one, but it also needs a defense that pulls current status. Before a sensitive use, it can revalidate the generation or resolve it again. For short Vault leases, a strict TTL plus short cache may be enough. For a static API token, the rotation coordinator should invalidate the gateway cache as part of the cutover and then test the retired token directly against a harmless endpoint.

Actions already running require an explicit rule. Revocation can reliably block actions that have not started. It may not undo a request already accepted by a remote API, a database transaction already committed, or an SSH command already handed to a shell. The gateway should mark the action state as authorized, dispatched, acknowledged, or unknown, rather than claiming that revocation erased an effect.

Test the full path with the old generation still known to a controlled harness:

  1. Execute a harmless read through the gateway and record the generation.
  2. Revoke the lease or rotate and disable the static credential at the issuer.
  3. Attempt the same action through a warm gateway cache.
  4. Attempt direct use of the retired credential from the harness.
  5. Confirm both failures and correlate them with the lifecycle and execution records.

If step three succeeds, the gateway ignored revocation. If step four succeeds, the lifecycle workflow did not revoke at the issuer. If both fail but the records cannot identify the same generation, incident responders still cannot prove what happened.

Attribution needs issuer identity and caller identity

Lifecycle logs answer who minted, renewed, rotated, or revoked a credential. Gateway logs answer which local process requested which action, who approved it, what target received it, and what result returned. Neither log can replace the other.

Dynamic credentials improve attribution at the issuer when each lease produces a distinct remote identity. HashiCorp's database secrets engine documentation notes that unique generated usernames let operators trace database access to a specific service instance. That is useful, but the database username still identifies the gateway's leased identity, not necessarily the agent process that asked the gateway to run a query.

The gateway therefore needs a stable session identity and a trustworthy process identity. A PID alone is weak because operating systems reuse PIDs and processes can launch children. Record the executable identity available on the platform, its signing authority when present, the parent process, the session start and end, and an unguessable session ID. Bind every action record to that session.

The join field is the credential generation. Put the Vault lease ID or static rotation event ID in both the lifecycle record and the gateway action record. Also record the remote request ID when the API returns one. During an incident, investigators should be able to walk this chain:

agent session -> gateway action -> credential generation -> issuer event -> remote request

Do not put secret values, Authorization headers, private keys, or resolved environment blocks into any of those logs. Redaction after logging is unreliable because exceptions, debug dumps, and tracing exporters may copy the data first. Build structured records from an allowlist of safe fields.

Attribution also fails when all actions share a service account with a long lifetime and no gateway record survives. Rotation reduces the lifetime of that account but does not identify the caller. Conversely, perfect process logs cannot prove which credential generation reached the target if the gateway omits the lease or version. Preserve both dimensions.

Environment injection crosses the boundary

Stop a session without rotating
Revoke an active agent run immediately while the credential remains under separate lifecycle control.

A workflow that injects a secret into an agent's environment gives the agent possession, so the desktop gateway no longer controls each use. This distinction matters most with 1Password CLI patterns because retrieval convenience can look like execution control.

The 1Password documentation says op run starts a subprocess with secrets provisioned as environment variables for that process. That can be a sensible way to keep plaintext out of a checked-in .env file, but the child process can read the variable, print it, pass it to another process, or use it for an unapproved request. op inject resolves references into a configuration stream, and op read returns a resolved value to the caller. None of those paths is equivalent to a gateway that keeps the secret outside the agent.

For human-operated scripts that need broad native SDK behavior, environment injection may be acceptable. For an autonomous agent whose network and SSH actions need control on each use, it defeats the intended boundary. Give the gateway the restricted retrieval identity and expose action-shaped tools to the agent instead.

The bootstrap still deserves scrutiny. 1Password recommends service accounts for least privilege and allows access to be restricted to specific vaults. That narrows what the gateway can retrieve. It does not constrain what the gateway can do after retrieval, so the gateway's action surface, approvals, and outbound target validation remain necessary.

Avoid a popular workaround: resolve the secret in a wrapper, call the gateway with the credential as a parameter, then promise that the gateway will redact it. The agent or wrapper already held the value, shell history and process inspection may expose it, and the gateway cannot prove that no second use occurred. Pass the reference across the boundary and resolve inside the component that executes.

Approval is independent of rotation

A freshly rotated credential can still authorize a bad action, and a carefully approved action can still use a stale credential. Rotation and approval reduce different risks, so neither should silently stand in for the other.

The lifecycle authority answers, "Is this credential generation valid?" The gateway answers, "May this caller cause this effect now?" The remote service answers, "Does this authenticated identity have permission?" Keep all three answers visible. A green approval card should not imply that the credential is current unless the gateway checked it. A current lease should not bypass a human approval required for a destructive call.

Approval reuse needs a defined scope. If the gateway approves an agent session, bind that approval to the process session and terminate it when the process exits or the user revokes it. If a credential is marked for approval on every call, rotation should preserve that requirement when the new generation appears. A new value must not reset a sensitive credential to a weaker default.

Approval text should describe the action, target, and caller, not the secret. "Allow signed process X to run POST /deployments against production" gives a person something to judge. "Allow use of API key prod-3" makes the human reconstruct intent from inventory names and trains them to approve opaque prompts.

Sallyport implements this execution half for HTTP API and SSH actions on macOS: agents connect through its MCP shim, while credentials stay in its encrypted in-process vault and the app executes the action. Its fixed controls separate a locked vault, process session approval, and optional approval on every use; that model does not remove the need for an external lifecycle owner when another system mints or rotates the credential.

Dual rotation creates two apparent authorities

Put HTTP execution behind approval
Agents request API actions while Sallyport adds bearer, basic, or custom-header credentials itself.

Do not let the lifecycle system and the gateway independently rotate the same credential. Teams sometimes call that defense in depth, but two writers create ambiguous generations, unreliable rollback, and revocation races.

Consider a static provider that permits two active API keys. The 1Password workflow creates key B, tests it, and updates the authoritative item while key A remains active for a short cutover. At the same time, the gateway's local scheduler creates key C because its copy of key A reached a configured age. Some processes resolve B, the warm cache still holds A, and the gateway begins using C. Disabling A proves almost nothing, because nobody knows whether B or C should survive.

One coordinator must own the rotation state machine. For a Vault dynamic secret, Vault already supplies that coordinator through its role and lease machinery; the gateway consumes leases and does not rotate the remote account. For a static record governed through 1Password, the rotation job can coordinate the provider and the stored item; the gateway observes generation changes and invalidates its cache. Local encryption by the gateway protects a stored copy, but re-encrypting storage is not issuer credential rotation.

A workable static rotation has explicit states rather than a single rotated flag:

prepared -> activated -> distributed -> old_disabled -> verified
                |             |
                +-> rollback <-+

prepared means the issuer created a candidate generation. activated means a harmless authenticated request succeeded with it. distributed means the authoritative reference resolves to the candidate and gateways acknowledge the new generation. old_disabled means the issuer rejects the predecessor. verified means a warm-cache gateway and a direct harness both fail with the old value. Keep rollback possible only while the predecessor remains intentionally active.

The gateway should receive state changes that contain references and generation IDs, never both secret values. An invalidation event might name credential_ref, old_generation, new_generation, and effective_at. On receipt, the gateway drops the old cache entry, cancels queued actions bound to it, and re-resolves when the next approved action starts. If the event never arrives, the gateway's cache cap and generation check must still converge on the authoritative value.

Rollback deserves the same rigor. Repointing a 1Password item to key A does not work after the provider has disabled key A. Reissuing a new value under the old display name does not restore the old generation either. The coordinator should create or reactivate only what the provider supports, assign a new generation ID, and run the normal distribution and verification phases again.

Keep the ownership record small enough to inspect during an incident. For every credential reference, name one rotation coordinator, one issuer, one gateway cache policy, one revocation command, and one person or service authorized to start emergency retirement. If two rows claim they can create the next generation, stop. That is not redundancy; it is a race with secret material.

Test the seam instead of each box

A passing Vault test and a passing gateway test do not prove that their handoff works. Test stale caches, overlapping generations, delayed approvals, failed revocation, process restarts, and missing join fields at the boundary.

Use a disposable target identity and run this acceptance matrix before production:

ConditionExpected gateway decisionEvidence required
Current generation, enough TTLExecuteSession, action, generation, remote request
Current generation, TTL too shortRe-resolve or denyReturned TTL and local timing decision
Rotated record, warm old cacheReject old generationCache invalidation and issuer denial
Revoked Vault leaseDeny without retrying old leaseRevocation record and failed target auth
Approval expires while waitingDeny or request fresh approvalApproval deadline and no dispatch event
Gateway restarts after approvalRequire the configured session decisionNew session identity and prior session end
Issuer revocation failsMark incident, retain evidenceProvider error and unresolved generation
Target accepts retired tokenFail the lifecycle testDirect probe result and rollback decision

Run the matrix against the same paths agents will use. A mock that returns 401 on cue cannot reveal whether a real database plugin removed a user, whether a provider honors overlapping keys, or whether an SSH connection remains alive after credential revocation.

Set explicit pass criteria. No action starts after not_after. A revoked or retired generation fails through a warm cache. Every execution record joins to one lifecycle generation without secret material. A failed issuer revocation blocks claims of successful retirement. An approval record identifies a process session and expires according to the configured control.

The boundary earns its keep when failures stay local. Vault or the 1Password rotation workflow can replace credentials without teaching the agent about secret values. The gateway can change approval behavior without becoming the issuer. Incident responders can revoke a generation, stop a session, and see which remote effects may already have happened. If your seam test cannot prove those three operations independently, fix the contract before adding another secret backend.

FAQ

Should Vault rotate credentials that a desktop gateway uses?

Yes, when a Vault secrets engine can mint and revoke the credential at the target system. The gateway should consume the returned lease metadata, enforce its deadline, and record the lease ID with every action.

Can 1Password automatically rotate any stored API key?

No. Storing and retrieving a generic API key does not rotate it at the issuer. A complete rotation workflow must create the replacement upstream, update the authoritative record, test it, and disable the old key.

Does passing a secret reference keep an AI agent secretless?

Only if the agent cannot resolve the reference itself and the execution gateway resolves it inside the trusted action path. If the agent can call op read, inspect an injected environment variable, or receive the resolved value, it possesses the secret.

Can a gateway cache a Vault dynamic credential?

It can cache one within a documented limit that never exceeds the returned lease lifetime. Every retry and delayed approval must check the remaining lifetime, and revocation must invalidate the cached generation.

What should happen to an action already running when a credential is revoked?

Revocation should block work that has not started, but it may not undo an API request already accepted or a command already dispatched. Record the action state precisely so responders can distinguish blocked, completed, and unknown effects.

Which ID should join gateway logs to Vault logs?

Use the Vault lease ID for a dynamic secret. For a static secret, use an immutable rotation event or version ID rather than a mutable item name.

Is `op run` equivalent to a per-use execution gateway?

No. op run provisions secrets to a subprocess environment, so that process can read and reuse them. A per-use gateway accepts an action request, injects authentication itself, and returns the result without returning the credential.

Who owns revocation when 1Password stores the secret?

The rotation workflow owns coordination, while the upstream issuer performs the effective revocation. Deleting or replacing a password-manager record is not enough unless the old credential also stops working at the target.

Does frequent rotation remove the need for approvals?

No. Rotation limits how long a credential remains useful, while approval controls whether a particular caller may cause a particular effect. A new credential can authorize the same destructive operation as the old one.

How do I know the lifecycle and execution split is worth it?

Use it when agents perform authenticated actions whose credentials they should never possess and when you need independent revocation and attribution. If the handoff cannot preserve generation, expiration, and issuer status, the split adds boxes without adding control.

Sallyport

Sallyport runs API calls and SSH commands for your AI agent. The keys stay in a local vault on your Mac; you approve each run and every action lands in a sealed journal.

© 2026 Sallyport · Open source under Apache-2.0 · Oleg Sotnikov