escapes is a setuid-root binary that replaces traditional sudo with a grant-based approval workflow. Instead of a password, each privileged command requires real-time approval from an authorized approver through an OpenApe Identity Provider (IdP).
apes run --as root -- whoami
│
▼
┌───────────┐ POST /api/grants ┌────────────────┐
│ apes │ ──────────────────────►│ OpenApe IdP │
│ (CLI) │ ◄── approved + JWT ───│ │
└─────┬─────┘ └───────┬────────┘
│ escapes --grant <jwt> -- whoami │
▼ Approver approves
┌───────────┐ POST /consume in browser UI
│ escapes │ ──────────────────────►
│ (setuid) │ ◄── OK ──────────────
│ │ verify 7 properties
└─────┬─────┘ elevate, exec
▼
Command runs as root
Key properties:
- Grant-token only —
escapesreceives a pre-approved JWT fromapes; no key management, no polling - 7-step verification chain before any command runs:
- Issuer is in
allowed_issuers - JWT signature valid (JWKS)
- Approver is in
allowed_approvers - Audience is in
allowed_audiences target_hostmatches this machine- Command /
cmd_hashmatches - IdP consume check passes (replay protection)
- Issuer is in
- Environment is sanitized before exec (
LD_PRELOAD,PATH, etc.) - Full audit log in JSONL format
The security boundaries are:
allowed_issuers— which IdPs are trusted (only their JWKS is fetched)allowed_approvers— who can approve grants (equivalent to sudoers)allowed_audiences— which services this instance accepts grants for (default:["escapes"])target_host— grants are bound to a specific machine; a grant for"macmini"won't work on"server01"- Config is root-owned —
/etc/openape/config.tomldefines the trust boundary; only root can modify it
- A running OpenApe IdP with grants support — see docs.openape.at
@openape/apesCLI — the companion tool that handles login, grant requests, and execution pipeline- macOS (aarch64/x86_64) or Linux (amd64/arm64)
Download the .pkg installer from GitHub Releases and double-click. The installer sets the setuid bit, creates /etc/openape/config.toml, and the audit log directory.
curl -sSfLO https://github.com/openape-ai/escapes/releases/latest/download/openape-escapes_0.4.0_amd64.deb
sudo dpkg -i openape-escapes_0.4.0_amd64.debcurl -sSfLO https://github.com/openape-ai/escapes/releases/latest/download/openape-escapes-0.4.0.x86_64.rpm
sudo rpm -i openape-escapes-0.4.0.x86_64.rpmcurl -sSf https://raw.githubusercontent.com/openape-ai/escapes/main/packaging/install.sh | sudo bashDownloads the latest release, verifies SHA256 checksums, and installs with the setuid bit.
Requires Rust 1.70+:
cargo build --release
sudo make installnpm install -g @openape/apesescapes --updateChecks GitHub Releases for a new version, downloads, verifies the checksum, and atomically replaces the binary (preserving setuid root).
curl -sSf https://raw.githubusercontent.com/openape-ai/escapes/main/packaging/macos/uninstall.sh | sudo bashsudo apt remove openape-escapessudo rpm -e openape-escapessudo make uninstall
sudo rm -rf /etc/openape /var/log/openape # optional: remove config + logsConfig lives at /etc/openape/config.toml (permissions 0600, owned by root).
Instead of editing TOML by hand, run:
sudo escapes trust --idp https://id.openape.ai --approvers you@example.comThis validates that the IdP is reachable and its JWKS has at least one signing key, then writes /etc/openape/config.toml with mode 0600. Re-running with new approvers merges them in; pass --replace to overwrite both lists.
Run without flags in a terminal for an interactive prompt:
sudo escapes trust
# IdP URL (e.g. https://id.openape.ai): …
# Approver emails (comma-separated): …Flags:
| Flag | Purpose |
|---|---|
--idp <url> |
Issuer URL to trust |
--approvers <a,b,c> |
Comma-separated approver emails |
--replace |
Overwrite instead of merge |
--skip-validation |
Skip the reachability + JWKS probes (airgapped bootstrap) |
# host = "macmini" # default: system hostname
# run_as = "root" # default: "root"
# audit_log = "/var/log/openape/audit.log" # default
[security]
allowed_issuers = ["https://id.openape.at"] # REQUIRED
allowed_approvers = ["phofmann@delta-mind.at"] # REQUIRED
# allowed_audiences = ["escapes"] # default: ["escapes"]
# [tls]
# ca_bundle = "/etc/ssl/certs/ca-certificates.crt"| Field | Required | Default | Description |
|---|---|---|---|
host |
no | system hostname | Machine identifier for target_host verification |
run_as |
no | "root" |
Default user to execute commands as |
audit_log |
no | /var/log/openape/audit.log |
Path to the JSONL audit log |
security.allowed_issuers |
yes | — | Trusted IdP URLs |
security.allowed_approvers |
yes | — | Identifiers of users who can approve grants |
security.allowed_audiences |
no | ["escapes"] |
Accepted JWT audience values |
tls.ca_bundle |
no | system default | Custom CA bundle path |
Use apes to request, approve, and execute:
# Login to IdP (once per machine)
apes login
# Request grant and execute under root
apes run --as root -- whoami
# With a reason and a longer-lived approval
apes run --as root --approval timed --reason "deploy v2.1" -- systemctl restart nginx
# Non-blocking mode (default): returns exit 75 while the grant is pending,
# the user approves in the browser, then you block once with --wait:
apes run --as root -- apt update
# → Grant pending <id>, approval URL printed
apes grants run <id> --wait
# → blocks internally until approved, then executesWhen apes run sees --as <user>, it switches the audience to escapes, posts the grant to the IdP, waits for approval (either in-process with --wait, or on a follow-up apes grants run --wait), retrieves the JWT, and invokes escapes --grant <jwt> -- <command> which performs the 7-step verification chain and elevates to root.
Or provide the JWT directly to escapes:
escapes --grant <jwt> -- whoami
escapes --grant-file /tmp/grant.jwt -- systemctl restart nginx
echo "$JWT" | escapes --grant-stdin -- apt updateescapes accepts flags only; there are no subcommands.
| Flag | Description |
|---|---|
--config <path> |
Path to config file (default: /etc/openape/config.toml) |
--grant <jwt> |
Grant token JWT (or set ESCAPES_GRANT env var) |
--grant-stdin |
Read the JWT from stdin |
--grant-file <path> |
Read the JWT from a file |
--run-as <user> |
Execute command as this user instead of root |
--update |
Self-update from GitHub Releases |
-- <cmd> [args...] |
Command to execute with elevated privileges |
escapesloads the config (as root)- Resolves the grant JWT from
--grant,--grant-stdin, or--grant-file - Extracts the issuer from the JWT (unverified)
- Checks issuer is in
allowed_issuers - Fetches JWKS from
{issuer}/.well-known/jwks.json - Verifies JWT signature
- Checks
decided_byis inallowed_approvers - Checks
audis inallowed_audiences - Checks
target_hostmatches this machine - Verifies command matches grant (array or
cmd_hash) - Calls IdP consume endpoint (replay protection)
- Elevates to root (or
run_asuser from JWT/config) - Sanitizes environment
- Writes audit log entry
- Replaces process with command via
execvp
Every execution and error is logged in JSONL format. Default location: /var/log/openape/audit.log.
grant_run — command approved and executed:
{"ts":"2026-04-14T10:30:00Z","event":"grant_run","real_uid":1000,"command":["whoami"],"cmd_hash":"ab12...","grant_id":"...","grant_type":"once","agent":"agent@id.openape.at","issuer":"https://id.openape.at","decided_by":"phofmann@delta-mind.at","audience":"escapes","target_host":"macmini","host":"macmini"}error — unexpected failure:
{"ts":"...","event":"error","real_uid":1000,"command":["..."],"host":"macmini","message":"..."}| Code | Meaning |
|---|---|
| 0 | Success (command ran) |
| 1 | Configuration error, HTTP error, I/O error |
| 5 | JWT verification failed or cmd_hash mismatch |
| 126 | Exec failed or privilege elevation error |
| 127 | Command not found |
MIT