Skip to content

akoenig/usher

Repository files navigation

Usher

Local credential gateway for agents and automations.

Usher lets tools call remote APIs without carrying API credentials themselves. It stores OAuth2 refresh tokens and bearer tokens locally, restricts who may call it, resolves the right credential from the target URL, and forwards approved requests through a single /call endpoint.

Agents get the API response they asked for. Credentials stay where they belong: outside prompts, logs, scripts, and model context.

Why Usher Exists

Modern agents are excellent at deciding what to call next. They are much less ideal places to keep long-lived secrets.

Without a broker, credentials tend to leak into places that are hard to audit: shell history, environment dumps, prompt traces, generated code, tool logs, and copied examples. Usher creates a narrow boundary between an agent and the services it needs. The agent asks Usher to call a URL. Usher decides whether the caller is allowed, finds exactly one matching credential, applies authorization, and returns the upstream response.

Usher is headless, self-hosted, and intentionally small. It does not try to understand every provider API. It gives you a secure local path for authenticated HTTP.

How It Works

  1. Install and run the usher daemon locally.
  2. Configure credentials through the interactive CLI.
  3. Give each credential an allowed origin and path prefix.
  4. Send HTTP requests to /call?url=<remote-url>.
  5. Usher matches the target URL, injects the credential, proxies the request, and returns the upstream response.

Credential IDs are for administration. API callers do not pass credential IDs to /call; the target URL determines which credential is used.

Install

Usher is published as @akoenig/usher and exposes the usher binary.

Agent-Assisted Install

Paste this prompt into your AI agent to install Usher:

Install Usher locally for me.

Before installing anything, check whether Node.js is available by running `node --version`. If Node.js is missing, stop and tell me to install the current LTS release from https://nodejs.org/ before continuing.

Prefer `pnpm` for the installation. If `pnpm --version` works, use `pnpm add --global @akoenig/usher`. If pnpm is missing but Corepack is available, run `corepack enable`, then try `pnpm --version` again and use pnpm. If pnpm still is not available, use an npm-compatible global install command that is available on this machine, such as `npm install --global @akoenig/usher`.

After installation, verify the binary by running `usher --help` and report the installed command path if the shell can resolve it.

Manual Install

Install Usher directly with pnpm:

pnpm add --global @akoenig/usher

Any npm-compatible package manager can install the package globally. The examples in this README use pnpm.

Configure

Create Usher's local configuration file:

usher init

The init command writes ~/.config/usher/config.json with 0600 permissions and an inline generated encryption key. It refuses to overwrite an existing non-empty config file.

port is optional and defaults to 3000.

The config file contains the encryption key and must be owned by the process user with 0400 or 0600 permissions. Generate the key once and keep it with the database. Stored credential secrets are encrypted with this key; replacing or deleting it makes existing encrypted credential material unreadable.

Environment variables are optional overrides, not required setup. Available overrides are USHER_DATABASE_PATH, USHER_ENCRYPTION_KEY, USHER_BASE_URL, USHER_ALLOWED_CALLER_IPS, USHER_PORT, USHER_UPSTREAM_TIMEOUT_MILLIS, USHER_MAX_BODY_BYTES, and USHER_AUDIT_RETENTION_DAYS. USHER_ALLOWED_CALLER_IPS is comma-separated when set as an environment variable, for example 127.0.0.1,::1.

upstreamTimeoutMillis (default 30000) bounds how long Usher waits for an upstream response before failing the /call. maxBodyBytes (default 104857600, i.e. 100 MiB) caps both the inbound request body and the upstream response body; oversized inbound requests are rejected with HTTP 413. auditRetentionDays is optional; when set, the daemon prunes audit events older than that many days once per hour. When it is unset, audit events are kept indefinitely.

Run The Daemon

Start Usher in the foreground:

usher daemon start

usher daemon is also valid and starts the same daemon flow.

On Linux systems with user-level systemd, install and start Usher as a durable user service:

usher daemon install

The install command writes a user service named usher.service, reloads the user systemd daemon, enables lingering for the current user, and restarts the service. Re-running usher daemon install is safe and is the supported way to apply a new unit file after an upgrade.

