DEV Community

Zira
Zira

Posted on

Your AI Agent Needs a Credential Lease, Not a Permanent API Key

Long-running agents turn a small credential mistake into a large incident.

A worker that can run overnight, spawn tools, or retry after a restart should not receive a permanent API key and keep it until the process dies. Treat access as a lease with an owner, scope, expiry, and revocation state.

The lease contract

Store these fields beside the run, not only in environment variables:

  • lease_id
  • run_id
  • principal
  • allowed_actions
  • allowed_resources
  • issued_at
  • expires_at
  • revoked_at
  • credential_version

A tool call is allowed only when all of these checks pass:

  1. The lease exists and is unexpired.
  2. The run is still authorized to use it.
  3. The requested action and resource match the lease.
  4. The credential version is current.
  5. The worker has not entered a quarantine state.

The model can suggest a tool call. It must not decide whether the lease is valid.

Recheck at dispatch time

Checking access when a run starts is not enough. Queues, retries, and browser sessions create time gaps. Recheck immediately before dispatch:

def authorize(call, lease, policy, now):
    if lease.revoked_at is not None:
        return 'DENY_REVOKED'
    if now >= lease.expires_at:
        return 'DENY_EXPIRED'
    if lease.credential_version != policy.current_version(lease.principal):
        return 'DENY_STALE_VERSION'
    if call.action not in lease.allowed_actions:
        return 'DENY_ACTION'
    if call.resource not in lease.allowed_resources:
        return 'DENY_RESOURCE'
    return 'ALLOW'
Enter fullscreen mode Exit fullscreen mode

Do not silently refresh a denied lease. Create a new authorization decision with a new lease ID, and record why the old one failed.

Make revocation observable

A useful event trail has one row per decision:

  • LEASE_ISSUED
  • CALL_PROPOSED
  • CALL_ALLOWED or CALL_DENIED
  • CREDENTIAL_PRESENTED
  • OUTCOME_CONFIRMED, OUTCOME_FAILED, or OUTCOME_UNKNOWN
  • LEASE_REVOKED

Never log the secret itself. Log the lease ID, credential version, provider request ID when available, and a hash of the resource identifier if the raw value is sensitive.

The failure test that matters

Run this test against a staging provider:

  1. Start a run with a 10-minute lease.
  2. Queue a tool call but pause the worker before dispatch.
  3. Revoke the lease and rotate the credential.
  4. Resume the worker.
  5. Assert that dispatch is denied with DENY_REVOKED or DENY_STALE_VERSION.
  6. Repeat with the provider accepting the request but timing out the response.
  7. Mark the result UNKNOWN and reconcile using the provider request ID before retrying.

This catches the dangerous gap between the agent being allowed earlier and the agent being allowed now.

Hosting does not remove the boundary

If you run an always-on OpenClaw or browser worker on a managed runtime such as managed OpenClaw hosting on Ampere, the lease layer still belongs in your application. Hosting can simplify where a process runs; it does not decide which tenant, tool, resource, or credential version a worker may use.

A practical review checklist

  • Every lease has an expiry and explicit scope.
  • Dispatch rechecks policy after queueing and after retries.
  • Revocation reaches workers, browser sessions, and child tools.
  • Credential rotation invalidates old versions.
  • UNKNOWN outcomes are reconciled before retry.
  • Logs prove decisions without exposing secrets.
  • A staging test demonstrates that a paused worker cannot use a revoked lease.

The goal is not to make agents incapable of acting. It is to make every action attributable, bounded, revocable, and recoverable.

Top comments (0)