berthdocs

Guides

Secrets with 1Password

Name a secret in a project's environment instead of holding it, and let each box read it when needed.

An env value in .berth/config.json, a kit or a box's own config can name a secret instead of holding it, so the file, or the kit, can be shared, even publicly, without one in it:

.berth/config.json
{
  "env": {
    "DATABASE_PASSWORD": "op://dev/cal-db/password",
    "STRIPE_SECRET_KEY": "op://dev/Stripe test/secret key",
    "OPENAI_API_KEY": "env://OPENAI_API_KEY"
  }
}

References

  • op://vault/item/field (or op://vault/item/section/field, with ?attribute=otp and the like) is 1Password's own reference syntax, read with the 1Password CLI on the box: op read.
  • env://NAME passes on a variable from berthd's own environment, under whatever name the project wants.

Setting up 1Password on a box

  1. Install op on the box. berthd finds it on its PATH, in ~/.local/bin, /opt/homebrew/bin or /usr/local/bin, or wherever $BERTH_OP says.
  2. Sign it in. With a service account, set OP_SERVICE_ACCOUNT_TOKEN in berthd's own environment or in the box's ~/.berth/env.json (which every worktree also gets, so berthd's environment is the tighter place). Otherwise berthd uses the box user's own signed-in op.
  3. Check a reference:
berth secret test devl op://dev/cal-db/password   # Resolved op://dev/cal-db/password: 24 characters

Or from the app: Project settings → Environment → Test. Either way the box reports only whether it could read it and how long the value is.

How values are handled

The box reads a reference when it builds a worktree's environment for a hook, a flow, a setup script or exec, and keeps the value in memory for five minutes. It never writes a value to disk or puts one in a log, an event, the API or an error: config, GET /v1/locations/{name}/config and GET /v1/env show the reference. A value is not expanded, so a $ in a password stays a $.

Terminals, agents and services don't get values from berthd at all: tmux takes a session's environment as command-line arguments, which other processes can read, and a service's unit file is on disk and restarted by the service manager without berthd. So they get the references, and their program runs behind berthd secret exec, which resolves them in its own memory and then replaces itself with the shell or agent. It prints which variables it could not set, and reports to the box which resolved and which failed (never a value), so the box can announce failures. For a service, env:// reads the service manager's environment. Each start reads op afresh, so a service that keeps crashing reads it on every restart.

When a reference fails

If a reference cannot be read (no op, not signed in, no such item, a read that takes over 15 seconds), what needs it still starts, without that variable, and the box sends a secret.failed event with the variable, the reference and the reason, which the app shows as a notification. A failure is remembered for 30 seconds, so a broken reference does not run op for every hook. When it reads again, the box sends secret.resolved.

A kit that uses op:// should list { "tool": "op" } in requires.

On this page