docs: OpenRouter routing, security controls, tool guards
ober
53d2ece033da3847103d4da2385ed87a45c8e59a
--- a/docs/cli.md +++ b/docs/cli.md @@ -146,6 +146,7 @@ Available in the REPL and TUI. | `/plan` | Enter PLAN mode (read-only; write tools hidden). | | `/build` | Enter BUILD mode (read + write). | | `/mode` | Show the current mode. | +| `/security` | Show the current security posture: mode, disabled tools, MCP status, and sandbox state. | | `/doit [on\|off\|status\|max N\|max unlimited]` | Local-only auto-continue mode. When enabled on an `ollama*` or `mlx*` provider, jcode automatically sends a continuation prompt after each stopped assistant turn until the model marks the task done or blocked. | | `/clear` | Start a new session. | | `/sessions` | List saved sessions. | @@ -174,6 +175,8 @@ Available in the REPL and TUI. | Command | Effect | |---|---| | `/tools` | List available tools. | +| `/tool off <name>` | Disable a tool for the current session. Hidden from the model and rejected at execute time. | +| `/tool on <name>` | Re-enable a previously disabled tool. | | `/agents` | List named sub-agent roles for the `task` tool (built-ins + `jcode.json` variants). | | `/mcp` | Toggle MCP tools on/off. | | `/plugins` | List loaded plugins. | --- a/docs/providers.md +++ b/docs/providers.md @@ -161,6 +161,95 @@ every OpenAI-compatible endpoint: reuse reduces latency. For explicit compatibility testing, set `providers.ollama.wire` to `"openai"`. +### OpenRouter routing (auto-router, fallbacks, price caps) + +When the active provider is `openrouter`, `jcode` passes through provider/model +`options` and applies a hard `max_price` guardrail so routing knobs that live +only in the OpenRouter API become usable from config. + +Config lives under `providers.openrouter` (provider-level) and +`providers.openrouter.models.<MODEL>` (per-model overrides). `options` is a +free-form object that is deep-merged into the request body, so any field the +OpenRouter `chat/completions` endpoint accepts can be set there: + +```json +{ + "providers": { + "openrouter": { + "options": { + "route": "auto", + "provider": { + "data_collection": "deny", + "sort": "price" + } + } + } + } +} +``` + +Common knobs: + +| Field | Meaning | +|---|---| +| `route: "auto"` | Use OpenRouter's auto-router instead of a concrete model. | +| `provider.data_collection: "deny"` | Refuse providers that train on request data. | +| `provider.sort: "price"` | Prefer the cheapest adequate provider. | +| `provider.fallbacks: [...]` | Ordered list of providers to try on failure. | + +For non-OpenRouter providers, passthrough is **opt-in** via +`providers.<NAME>.options_passthrough: true` to avoid leaking unsupported fields +to arbitrary endpoints. + +`max_price` is a fail-closed guardrail under `providers.openrouter.max_price`. +It is merged into the request's `provider` object automatically: + +```json +{ + "providers": { + "openrouter": { + "max_price": { + "prompt": 0.00001, + "completion": 0.00005, + "image": 0.00010, + "request": 0.005 + } + } + } +} +``` + +OpenRouter rejects the request if the cheapest candidate exceeds the cap, so +this is a server-side guarantee rather than client-side accounting. + +### Routed-model surfacing + +When the provider returns a `model` field, `jcode` stores it as the +conversation's routed model (`(current-routed-model)`) and surfaces it in TUI +status and trace logs. This matters when the request targets a router such as +`openrouter/auto-beta` and the actual serving model is chosen server-side. + +Model capability metadata is also looked up from config under +`providers.<NAME>.models.<MODEL>.tool_call` and `.context_window`, falling back +to built-in heuristics only when no card is configured. This lets new models be +added without code changes: + +```json +{ + "providers": { + "openrouter": { + "models": { + "moonshotai/kimi-k3": { + "tool_call": true, + "context_window": 256000, + "output_limit": 32000 + } + } + } + } +} +``` + ### Grok CLI session `grok` is a separate provider from `xai` even though both surface Grok models. --- a/docs/threat-model.md +++ b/docs/threat-model.md @@ -38,6 +38,16 @@ production use. tools avoid logging full shell commands, prompts, or commit messages. - Tool execution is routed through registry checks, mode checks, and permission decisions. Write tools enforce path checks before filesystem mutation. +- Runtime tool ACL (`/tool off/on`) and PLAN/BUILD mode let the user shrink the + tool surface during a session. Disabled tools are hidden from the model and + rejected at execute time. +- A duplicate-call loop breaker tracks identical tool calls within a turn: + it warns after 3 repetitions and blocks the same call after 5, preventing + runaway loops from burning context and tokens. +- Tool results are scanned for common secret shapes (API keys such as `sk-`, + `AIza`, `ghp_`, Slack tokens, etc.) and replaced with a redaction notice + before being returned to the model or logged, reducing the risk of leaking + credentials across the provider boundary. - The key store encrypts stored provider keys and writes with owner-only file permissions. - Remote server and relay tokens are generated locally, stored outside the --- a/docs/tools.md +++ b/docs/tools.md @@ -206,12 +206,21 @@ keys. Every other provider's credentials plus `~/.ssh`, `~/.aws`, `~/.gnupg`, when the chosen CLI is `claude`, `~/.grok` is denied; when the chosen CLI is `grok`, `~/.claude` is denied; no CLI can read another's bearer token. -### 4. Generated-artifact protection +### 4. Generated-artifact and Scheme-source protection The write tools refuse to clobber build output: paths ending in `.sls` or `.so`, and anything under `lib/`, are rejected — those are compiled from `src/**/*.ss` and editing them by hand is always a mistake. +Additionally, native write tools (`write`, `edit`, `edit_block`, `multi-edit`, +`patch`, `apply_patch`) reject edits to `.ss` and `.sls` Scheme source files. +Jerboa source must be modified through the **jerboa-mcp balanced tools** +(`jerboa_balanced_replace`, `jerboa_balanced_insert`, `jerboa_write_file`), which +check delimiter balance before writing. This prevents the paren-imbalance bugs +that are easy to introduce with unrestricted text edits. The `verified` tool can +still edit Scheme source inside its own `write_scope` because it runs through +the same balanced-tool layer. + ### Checkpoints After any successful write-tool call, `registry.ss` snapshots the filesystem