Skip to content

feat(policy): native expiration / single-use bounds for approved policy grants (expires_at, max_uses) #2881

Description

@ayoisio

Summary

OpenShell has no native way to make an approved policy grant time-bounded or single-use. Once a draft/chunk is approved and applied, the resulting allow rule stays in effect until a client explicitly calls the API to revoke it. There is no expires_at, ttl, or max_uses on the grant itself, so OpenShell will neither auto-expire nor one-shot-limit it.

Motivation

We are integrating an external policy-recommendation service with OpenShell (the external service produces a recommendation; OpenShell remains the enforcement authority). Many of these grants should be tightly bounded in time and usage — e.g. "allow this sandbox one egress call to an approved external endpoint, valid for the next 10 minutes."

Today we emulate that with apply-then-revoke: approve/apply the rule, then issue a follow-up API call to remove it after the window or after a single use. It works, but it pushes all single-use/expiry/replay enforcement into the external service and leaves a gap — nothing at the OpenShell layer bounds the grant to exactly one use or a fixed lifetime before the external caller revokes it.

Broader use cases:

  • Just-in-time, least-privilege grants that auto-expire.
  • Single-use grants scoped to one action (submit one envelope, run one query).
  • Break-glass / exception approvals that should lapse automatically.
  • Any third-party policy advisor that wants OpenShell to enforce the time/use bound rather than trusting the advisor to call back and revoke.

Proposed capability

Add optional expiration / usage bounds to an approved policy grant (the draft/chunk or its resulting rule):

  • expires_at (absolute) or ttl (relative): the grant auto-lapses (reverts to prior state) when it passes.
  • max_uses: the grant is consumed after N matching requests (default unlimited).
  • optional nonce / idempotency key: prevents replay of the same grant.

Enforced server-side, so the grant is bounded even if the external service never calls back. Expiry vs. manual revocation should be distinguishable in status/events.

Prior art

#2591 — feat(sandbox): add server-managed expiration leases applies the same server-managed TTL/expiration-lease pattern at the sandbox level (a reconciler auto-terminates expired sandboxes). This request is the policy-grant analog: the same lease model, but the grant auto-reverts instead of the sandbox being deleted.

Current workaround

Apply-then-revoke from the external decision layer: our decision store tracks expiry/nonce/uses and issues a follow-up deny/drop API call. Functional, but the guarantee lives outside OpenShell and is subject to a revoke race.

Acceptance criteria (rough)

  • A grant can be created with expires_at/ttl and/or max_uses (and optionally nonce).
  • OpenShell auto-reverts/denies once the bound is hit, with no external call required.
  • Status and events distinguish auto-expiry from manual revocation.

Related: #2591 (sandbox expiration leases); the draft/chunk approval flow (ApproveDraftChunk, policy reload).

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:triage-neededOpened without agent diagnostics and needs triage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions