Just-in-time privileged access

Don't hand someone the master key and hope they hand it back — lend it for the job, then take it back automatically when the clock runs out.

The idea

Just-in-time (JIT) privileged access replaces standing admin rights with a request. Instead of carrying elevated access all the time, an engineer asks for a specific scope and a short window. A reviewer (or a policy) approves it, a time-bound grant is issued, the work gets done, and the grant auto-expires — access is revoked without anyone having to remember.

This is the principle of least privilege made temporal: not just who can do what, but for how long. The goal is to shrink standing privilege to almost nothing, so the blast radius of a stolen credential or a careless action is small and short-lived.

Press play to walk an access request through its lifecycle: request, approval, a time-boxed grant, and auto-expiry.

How it works

By default the engineer holds only read-only rights — that is their standing privilege. To do privileged work they request a narrow scope for a short window. A reviewer or a policy approves (or denies), and on approval the system issues a grant with an expires_at timestamp. Every authorisation check then asks: is there an active, unexpired grant for exactly this scope? A background sweeper revokes grants the moment they expire. Every transition — request, approve/deny, grant, expiry — writes an audit entry, so you can always answer "who had access, to what, and when."

def request_access(user, scope, ttl):
    grant = approve(user, scope)          # peer/manager or policy
    if not grant.approved:
        audit(user, scope, "denied")
        return None
    grant.expires_at = now() + ttl        # time-boxed
    audit(user, scope, "granted", grant.expires_at)
    return grant

def is_allowed(user, action):
    g = active_grant(user, action.scope)  # must match scope
    return g is not None and now() < g.expires_at

def sweep(grants):                        # runs continuously
    for g in grants:
        if now() >= g.expires_at:
            revoke(g)                     # access removed automatically
            audit(g.user, g.scope, "expired")

The engineer never gains standing admin. They borrow exactly the scope they asked for, only for the window they asked for, and the system reclaims it on its own — no cleanup step to forget.

Signals & trade-offs

LeverEffectWatch
Short TTLMinimal exposure window, small blast radiusMore re-requests, more friction mid-task
Auto-approve by policyFast — no human in the loopWeaker control; lean on tight scope + audit
Narrow scopeGrant can't be reused for other systemsMay need several grants for one job
Standing accessAlways convenient, zero waitLarge, permanent attack surface

Watch out for

Worked example

At 2am an on-call engineer is paged: a production database is rejecting writes. They hold read-only access by default, so they file a JIT request for db-prod:write with a 30-minute TTL, noting the incident ticket. The secondary on-call approves it in one tap, an audit entry records the grant and its expires_at, and the engineer's badge flips to "admin (expires 02:31)." They run the fix, writes recover, and they close the incident. They don't have to remember to drop access — at 02:31 the sweeper revokes the grant, writes an "expired" audit line, and their badge returns to read-only. Standing privilege for the night: zero. The exposure window: thirty minutes, fully logged.

Check yourself

A grant is issued with a 15-minute TTL but the engineer's task runs long and they're still working at minute 20. What does a correct JIT system do?