Upgrade

Update the package, then reinstall the service so the running daemon picks up the new binary and any unit file changes:

pnpm add --global @akoenig/usher
usher daemon install

usher daemon install rewrites the systemd unit and restarts the service in one step. If you run the daemon in the foreground instead, stop it and start usher daemon start again after updating.

Upgrades are safe to apply in place: database migrations run automatically at daemon startup, and the config file, encryption key, and stored credentials are untouched.

Configure Credentials With The CLI

Credential administration is intentionally local. Start the daemon first, then use the usher credentials commands from the same machine.

Create a bearer token credential interactively:

usher credentials create-bearer-token

Create an OAuth2 credential interactively:

usher credentials create-oauth2

The create commands prompt for the credential label, allowed request matcher, and secret material. Secrets are collected interactively instead of being passed as normal command-line arguments.

Inspect and manage credentials:

usher credentials list
usher credentials get cred_0123456789abcdef
usher credentials update cred_0123456789abcdef
usher credentials rotate-token cred_0123456789abcdef
usher credentials authorize cred_0123456789abcdef
usher credentials delete cred_0123456789abcdef

update edits a credential's label and allowed request matcher in place, keeping the same credential ID and history. rotate-token replaces the secret of a bearer token credential without recreating it. authorize prints the login URL for an existing OAuth2 credential so you can re-run the authorization flow, for example after a refresh token has been revoked. Completing the login flow again rotates the stored refresh token and granted scopes.

Connect A Bearer Token

Bearer tokens are the fastest path to a working credential.

Run:

usher credentials create-bearer-token

When prompted, provide:

  • A human-readable label, such as Internal API.
  • The allowed origin, such as https://api.example.com.
  • The allowed path prefix, such as /v1/.
  • The bearer token.

After creation, Usher can apply that token to matching /call requests.

Connect An OAuth2 Credential

Run:

usher credentials create-oauth2

The CLI prompts for provider details, client credentials, scopes, and the allowed request matcher. For OAuth2 credentials, Usher returns a login URL. Open that URL in a browser to complete authorization before using the credential for /call.

When using the Google OAuth2 preset, choose the API host and path prefix that match the API you want to call:

Calendar: allowed origin https://www.googleapis.com, path prefix /calendar/
Drive:    allowed origin https://www.googleapis.com, path prefix /drive/
Gmail:    allowed origin https://gmail.googleapis.com, path prefix /gmail/

OAuth refresh tokens are stored encrypted. Access tokens are obtained as needed and are not exposed through CLI output.

Call Remote APIs

Send approved requests through /call with the remote URL encoded in the url query parameter:

curl -sS 'http://localhost:3000/call?url=https%3A%2F%2Fapi.example.com%2Fv1%2Fresource'

Usher resolves the target URL to one credential, applies authorization, forwards the request, and returns the upstream response. A credential with origin https://api.example.com and path prefix /v1/ can authorize calls under that path.

Usher forwards the HTTP method, body, and non-reserved headers, then returns the upstream status, headers, and body as directly as possible. It strips hop-by-hop headers along with the caller's Host, Content-Length, Accept-Encoding, and Expect headers, applies a configurable upstream timeout, and does not follow upstream redirects: a 3xx response is returned to the caller unchanged so a redirect can never send a credential to an origin that was not approved. Requests and responses larger than maxBodyBytes are rejected.

Example POST request:

curl -sS \
  -X POST \
  -H 'content-type: application/json' \
  --data '{"name":"Ada"}' \
  'http://localhost:3000/call?url=https%3A%2F%2Fapi.example.com%2Fv1%2Fusers'

Read Audit Events

Usher records audit events for local inspection. Read the latest 10 events:

usher events

Request a larger tail or follow new events as they arrive:

usher events -n 50
usher events -f
usher events -n 50 -f

Filter events by the credential they matched or by outcome:

usher events --credential cred_0123456789abcdef
usher events --outcome denied
usher events --outcome error -f

Each line starts with the event name, such as OutboundCallCompleted, followed by the request outcome and metadata.

