Skip to content

openape-ai/escapes

Repository files navigation

escapes — Privilege Elevation via OpenApe Grants

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 onlyescapes receives a pre-approved JWT from apes; no key management, no polling
  • 7-step verification chain before any command runs:
    1. Issuer is in allowed_issuers
    2. JWT signature valid (JWKS)
    3. Approver is in allowed_approvers
    4. Audience is in allowed_audiences
    5. target_host matches this machine
    6. Command / cmd_hash matches
    7. IdP consume check passes (replay protection)
  • Environment is sanitized before exec (LD_PRELOAD, PATH, etc.)
  • Full audit log in JSONL format

Security Model

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.toml defines the trust boundary; only root can modify it

Prerequisites

  • A running OpenApe IdP with grants support — see docs.openape.at
  • @openape/apes CLI — the companion tool that handles login, grant requests, and execution pipeline
  • macOS (aarch64/x86_64) or Linux (amd64/arm64)

Install

macOS (recommended)

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.

Linux (Debian/Ubuntu)

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.deb

Linux (RHEL/Fedora)

curl -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.rpm

Shell installer (all platforms)

curl -sSf https://raw.githubusercontent.com/openape-ai/escapes/main/packaging/install.sh | sudo bash

Downloads the latest release, verifies SHA256 checksums, and installs with the setuid bit.

From source

Requires Rust 1.70+:

cargo build --release
sudo make install

Install apes

npm install -g @openape/apes

Update

escapes --update

Checks GitHub Releases for a new version, downloads, verifies the checksum, and atomically replaces the binary (preserving setuid root).

Uninstall

macOS (.pkg)

curl -sSf https://raw.githubusercontent.com/openape-ai/escapes/main/packaging/macos/uninstall.sh | sudo bash

Linux (.deb)

sudo apt remove openape-escapes

Linux (.rpm)

sudo rpm -e openape-escapes

Manual

sudo make uninstall
sudo rm -rf /etc/openape /var/log/openape  # optional: remove config + logs

Configuration

Config lives at /etc/openape/config.toml (permissions 0600, owned by root).

Quick setup with escapes trust

Instead of editing TOML by hand, run:

sudo escapes trust --idp https://id.openape.ai --approvers you@example.com

This 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)

Hand-written TOML

# 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"

Fields

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

Usage

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 executes

When 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 update

CLI reference

escapes 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

What happens when you run a command

  1. escapes loads the config (as root)
  2. Resolves the grant JWT from --grant, --grant-stdin, or --grant-file
  3. Extracts the issuer from the JWT (unverified)
  4. Checks issuer is in allowed_issuers
  5. Fetches JWKS from {issuer}/.well-known/jwks.json
  6. Verifies JWT signature
  7. Checks decided_by is in allowed_approvers
  8. Checks aud is in allowed_audiences
  9. Checks target_host matches this machine
  10. Verifies command matches grant (array or cmd_hash)
  11. Calls IdP consume endpoint (replay protection)
  12. Elevates to root (or run_as user from JWT/config)
  13. Sanitizes environment
  14. Writes audit log entry
  15. Replaces process with command via execvp

Audit Log

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":"..."}

Exit Codes

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

License

MIT

About

Privilege elevation via the OpenApe grant system (Rust)

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors