sigil
Secrets an agent can use without ever seeing them.
macOS · Apple siliconLinux · x86_64
sigil is a local credential CLI over your platform's own secret store: the macOS Keychain, or the Secret Service on Linux. Its core move is inject-at-fire. A secret goes into one child process's environment, so it never lands in argv, shell history, an agent's transcript, or a file.
sigil run GROK_API_KEY -- ./my-bot
Install
curl -fsSL https://sigil-cli.daystra.com/install.sh | sh
Installs to ~/.local/bin/sigil. The script checks the SHA-256 from the release manifest. On a platform with no published build it says so and stops.
| Platform | Build | Updates |
|---|---|---|
| macOS arm64 | Developer ID signed and notarized. The installer checks the signature against the release pin. | sigil update |
| Linux x86_64 | glibc ≥ 2.28. SHA-256 plus an Ed25519 release signature, checked with openssl against a pinned key (install.sh; compare the key with the source README). Verified on OpenSSL 3: Debian 12+, Ubuntu 22.04+, RHEL 9+, Arch. Older glibc-2.28 distros (Debian 10/11, Ubuntu 20.04, RHEL 8) ship OpenSSL 1.1.1 and are untested: if the check cannot run, the installer refuses with a message rather than install unchecked. | Re-run the installer |
| Intel macOS · Linux arm64 · Windows | Not published yet. Build from source. | — |
Use
sigil set OPENAI_API_KEY # value read from a hidden prompt or stdin, never argv
sigil run OPENAI_API_KEY -- ./job # injected into ./job's environment only
sigil list # names, never values
sigil config can-resolve --json NAME # will a background job resolve this? no prompt
Platforms are not equal, and we say so
macOS: reading a value requires a Touch ID tap, unless you deliberately arm that name for unattended use. Signed grants and owner binding (sigil owner) are macOS-only.
Linux: the gate is a [y/N] confirmation on the terminal. It shows that something is driving the terminal, and it does not prove who that is. The default backend needs secret-tool (libsecret-tools) and a running gnome-keyring or kwallet.
Bots, CI, headless boxes: on a real store, arm names once from a terminal with sigil config arm-unattended NAME. With no Secret Service at all, use sigil --backend env together with SIGIL_ASSUME_YES=1. That backend is a plaintext secrets.env, so treat it as a convenience and not a vault. In CI, prefer the platform's own secret injection; the env backend is for headless boxes that have nothing better. When a non-terminal process asks for an unarmed name, sigil refuses and names it rather than hanging.
Where the boundary actually is
Hiding a value is not the same as stopping an agent from leaking it. Keeping a secret out of the agent's context holds for a cooperative agent. An agent that can pick the command can still leak it: sigil run API_KEY -- sh -c env prints the value. The real boundary is which program may receive the secret.
Owner binding (macOS) is that boundary. sigil owner confirm NAME -- /path/to/bin args pins a name to a code-signed executable and its exact argv, env and cwd, with one Touch ID tap. Every other command gets exit 7 OWNER_MISMATCH, and a changed binary gets 8 OWNER_CHANGED. A name that is armed but not bound is released to any command: that is convenience, not containment.
The environment is not a vault. While the child runs, any process of the same user can read its environment (ps eww, /proc/<pid>/environ). That is inherent to env injection, not specific to sigil.
Secrets stay on the machine that does the work. Coordination between machines carries intent, never values. Each host resolves its own credentials locally.
For agents
- /llms.txt: when and how an agent should reach for sigil, with concrete calls.
- /latest.txt: the release manifest (
key=value). - /.well-known/sigil-cli.json: name, install line, platforms, exit codes.
- /install.sh: the installer source. Read it before you pipe it to a shell.