A developer with permanent admin access is a breach waiting for a phishing email. The blast radius is the standing part, not the privilege itself: anyone who compromises the account inherits access that never expires. Just-in-time (JIT) access flips the model — nobody holds elevated permissions by default; they request them for a task, a human approves with the justification attached, the grant expires automatically, and every step is audited.
JIT privileged access approval workflow: request, grant, revoke
| Element | What it does | Fails closed when… |
|---|---|---|
| Request | Who, what system, why, for how long | No request, no access |
| Approval | Named approver, on record | No approver → auto-deny after SLA |
| Time bound | Grant carries an explicit expiry | Expiry enforced by the platform, not the user |
| Auto-revoke | Entitlement removes itself | Task done early → user can end the grant |
Step 1: the request creates the decision
Your JIT platform (AWS SSM, Google PAM, BeyondTrust, or your own script) stops at the approval boundary and asks — instead of granting:
#!/usr/bin/env bash
# request-jit.sh — request temporary access to a production node
set -euo pipefail
NODE="${1:?node name}"
ROLE="${2:?role to elevate}"
DURATION_H="${3:-4}" # routine ops: 4h, incident response: 8h, maintenance: 12h
REASON="${4:?business justification}"
EXPIRES_AT=$(date -u -d '+15 minutes' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u -v+15M '+%Y-%m-%dT%H:%M:%SZ')
actionbox ask \
"Grant ${ROLE} on ${NODE} for ${DURATION_H}h?" \
--option approve="Approve (auto-revokes in ${DURATION_H}h)" \
--option deny="Deny" \
--context-json "[{\"type\":\"key_value\",\"items\":{\"requester\":\"$(whoami)\",\"node\":\"${NODE}\",\"role\":\"${ROLE}\",\"duration_h\":\"${DURATION_H}\",\"reason\":\"${REASON}\"}}]" \
--expires "$EXPIRES_AT" \
--on-expire-json '{"type":"resolve","response":{"type":"single_choice","value":"deny"},"reason":"approval SLA exceeded"}' \
--callback-url https://ops.acme.com/actionbox/jit-callbackThe callback does the actual elevation only when the decision is approve. Timeout is a denial — the request vanishes instead of lingering.
Step 2: the grant is time-bounded and self-revoking
The approver's "yes" is not a permanent key. The callback grants the role with an explicit expiry, and revocation is the platform's job:
# jit-callback.sh — called with the decision from Actionbox
DECISION="$1"
if [ "$DECISION" = "approve" ]; then
# Call your PAM/provider grant API with the approved role, node, and
# bounded duration. Use the provider's documented command/API here.
# The access duration is enforced by SSM — no cleanup script needed.
# Audit: session recording + approval record both reference this request.
else
echo "Access denied: $DECISION"
exit 1
fiAuto-revocation prevents the classic failure mode — users request elevation "for a quick fix" and never release it, accumulating residual privilege. The timer expires, session ends, the audit log closes the record.
Step 3: every grant is an audit record
JIT without an audit trail is just permanent elevation with extra steps. The record per grant is what supports forensic reconstruction later:
# Monthly review: use Actionbox History/API filters for the Source and
# resolved status. The CLI `get` command requires one Action ID.Watch three metrics: approval latency (median under two minutes for routine elevations), break-glass usage (under 0.1% of total elevations), and approver engagement (evidence they actually examined the request). When latency drifts up, people start routing around the system — that's how standing privilege sneaks back in.
Full example: break-glass with a reason, not a backdoor
Every JIT system needs a fallback when the platform is down. Make it a recorded path, not a shared password:
#!/usr/bin/env bash
# break-glass.sh — emergency elevation, logged even when the queue is down
set -euo pipefail
reason="${1:?incident ticket required}"
actionbox ask \
"BREAK-GLASS: emergency elevation by $(whoami)" \
--option approve="Authorize for the incident window" \
--option deny="Route through the normal JIT queue" \
--context-json "[{\"type\":\"key_value\",\"items\":{\"mode\":\"break-glass\",\"ticket\":\"${reason}\",\"expiry\":\"auto-revoke 1h\"}}]" \
--callback-url https://ops.acme.com/actionbox/break-glass-callbackUnapproved break-glass is how the fallback becomes the new standing-privilege backdoor. Approved-and-logged break-glass is how you keep an emergency lane without the hole.
Why this beats "everyone is an admin"
- Blast radius shrinks to the task — a compromised account owns nothing for longer than its last grant
- Approval latency is a product feature — routine requests target a two-minute median, not a ticket queue
- Expiry is enforced, not hoped for — revocation doesn't depend on user discipline
- Compliance evidence is one query — who had what, when, why, and who approved — on record forever
Try it
- Install the CLI on your bastion or automation host
- Create a Source →
ACTIONBOX_TOKEN - Put the ask in front of your elevation command
Create a free Source · CLI + callback reference · Secret rotation sign-off · Cron jobs that ask before acting