Registries and trust
Know where stacks finds each stack and fragment, which sources it trusts, how the signed index is verified, and what changes for an untrusted entry.
# Registries and trust > Know where stacks finds each stack and fragment, which sources it trusts, how the signed index is verified, and what changes for an untrusted entry. stacks reads entries from four places, called origins, and trusts entries from some origins and not others. Trust decides whether stacks hands an entry to an agent without asking you first. ## How stacks decides trust stacks trusts an entry by its origin and doesn't inspect what the entry says. Entries from places you or the stacks maintainers control are trusted, and any other entry needs a person to review it first. A stack's guidance becomes your agent's instructions and its verify steps run as shell commands on your machine, so an entry shipped inside a repository you just cloned could tell the agent to do anything. ## Origins and lookup order When stacks looks up an id, it checks the origins in this order and uses the first one that has it: | Origin | Where | Trusted | | --- | --- | --- | | `project` | `.stacks/stacks/` and `.stacks/fragments/` in the current directory | No. A cloned repository can ship these | | `user` | `~/.config/stacks/` (or `$XDG_CONFIG_HOME/stacks/`), beside `config.json` | Yes | | `index` | The signed registry index from stacks.steamfield.io, verified and cached locally | Yes, for the default index. No, for a custom `STACKS_INDEX_URL` | | `bundled` | The registry shipped inside the CLI package | Yes, unless `STACKS_REGISTRY_DIR` replaced it | `stacks show` prints an entry's origin and trust on its first line: ```sh title="Terminal" group="show" stacks show go-cli ``` ```text title="Output" group="show" Go CLI (go-cli), stack, origin bundled, trusted A starter for a terminal program written in the Go language, with settings and clear error messages. ... Shadows: none ... ``` A higher origin's copy hides the lower ones. `list --json` marks the winning copy's `shadows` with the origins it hides, and `show` prints a `Shadows:` line. An entry that fails to load is listed under `rejected` with a one-line error, and stacks never silently falls back to a lower copy of it. An entry written for a newer `schema_version` than your CLI reads is the exception: it's skipped with a warning, and the next origin's copy is used. Your own stacks and fragments belong in the user origin. `stacks create stack my-api` starts one there, and `stacks create stack go-cli --from bundled` copies a bundled entry there to edit, after which your copy shadows the original. ## The signed index The index is how new and updated entries reach you without a CLI release. `list`, `show`, `new` and `add` read it from the hosted API at https://stacks.steamfield.io, refreshing it when the cached copy is stale. `search` ranks the cached copy and never refreshes it. The [directory](https://stacks.steamfield.io/stacks/) is built from the same index. Before using anything from it, the CLI checks: 1. The index envelope's Ed25519 signature against the public keys compiled into the CLI. A signature that doesn't verify rejects the whole index. 2. The index format version and the minimum CLI version it declares. An index your CLI is too old to read is set aside, and the last verified index or the bundled registry is used instead. 3. Each entry bundle's `sha256` digest against the digest the signed index advertises. A bundle that doesn't match is rejected on its own, without hiding unrelated entries. Fetched content is only read and written to disk, never executed by the CLI. An index entry whose digest matches a bundled one keeps its bundled identity. An index stack names the exact fragment digests it was proven with; when that composition isn't available, the bundled stack and fragments are used instead. ### Caching and offline use The verified index lives under `~/.cache/stacks/index/` (`$XDG_CACHE_HOME/stacks/index/`): the signed envelope in `index.json`, request state in `state.json`, and extracted bundles under `entries/<digest>/`. Cached files are verified again before every use, offline too. | Situation | What happens | | --- | --- | | Cached index younger than 24 hours | Used without a request | | Refresh needed | One request with a 1,500 ms deadline | | Request fails | The last verified index is kept, or the bundled registry is used. The next attempt waits ten minutes | | Server answers 404 | The next attempt waits 24 hours | | `--offline` or `STACKS_OFFLINE=1` | No request. Verified cached entries stay available | `stacks verify` never touches the index. `stacks doctor --json` reports its state under `remoteIndex` without contacting the server: the URL, `isDefaultUrl` (whether that URL is the default, signature-verified index), the offline flag, when it was cached, the last attempt and its outcome, and any backoff deadline. ### Using a custom index `STACKS_INDEX_URL` points the CLI at a different index, such as one your team hosts. It needs HTTPS, except for plain HTTP on `localhost` or `127.0.0.1`. An invalid URL is never fetched. Entries from a custom index are untrusted, because the CLI's keys only cover the default index. Digests are still checked, so a bundle can't change underneath its index, but nothing vouches for the index itself. Setting `STACKS_INDEX_URL` also turns off the published stats in `list` and `show`, since those numbers describe the default registry. See [Configuration](https://stacks.steamfield.io/docs/reference/configuration.md#environment-variables). ## What an untrusted entry changes You can still show, compose and build from an untrusted entry, but these steps need a person: | Where | Trusted entry | Untrusted entry | | --- | --- | --- | | `stacks new` / `stacks add` handoff | Can launch an agent | Never launches an agent. Under an agent, the printed next step says `STOP`: show the user the entries and the brief's first section, and continue only if they confirm | | `stacks add` under an agent or without a terminal | Records the new steps' digest | Writes the new steps but keeps the previous digest, so verify reports `changed` until a person runs `stacks verify --accept-changes` | | `stacks create --from` | Copies into your user registry | Needs a person at a terminal to read the full trust report. Agents are refused with `PermissionDenied` (exit 6) | | `stacks search` | Ranked normally | Never ranked `strong`, and carries an `untrusted` penalty | | The manifest | `"trusted": true` | `"trusted": false`, so the record stays with the project | > [!WARNING] > Promoting a project entry into your user registry makes it trusted everywhere on your machine. Read the full trust report before you confirm, including when you pass `--yes`. ## Related - [How stacks works](https://stacks.steamfield.io/docs/concepts.md): what stacks and fragments are. - [Write your own stack or fragment](https://stacks.steamfield.io/docs/guides/authoring.md): build an entry in your user registry. - [How search ranks entries](https://stacks.steamfield.io/docs/reference/search.md): how origin and trust affect ranking.View as MarkdownOpen in ClaudeOpen in ChatGPT
stacks reads entries from four places, called origins, and trusts entries from some origins and not others. Trust decides whether stacks hands an entry to an agent without asking you first.
How stacks decides trust
stacks trusts an entry by its origin and doesn't inspect what the entry says. Entries from places you or the stacks maintainers control are trusted, and any other entry needs a person to review it first. A stack's guidance becomes your agent's instructions and its verify steps run as shell commands on your machine, so an entry shipped inside a repository you just cloned could tell the agent to do anything.
Origins and lookup order
When stacks looks up an id, it checks the origins in this order and uses the first one that has it:
| Origin | Where | Trusted |
|---|---|---|
project |
.stacks/stacks/ and .stacks/fragments/ in the current directory |
No. A cloned repository can ship these |
user |
~/.config/stacks/ (or $XDG_CONFIG_HOME/stacks/), beside config.json |
Yes |
index |
The signed registry index from stacks.steamfield.io, verified and cached locally | Yes, for the default index. No, for a custom STACKS_INDEX_URL |
bundled |
The registry shipped inside the CLI package | Yes, unless STACKS_REGISTRY_DIR replaced it |
stacks show prints an entry's origin and trust on its first line:
stacks show go-cliGo CLI (go-cli), stack, origin bundled, trusted
A starter for a terminal program written in the Go language, with settings and clear error messages.
...
Shadows: none
...A higher origin's copy hides the lower ones. list --json marks the winning copy's shadows with the origins it hides, and show prints a Shadows: line. An entry that fails to load is listed under rejected with a one-line error, and stacks never silently falls back to a lower copy of it. An entry written for a newer schema_version than your CLI reads is the exception: it's skipped with a warning, and the next origin's copy is used.
Your own stacks and fragments belong in the user origin. stacks create stack my-api starts one there, and stacks create stack go-cli --from bundled copies a bundled entry there to edit, after which your copy shadows the original.
The signed index
The index is how new and updated entries reach you without a CLI release. list, show, new and add read it from the hosted API at https://stacks.steamfield.io, refreshing it when the cached copy is stale. search ranks the cached copy and never refreshes it. The directory is built from the same index.
Before using anything from it, the CLI checks:
- The index envelope's Ed25519 signature against the public keys compiled into the CLI. A signature that doesn't verify rejects the whole index.
- The index format version and the minimum CLI version it declares. An index your CLI is too old to read is set aside, and the last verified index or the bundled registry is used instead.
- Each entry bundle's
sha256digest against the digest the signed index advertises. A bundle that doesn't match is rejected on its own, without hiding unrelated entries.
Fetched content is only read and written to disk, never executed by the CLI. An index entry whose digest matches a bundled one keeps its bundled identity. An index stack names the exact fragment digests it was proven with; when that composition isn't available, the bundled stack and fragments are used instead.
Caching and offline use
The verified index lives under ~/.cache/stacks/index/ ($XDG_CACHE_HOME/stacks/index/): the signed envelope in index.json, request state in state.json, and extracted bundles under entries/<digest>/. Cached files are verified again before every use, offline too.
| Situation | What happens |
|---|---|
| Cached index younger than 24 hours | Used without a request |
| Refresh needed | One request with a 1,500 ms deadline |
| Request fails | The last verified index is kept, or the bundled registry is used. The next attempt waits ten minutes |
| Server answers 404 | The next attempt waits 24 hours |
--offline or STACKS_OFFLINE=1 |
No request. Verified cached entries stay available |
stacks verify never touches the index. stacks doctor --json reports its state under remoteIndex without contacting the server: the URL, isDefaultUrl (whether that URL is the default, signature-verified index), the offline flag, when it was cached, the last attempt and its outcome, and any backoff deadline.
Using a custom index
STACKS_INDEX_URL points the CLI at a different index, such as one your team hosts. It needs HTTPS, except for plain HTTP on localhost or 127.0.0.1. An invalid URL is never fetched.
Entries from a custom index are untrusted, because the CLI's keys only cover the default index. Digests are still checked, so a bundle can't change underneath its index, but nothing vouches for the index itself. Setting STACKS_INDEX_URL also turns off the published stats in list and show, since those numbers describe the default registry. See Configuration.
What an untrusted entry changes
You can still show, compose and build from an untrusted entry, but these steps need a person:
| Where | Trusted entry | Untrusted entry |
|---|---|---|
stacks new / stacks add handoff |
Can launch an agent | Never launches an agent. Under an agent, the printed next step says STOP: show the user the entries and the brief's first section, and continue only if they confirm |
stacks add under an agent or without a terminal |
Records the new steps' digest | Writes the new steps but keeps the previous digest, so verify reports changed until a person runs stacks verify --accept-changes |
stacks create --from |
Copies into your user registry | Needs a person at a terminal to read the full trust report. Agents are refused with PermissionDenied (exit 6) |
stacks search |
Ranked normally | Never ranked strong, and carries an untrusted penalty |
| The manifest | "trusted": true |
"trusted": false, so the record stays with the project |
Warning
Promoting a project entry into your user registry makes it trusted everywhere on your machine. Read the full trust report before you confirm, including when you pass
--yes.
Related
- How stacks works: what stacks and fragments are.
- Write your own stack or fragment: build an entry in your user registry.
- How search ranks entries: how origin and trust affect ranking.