Health Check

Usher exposes an unauthenticated liveness endpoint for supervisors and monitoring:

curl -sS http://localhost:3000/health

It returns {"status":"ok"} with HTTP 200 when the daemon is running. The endpoint is not restricted by allowedCallerIps and never touches credential material.

Backup And Restore

Usher's state is two things: the SQLite database (encrypted credential material and audit events) and the encryption key embedded in ~/.config/usher/config.json. Both are required to read stored secrets; the database alone is useless without the key, and the key alone cannot reconstruct credentials.

Back up both together. Stop the daemon (or rely on WAL-mode consistency) and copy the files:

cp ~/.config/usher/usher.sqlite ~/.config/usher/usher.sqlite.backup
cp ~/.config/usher/config.json ~/.config/usher/config.json.backup

For a consistent online snapshot of the database, prefer SQLite's backup command:

sqlite3 ~/.config/usher/usher.sqlite ".backup '/path/to/usher.sqlite.backup'"

To restore, stop the daemon, put both files back at their original paths with 0600 permissions (the config file must be owned by the daemon user with 0400 or 0600), and start the daemon again. Keep backups of the config file as secure as the key itself: anyone with the key and the database can decrypt every stored credential.

Safety Model

Usher is designed to make the secure path the simple path.

  • Credential administration is local to the daemon.
  • Admin credential endpoints are local administration paths.
  • /call is restricted by allowedCallerIps from the config file or USHER_ALLOWED_CALLER_IPS.
  • Stored credential secrets are encrypted with the configured key.
  • Credential secrets are redacted from list, get, and create output.
  • Overlapping allowed request matchers are rejected so a target URL resolves to at most one credential.
  • Callers do not need to know credential IDs to call remote APIs.

Quick Reference

Common CLI commands:

usher init
usher daemon start
usher daemon install
usher credentials create-bearer-token
usher credentials create-oauth2
usher credentials list
usher credentials get cred_0123456789abcdef
usher credentials update cred_0123456789abcdef
usher credentials rotate-token cred_0123456789abcdef
usher credentials authorize cred_0123456789abcdef
usher credentials delete cred_0123456789abcdef
usher events
usher events -n 50 -f
usher events --credential cred_0123456789abcdef --outcome denied

Endpoint quick reference:

GET    /health
GET    /credentials
POST   /credentials
GET    /credentials/{credentialId}
PATCH  /credentials/{credentialId}
DELETE /credentials/{credentialId}
GET    /credentials/{credentialId}/oauth2/login
GET    /oauth2/callback
GET    /events
<any>  /call?url=<absolute-https-target-url>

Configuration file:

~/.config/usher/config.json

Required JSON fields:

databasePath
encryptionKey
baseUrl
allowedCallerIps

Optional JSON fields:

port=3000
upstreamTimeoutMillis=30000
maxBodyBytes=104857600
auditRetentionDays  (unset means keep audit events indefinitely)

Example:

{
  "databasePath": "/home/alice/.config/usher/usher.sqlite",
  "encryptionKey": "base64url:<32-byte random key encoded as base64url>",
  "baseUrl": "http://localhost:3000",
  "allowedCallerIps": ["127.0.0.1", "::1"],
  "port": 3000,
  "upstreamTimeoutMillis": 30000,
  "maxBodyBytes": 104857600,
  "auditRetentionDays": 90
}

Replace /home/alice with your home directory.

Optional environment overrides:

USHER_DATABASE_PATH
USHER_ENCRYPTION_KEY
USHER_BASE_URL
USHER_ALLOWED_CALLER_IPS
USHER_PORT
USHER_UPSTREAM_TIMEOUT_MILLIS
USHER_MAX_BODY_BYTES
USHER_AUDIT_RETENTION_DAYS

Source Development

Install dependencies from a source checkout:

vp install

Run the daemon from source:

vp run dev daemon

Build and run the compiled daemon:

vp run build
node dist/Main.mjs daemon start

About

Self-hosted credential broker for authenticated HTTP calls.

Resources

License

Stars

Watchers

Forks

Contributors