# sigil > sigil is a local credential CLI over the platform's own secret store (macOS Keychain, or the Freedesktop Secret Service on Linux) with inject-at-fire: a secret goes into ONE child process's environment and never touches argv, shell history, the transcript, or a file. Reads are gated by an approval (Touch ID on macOS, terminal confirmation on Linux) unless a name has been deliberately armed for unattended use. sigil is how an agent uses a credential without seeing it. Instead of pasting a token, echoing `$VAR`, or `$(…)`-substituting a secret onto a command line, the agent runs `sigil run NAME -- cmd` and the value exists only inside `cmd`'s environment. ## Install - One line: `curl -fsSL https://sigil-cli.daystra.com/install.sh | sh` — installs to `~/.local/bin/sigil` (override with `SIGIL_INSTALL_DIR`). It reads the same `latest.txt` manifest `sigil update` uses, verifies SHA-256, and on an unshipped platform exits with a message instead of downloading nothing. - **macOS arm64** — Developer ID signed + notarized. The installer also verifies the code signature against the release pin (`anchor apple generic and certificate leaf[subject.OU] = RY99V66U84`). Afterwards `sigil update` self-updates with the same check. - **Linux x86_64** — glibc 2.28 or newer (Debian 10+, Ubuntu 20.04+, RHEL 8+, Arch). Integrity is SHA-256 plus an Ed25519 release signature (`sig_linux_x86_64` in latest.txt), verified by install.sh with `openssl` against a pinned key, and it fails closed (`SIGIL_INSTALL_SKIP_SIGNATURE=1` skips it loudly). Verified on OpenSSL 3 (Debian 12+, Ubuntu 22.04+, RHEL 9+, Arch). OpenSSL 1.1.1 distros (Debian 10/11, Ubuntu 20.04, RHEL 8) are untested for the signature check. `sigil update` is macOS-only, so to update on Linux you re-run the installer. The default backend needs `secret-tool` (Debian/Ubuntu: `libsecret-tools`) and a running Secret Service (gnome-keyring or kwallet). - Not shipped yet: Intel macOS, Linux arm64, Windows. Build from source (`cargo build --release`). ## Platform truth (say this to users; do not paper over it) - The Linux gate is a `[y/N]` confirmation on the terminal. It shows that something is driving the tty, and it does not prove identity. It is weaker than Touch ID, and the docs do not claim parity. - Signed grants and owner binding (`sigil owner …`, `sigil anchor …`) are macOS only. - **Headless / bot / CI with no Secret Service:** use the env backend: `sigil --backend env …` (or `SIGIL_VAULT_BACKEND=env`). `SIGIL_ASSUME_YES=1` skips the prompt **only** on the env backend. The env backend stores values in a plaintext `secrets.env` in the working directory, so it is a convenience and not a vault. In CI, prefer the CI platform's own ephemeral secret injection; use the env backend on headless boxes that have nothing better. - **Unattended on a real store:** a human arms names once from a terminal with `sigil config arm-unattended NAME…`. Background processes can then `run` them. Unarmed names in a non-tty REFUSE with a message naming each name, and never hang on a prompt. - Values are per-machine. There is no sync. A new machine starts empty; `sigil export` / `sigil import` move a passphrase-encrypted bundle. ## The boundary (read before claiming "the agent can't see it") - `run` keeps the value out of the agent's context. It does NOT stop an agent that chooses the command: `sigil run KEY -- sh -c env` prints it. "Use without seeing" holds for a cooperative agent, not as an enforcement guarantee. - The enforcement is **owner binding (macOS)**. `sigil owner confirm NAME -- /abs/bin args…` pins NAME to a code-signed executable + exact argv/env/cwd (one Touch ID tap). Any other caller gets exit 7 OWNER_MISMATCH, and a changed binary gets 8 OWNER_CHANGED. An armed-but-unbound name releases to any command. - Injected values are readable by same-user processes while the child runs (`ps eww`, `/proc//environ`). That is inherent to env injection. - Cross-machine: transport intent, never values. Each host resolves its own secrets locally. - Audit: approval events (grants, owner confirms, anchor init, disarm) go to a hash-chained `~/.sigil/grants.log`, values never. Individual reads are deliberately NOT logged. ## CLI commands - `sigil set NAME`: the value is read from stdin, never from argv (`printf %s "$v" | sigil set NAME`, or the hidden tty prompt). - `sigil run NAMES… -- CMD [ARGS…]`: injects the named secrets into CMD's environment. Prefer this over `get`. - `sigil get NAME`: gated; prints the value to stdout. Avoid it in agent sessions, because stdout lands in the transcript. - `sigil list`: names only, no values, no gate. - `sigil config can-resolve [--json] NAME…`: answers "will these resolve unattended?" with no prompt and no values. - `sigil config status [--json]`: armed names and expiry. - `sigil config arm-unattended NAME… [--ttl-secs N]` / `sigil config disarm NAME…` - `sigil owner confirm NAME… [--env K=V] [--cwd DIR] -- /abs/bin ARGS…` / `sigil owner list` / `sigil owner revoke NAME…` (macOS): bind names to one signed executable. - `sigil export FILE` / `sigil import FILE` / `sigil reindex` / `sigil update [--check]` - `sigil --backend env `: the file backend for CI. ## When to use sigil (concrete agent calls) - Call an API with a token. The agent runs `sigil run GROK_API_KEY -- sh -c 'curl -s -H "Authorization: Bearer $GROK_API_KEY" https://api.x.ai/v1/models'`. The token never appears in argv or in the transcript. - Store a credential the user hands over. Have the user run `sigil set NAME` themselves and type it at the hidden prompt, so the value never passes through the agent. - Check before a background job: `sigil config can-resolve --json NAME`. Exit 0 means it will resolve unattended. Nonzero means it would refuse, and the output names the arm command. - Inside a CI or headless bot: `SIGIL_ASSUME_YES=1 sigil --backend env run NAME -- ./job`. - When something looks stuck: unarmed names in a non-tty refuse rather than hang. A hang means a tty prompt is waiting for a human. ## Exit codes 0 ok · 1 error or refusal (the message names the fix) · 7 OWNER_MISMATCH (macOS owner binding: the caller is not the bound owner) · 8 OWNER_CHANGED (the owner binary changed since it was confirmed). ## Machine-readable - Manifest: https://sigil-cli.daystra.com/latest.txt, made of flat `key=value` lines: `version`, `published`, `url_arm64`/`sha256_arm64` (macOS), and `url_linux_x86_64`/`sha256_linux_x86_64`. - Installer: https://sigil-cli.daystra.com/install.sh - Agent manifest: https://sigil-cli.daystra.com/.well-known/sigil-cli.json A Cybercussion Interactive, LLC project — https://cybercussion.com