Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ editing Stella's own code should know what lives where:
| `.stella/memories/*.md` | Durable lessons baked into the byte-stable system prompt prefix. Sorted by filename, loaded once per session. (Write side: the `save_memory` tool.) |
| `.stella/skills/<slug>/SKILL.md` | Auto-promoted skills from recurring reflection lessons. Never enforced — selected and injected as volatile context. |
| `.stella/tools/*.toml` | Developer-defined custom script tools. Also scanned at `~/.stella/tools/`. |
| `.stella/settings.json` | Project-scope provider config (overrides built-ins or defines new providers) and tool switches (`tools.bash: "on"` opts the shell tool in — it is off by default in every scope). Merged per-field with org-managed and user scopes. |
| `.stella/settings.json` | Project-scope provider config (overrides built-ins or defines new providers) and tool switches (`tools.bash: "off"` withholds the shell tool — every built-in, the shell included, is registered by default since #710). Merged per-field with org-managed and user scopes. |
| `.stella/mcp.toml` | MCP server config — extra tools merged into the registry at session start. |
| `.stella/domains.toml` | Domain taxonomy for memory/reflection tagging, inferred by `stella init`. |
| `.stella/workspace.json` | Durable per-workspace telemetry identity (`workspace_id`), written by `stella cloud register`. Deliberately **outside** `private/` and safe to commit — sharing it makes every clone/machine report under one `workspace_id` to a cloud org. |
Expand Down
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ are also accepted case-insensitively.
| ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `read_file` · `write_file` · `edit_file` · `delete_file` | File CRUD with surgical exact-substring edits |
| `apply_edits` | One transactional batch of exact-substring edits across many files — every edit validates first, and if any fails nothing is written (`dry_run` validates without writing) |
| `bash` | Run a shell command (timeout kill; `trace: true` echoes each line) — **off by default**, registered only with `"tools": {"bash": "on"}` in settings (any scope) |
| `bash` | Run a shell command (timeout kill; `trace: true` echoes each line) — **registered by default**, withheld with `"tools": {"bash": "off"}` in settings (any scope) |
| `grep` · `glob` | Regex content search (ripgrep) · glob file discovery (fd) |
| `graph_query` | Query the indexed code graph: symbol definitions/references, file imports/importers/neighborhood — auto-built at session start, refreshed live |
| `read_symbol` | Read a named symbol's exact source span, resolved through the code graph — no line-offset guessing; multiple definitions are listed, never silently picked |
Expand Down Expand Up @@ -465,11 +465,17 @@ All file tools are workspace-root-pinned, and every read/write/edit/delete is
recorded in the Files-Touched ledger (shown per turn as `[C·R·U·D] path`, also
via `/files`).

**Bash is opt-in.** The default tool surface has no _free-form shell tool_: the
model works through enumerable-argv tools (build/test/lint/format,
`run_script`'s project-declared verbs, the process group, the `repo_*` tools).
Enable `bash` per user, org, or project by adding `"tools": {"bash": "on"}` to
the corresponding `settings.json` scope (normal per-field merge — project wins).
**Bash ships on, and switching it off bounds the shell tool rather than every
path to a shell.** `bash` is registered like every other built-in; withhold it
per user, org, or project by adding `"tools": {"bash": "off"}` to the
corresponding `settings.json` scope (normal per-field merge — project wins).
Prefer the enumerable-argv tools regardless (build/test/lint/format,
`run_script`'s project-declared verbs, the process group, the `repo_*` tools) —
they never interpret a shell string. Note that `"bash": "off"` removes the
free-form shell _tool_, not the shell _capability_: `build_project` and
`run_tests` take a `command` override, `verify_done` a `test_cmd`, and
`run_script` composes from the scripts index, so all four still reach `bash -c`
behind the `command.started` policy fence.

Stated precisely, because a security claim that overreaches is worse than none:
turning `bash` off removes the tool, not every route to a shell.
Expand Down
8 changes: 4 additions & 4 deletions docs/design/semantic-resolution-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ strategy (`stella-mcp/tests/`).
which spends the memory whether or not a resolution query ever arrives.
- **Invariant fit:** clean on "ports, not concretions" (it *is* an adapter
behind a trait; `stella-core` never sees it); hostile to dependency-light /
no-daemon unless opt-in gated exactly like the shell and web tools
(`tools.bash: "on"` — off by default in every scope, `AGENTS.md`;
`stella-tools/src/bash.rs:1-12` "Opt-in, never ambient";
`stella-cli/src/settings.rs:552` for `web`) and registered conditionally
no-daemon unless gated behind a tool switch like the shell and web tools
(`tools.bash: "off"` withholds the shell — registered by default in every
scope since #710, `AGENTS.md`; `stella-tools/src/bash.rs:1-12`) and
registered conditionally
like `graph_query` (no server, no schema burning tokens).
- **Rough size:** LSP client core (initialize handshake, capability
negotiation, document sync, request correlation) plus lifecycle supervision,
Expand Down
21 changes: 15 additions & 6 deletions docs/design/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@ so managed denial survives explicit repository trust — the witness is
The agent's tool surface is the blast radius of a successful A2. Two
sub-boundaries matter and they are frequently conflated:

- **`bash` is opt-in** (`tools.bash: "on"`), and its module doc is explicit
that the default surface has no shell at all.
- **`bash` ships registered** and is withheld with `tools.bash: "off"`. #710
moved every built-in to on-by-default with a switch, because the previous
posture covered built-ins only and most operators never found the switch.
Assume the default surface HAS a shell.
- **The default surface is nonetheless not execution-free.** `run_script`,
`start_process`, `repo_commit`/`repo_push`, and workspace custom tools all
execute code without `bash` ever being enabled. `start_process` spawns argv
Expand Down Expand Up @@ -245,10 +247,17 @@ mitigation therefore has a shape the threat does not.

### R4 — No SSRF guard on web tools, by design

An opted-in session can fetch any http(s) URL the host can reach, including
`localhost` and cloud metadata endpoints. This is a documented decision: it
matches the `bash` opt-in and is required for the "fetch my internal dev
server" use case. The gate is the settings opt-in, not a network allowlist.
Any session can fetch any http(s) URL the host can reach, including `localhost`
and cloud metadata endpoints. It is required for the "fetch my internal dev
server" use case, and there is no network allowlist.

**The compensating control this decision rested on is gone.** The exposure was
accepted because the web family was opt-in, so reaching a metadata endpoint
took a deliberate host action. Since #710 the three key-free web tools are
registered by default and the only gate is an operator who knows to set
`"web": "off"`. Whether that remains acceptable, or whether the default surface
needs a loopback/metadata denylist, is an open ruling (#615) — R4 should not be
read as a still-current risk acceptance.

One sharp edge is recorded in `web.rs`: reqwest strips `Cookie` and
`Authorization` across a cross-host redirect, but a secret placed in a custom
Expand Down
2 changes: 1 addition & 1 deletion docs/why-stella.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ plane.
| **BYOK, model-agnostic** | Nine hosted providers (Anthropic, OpenAI, Gemini, xAI, DeepSeek, Z.ai, OpenRouter, Vertex, Bedrock) plus **any** OpenAI-compatible local server (Ollama, vLLM, LM Studio, llama.cpp). No account, no gateway. Pin per run with `--model provider/id`. |
| **Zero telemetry egress by default** | Community/default Stella sends no telemetry anywhere. Executions, the full event stream, per-call token/cost telemetry, and a `[C·R·U·D] path` files-touched ledger land in a local `.stella/private/store.db` you can open with any SQLite client — and the store is never a dependency of a turn. The sole exception is an [explicitly enrolled Oxagen Enterprise managed deployment](https://stella.oxagen.sh/docs/telemetry#oxagen-enterprise-managed-export): a current signed policy may authorize one minimal content-free operational rollup to one exact allowlisted HTTPS sink. |
| **Budget you can trust** | `--budget <usd>` aborts cleanly **between** steps, never mid-tool, so a cap can't corrupt a half-written edit. |
| **Bounded blast radius** | File tools are workspace-root-pinned; the `bash` tool is **off by default** (settings `tools.bash: "on"` to opt in — the default surface is enumerable argv, no shell); an opt-in `bash` sandbox (Seatbelt / bubblewrap) contains prompt-injection damage and **fails closed**; a cloned repo's own hooks never auto-execute (`STELLA_PROJECT_HOOKS=1` to opt in). |
| **Bounded blast radius** | File tools are workspace-root-pinned; the `bash` tool is **registered by default** and withheld with settings `tools.bash: "off"` (and switching it off bounds the shell _tool_, not every path to a shell — `build_project`, `run_tests`, `verify_done` and `run_script` still compose `bash -c` behind the `command.started` policy fence); an opt-in `bash` sandbox (Seatbelt / bubblewrap) contains prompt-injection damage and **fails closed**; a cloned repo's own hooks never auto-execute (`STELLA_PROJECT_HOOKS=1` to opt in). |

## Also in the box

Expand Down
6 changes: 3 additions & 3 deletions scripts/file-size-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
1623 stella-cli/src/candidate_ws.rs
4604 stella-cli/src/command_deck.rs
2095 stella-core/src/bus.rs
2087 stella-core/src/driver.rs
2098 stella-core/src/driver.rs
2819 stella-core/src/driver/tests.rs
1759 stella-fleet/src/fleet.rs
1601 stella-model/src/anthropic/tests.rs
Expand All @@ -22,11 +22,11 @@
2476 stella-pipeline/src/pipeline.rs
2022 stella-pipeline/src/pipeline/tests.rs
2399 stella-protocol/src/event.rs
2270 stella-store/src/lib.rs
2283 stella-store/src/lib.rs
2170 stella-store/src/tests.rs
1884 stella-store/src/usage.rs
1556 stella-tools/src/media.rs
3014 stella-tools/src/registry.rs
3019 stella-tools/src/registry.rs
1797 stella-tools/src/scripts.rs
1705 stella-tui/src/deck_render.rs
4049 stella-tui/src/deck_ui.rs
Expand Down
10 changes: 8 additions & 2 deletions stella-cli/src/agent/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ macro_rules! tool_steering {

- Read a definition by name with read_symbol; guessing read_file offsets after a graph_query is the round-trip it exists to remove.
- A change touching several files is ONE apply_edits call, not a chain of edit_file calls.
- A tool you cannot see is not available in this session rather than nonexistent. There is no shell unless the workspace enables it ("tools": {"bash": "on"}); issue tracking, web, and media tools register only once their backend is configured (`stella connect github|linear`, an API key, or `gh auth`; ci_status needs the gh CLI). Reach for tool_search before concluding a capability is missing."#
- A tool you cannot see is not available in this session rather than nonexistent. The shell ships registered and a workspace withholds it with "tools": {"bash": "off"}; issue tracking, web, and media tools register only once their backend is configured (`stella connect github|linear`, an API key, or `gh auth`; ci_status needs the gh CLI). Reach for tool_search before concluding a capability is missing."#
};
}

Expand Down Expand Up @@ -469,7 +469,13 @@ mod tests {
// A schema can only describe a tool that IS registered — it
// can never explain an absence or how to lift it.
"not available in this session",
"tools\": {\"bash\": \"on\"}",
// The switch's real polarity. Pinned as `off` on purpose:
// this sentence read `"bash": "on"` — "there is no shell
// unless the workspace enables it" — for every release after
// #710 shipped bash registered-by-default, so the prompt told
// the model the opposite of the tool surface it had, and this
// assertion pinned the false claim in place (#615).
"tools\": {\"bash\": \"off\"}",
"tool_search",
] {
assert!(
Expand Down
Loading