Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
3bf94df
test(danger): add RED tests for classifier bug hunt
claude Oct 8, 2026
b61327c
fix(danger): harden read ledger gate and injection scanner
claude Oct 8, 2026
c86771f
fix(danger): gate program-valued options and fix verb detection for e…
claude Oct 8, 2026
2b3e68f
fix(danger): classify wrapper payloads, env dumps and exported exec-e…
claude Oct 8, 2026
dec8089
Merge branch 'fix/red-exec' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
817aeb3
Merge branch 'fix/red-wrappers' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
d8394f4
fix(danger): carry shell state soundly and close analysis gaps
claude Oct 8, 2026
5a413ee
fix(danger): close path-spelling and destination gaps in the classifier
claude Oct 8, 2026
8bdf3d2
Merge branch 'fix/red-analysis' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
d9a7ef4
Merge branch 'fix/red-paths' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
c49421d
fix(danger): merge git archive -o into the shared git write-target case
claude Oct 8, 2026
70c655c
fix(danger): make normalization phases quote-aware and quote-safe
claude Oct 8, 2026
23679e5
Merge branch 'fix/red-normalize' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
3bdfa0c
fix(danger): drop duplicate hex-digit helper after normalize merge
claude Oct 8, 2026
e6dfda6
fix(danger): keep a plain ssh rsync transport as network egress
claude Oct 8, 2026
9bc5cb1
fix(danger): apply denylist entries to every command position
claude Oct 8, 2026
66dfc8f
fix(danger): consume value-taking options of transparent wrappers
claude Oct 8, 2026
a6c7cdd
fix(danger): expand brace sequences in the normalizer
claude Oct 8, 2026
255db27
fix(danger): gate secret-variable reads and workspace credential files
claude Oct 8, 2026
68dd63e
fix(danger): classify gh by command and verb instead of blanket egress
claude Oct 8, 2026
44dd3b8
fix(danger): gate unread scripts delivered through pipes, substitutio…
claude Oct 8, 2026
be5b642
fix(danger): let the current user's home outrank the /root system prefix
claude Oct 8, 2026
da8cb58
fix(danger): keep literal values bound by export, declare, typeset, l…
claude Oct 8, 2026
920c6b8
fix(danger): bound the read ledger and add ForgetReadLedger
claude Oct 8, 2026
002fa7c
fix(danger): scan markdown headers line by line and fold styled letters
claude Oct 8, 2026
27c2d67
fix(danger): split uploads, listeners and tunnels out of network egress
claude Oct 8, 2026
e54ca70
Merge branch 'w2/egress' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
f1c4f47
docs(security): describe brace sequences, indirect script delivery an…
claude Oct 8, 2026
cafe628
test(danger): reconcile gh upload forms with the gh verb adapter
claude Oct 8, 2026
98ad7ba
Merge branch 'w2/leftovers' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
a44527a
fix(danger): show approval prompt text with control and bidi characte…
claude Oct 8, 2026
436a26f
Merge branch 'w2/policy' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
f487d67
fix(danger): reconcile denylist and upload tests with secret-read gating
claude Oct 8, 2026
c128a6c
fix(danger): fail closed on unterminated quotes and bound analysis cost
claude Oct 8, 2026
fb91833
Merge branch 'w2/fuzz' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
6f6d4fc
fix(danger): denylist matches env -S commands and commands behind too…
claude Oct 8, 2026
0c5a631
fix(danger): denylist resolves statically known variables
claude Oct 8, 2026
e9facbb
fix(danger): read-ledger gating for fd --exec, stdin device scripts a…
claude Oct 8, 2026
9433892
fix(danger): runtime-built ping/traceroute targets and dig -f classif…
claude Oct 8, 2026
2befd06
fix(danger): credential files are recognised by directory components
claude Oct 8, 2026
e171603
fix(danger): escape control characters in denial errors returned to t…
claude Oct 8, 2026
78d3e10
fix(danger): unquoted bare carriage return classifies unknown
claude Oct 8, 2026
b55a631
fix(danger): escalate git verbs only when the repository is armed
claude Oct 8, 2026
ffd0740
Merge branch 'w3/githooks' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
191b4ba
fix(danger): classify shell compound commands through their simple co…
claude Oct 8, 2026
cdd6f98
Merge branch 'w3/compound' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
c5877c0
test(danger): pin effect verdicts for an option-grammar corpus
claude Oct 8, 2026
109f2cd
refactor(danger): one option-spec parser for the transfer-client gram…
claude Oct 8, 2026
0ed5b00
refactor(danger): gh verb options read through optSpec
claude Oct 8, 2026
62515c8
refactor(danger): wrapper and argv-composer options read through optSpec
claude Oct 8, 2026
0e39eeb
refactor(danger): subcommand lookup for git, docker, kubectl, helm an…
claude Oct 8, 2026
8bc3a05
fix(danger): close gaps found in adversarial review of the hardening …
claude Oct 8, 2026
d3c55ae
docs(security): describe glued substitutions and run-time program ope…
claude Oct 8, 2026
f415fcc
refactor(danger): tar, chmod, install and sed options read through op…
claude Oct 8, 2026
8f69045
refactor(danger): client-program and execution-file option scans use …
claude Oct 8, 2026
c7c2e8b
refactor(danger): output-file option scans use optSpec
claude Oct 8, 2026
65a80b3
docs: reconcile developer docs and danger package docs with the class…
claude Oct 8, 2026
5c45495
docs: reconcile operator docs with the danger-classifier changes
claude Oct 8, 2026
3db52ca
refactor(danger): awk program options read through optSpec
claude Oct 8, 2026
4a8e4ee
Merge branch 'w4/docsuser' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
8c1cd20
fix(danger): git --attr-source takes the next word
claude Oct 8, 2026
c1de62f
fix(odek): clamp unread_exec under max_risk and badge persistence cards
claude Oct 8, 2026
42b90a3
docs: reconcile SECURITY.md with the classifier and pin it in the reg…
claude Oct 8, 2026
414da94
Merge branch 'w4/docsec' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
1a3b5a8
fix(danger): bare git push is network egress; finish doc reconciliation
claude Oct 8, 2026
92e2681
refactor(danger): option scans tolerate an empty command
claude Oct 8, 2026
69b7015
Merge branch 'w3/optspec' into fix/danger-classifier-red-bugs
claude Oct 8, 2026
86aad6a
fix(danger): sed w/r commands accept a path-shaped filename without a…
claude Oct 8, 2026
7deb465
refactor(danger): satisfy staticcheck and drop helpers left unused by…
claude Oct 8, 2026
efe90b8
fix(danger): assemble inet_aton addresses without narrowing conversions
claude Oct 8, 2026
de87471
test(danger): scale timing bounds under the race detector; skip /root…
claude Oct 8, 2026
de6f857
fix(loop): stop the tool heartbeat synchronously so no signal fires a…
claude Oct 8, 2026
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
37 changes: 33 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,28 @@ internal/
tool/ Thread-safe tool registry, clarify.go, send_message.go
danger/ Command/URL classification + bypass-resistant tokenizer. Approver interface +
TTYApprover with friction mode (interactive approval system lives here).
classifier.go normalize entry point, RiskClass set (11 classes), DangerousConfig, ActionForCommand, verb/path classification,
package doc listing the layered design and its limitations
analysis.go Analyze → per-effect result, shell state across segments, MaxCommandBytes + token budget
command_effects.go Per-tool exec/write adapters (tar, sed, git, ssh/rsync, kubectl/helm, …)
normalize.go Unicode folding (homoglyphs, invisibles, styled letters) + command spacing
normalize_phases.go Quote-aware phases on a shared lexer: line joins, comments, here-docs, ANSI-C, brace lists/sequences
compound.go Shell compound-command parser (loops, if/case, groups, functions, [[ ]], (( ))): every simple
command inside is classified; unpaired constructs are unknown
wrapper_grammar.go One option grammar for wrappers (timeout/nice/sudo/flock/script/nix/mise/…) incl. command-string options
denylist.go Denylist token-prefix matching at every command position
secret_reads.go Secret-shaped env-var references and credential-file reads/writes → system_write
network_upload.go network_upload class: bodies from files/stdin, credentials, mutating methods, uploads, listeners
gh_adapter.go gh classified by command and verb (reads egress, mutations system_write, deletes destructive)
git_repo_arming.go Repo-aware git escalation: ordinary verbs are code_execution only when the repo is armed
readledger.go Unread-script gate: fingerprinted, bounded read ledger; ForgetReadLedger per session key
ledger_indirect.go Scripts delivered indirectly (pipes, substitutions, eval, find -exec, program-file options)
path_identity.go Symlink-resolving path targets for classification (snapshot, not an execution boundary)
injection.go Injection scanner (ScanInjection) with Unicode folding
approver.go Approver interface, TTY approver, friction
display.go SanitizeForDisplay/SanitizeInline: control/bidi/invisible characters escaped in every prompt
monotonicity_fuzz_test.go Fuzz invariants (suffix wipe never hidden, pipe-into-shell ≥ code_execution, harmless prefix
never lowers a verdict, bounded time)
bgproc/ Session-scoped background process manager (bg_* tools): bounded output rings,
spawn-time danger classification parity, group-signal stop
diagnostics/ Runtime diagnostics helpers
Expand Down Expand Up @@ -190,15 +212,15 @@ Layered prompt-injection / approval-fatigue defenses. The full per-mitigation li

- **Untrusted-content boundary** (`cmd/odek/untrusted.go`) — every externally-sourced tool result (browser, file/shell/search tools, MCP, session_search, @-refs, --ctx, attachments, artifact_read) is wrapped in a per-call nonce'd `<untrusted·content_<nonce>>` tag; tool-result delimiters are also nonce'd (`internal/loop`). Skill/episode context injected into the system prompt is wrapped too. The per-session audit log (`cmd/odek/audit.go`) records every ingest and flags divergence between user-mentioned resources and agent actions.
- **Provenance gates** — tainted memory episodes are stored but never auto-replayed; skills from untrusted sources (imported via URI, project `./.odek/skills/`) are pinned `NeedsReview` until `odek skill promote --force` is run after human review, excluded from trigger matching, and protected against frontmatter tampering. Load-time and import-time skill bodies go through the injection scanner (`guard.ScanContentWithScope`). `odek` self-invocation via shell is `system_write` so the agent can't reach its own trust mutations.
- **Danger classifier** (`internal/danger/classifier.go`) — bypass-resistant normalization ($IFS, command substitution, wrappers, backslashes, basenames); covers awk/sed/editor escapes, pipe-fed xargs composition, root-level mutation targets, git data-loss verbs, `gh` as network egress, `git -c`/config code exec, find/rsync destructive flags, env dumps, shell operand/redirect path classification (writes to shell rc files, ~/.ssh, ~/.odek escalate to system_write). Trust anchors under `~/.odek` are write-protected from generic file tools. The read ledger is fingerprinted (`WasReadFresh`: post-read mutation re-fires the unread-script gate), and unread-script approvals carry a pre-exec injection-scan enrichment incl. single-layer base64/hex decode (`cmd/odek/unreadscan.go`, scan never populates the ledger).
- **Danger classifier** (`internal/danger/classifier.go`; layered design in its package doc) — quote-aware normalization ($IFS, ANSI-C, brace lists/sequences, substitutions, heredocs, wrappers, backslashes, basenames); compound commands (`for`/`while`/`if`/`case`/groups/functions/`[[ ]]`) parsed so every simple command inside is classified, unterminated constructs and quotes classify `unknown`. Wrappers share one option grammar (`wrapper_grammar.go`) incl. command-string options (`env -S`, `script -c`, `flock -c`). Covers awk/sed/editor escapes, pipe-fed xargs composition, root-level mutation targets, git data-loss verbs, `git -c`/config code exec, find/rsync destructive flags, env dumps, exec-controlling `export`s, shell operand/redirect path classification (writes to shell rc files, ~/.ssh, ~/.odek escalate to system_write). `network_upload` (default prompt, ranked between `network_egress` and `code_execution`) splits uploads, credentialed/mutating requests, listeners and tunnels from plain egress; `gh` is classified by command and verb (`gh_adapter.go`). Ordinary git verbs escalate to `code_execution` only when the targeted repository is armed (hooks, fsmonitor, filters/drivers, editors, includes); an undeterminable repo, `GIT_*` overrides, sudo wrappers or a hook written earlier in the same command fail closed (`git_repo_arming.go`). Denylist entries are token prefixes matched at every command position, with tool global options stripped and known variables resolved — not raw string prefixes. Secret-shaped env vars (`$NAME`, `${!v}`, `printenv`, `os.environ`) and credential files (by basename, extension or directory) are `system_write` (`secret_reads.go`). Trust anchors under `~/.odek` are write-protected from generic file tools. The read ledger is fingerprinted (`WasReadFresh`: post-read mutation re-fires the unread-script gate), covers scripts delivered through pipes, substitutions, `eval`, `find -exec` and program-file options, is bounded (4096 paths per session, 1024 sessions, LRU eviction only ever removes a licence) and dropped per session with `danger.ForgetReadLedger` (serve session delete, Telegram reset, schedule run end); unread-script approvals carry a pre-exec injection-scan enrichment incl. single-layer base64/hex decode (`cmd/odek/unreadscan.go`, scan never populates the ledger). Fuzz invariants in `monotonicity_fuzz_test.go` (suffix wipe never hidden, pipe into shell at least `code_execution`, harmless prefix never lowers a verdict, bounded time).
- **Plan-check honesty** — plan acceptance checks record outcomes from actual engine observation only; failed or unclassifiable tool effects invalidate earlier checks in the same batch, so a check can never be satisfied by a claim inside tool output.
- **Approval friction** — TTY/WS/Telegram approvers engage friction after 3 same-class approvals in 60s (type `approve`, pause, trust shortcut hidden); `destructive`/`blocked`/`unknown` never get trust shortcuts. TTY prompts are process-wide serialized.
- **Approval friction** — TTY/WS/Telegram approvers engage friction after 3 same-class approvals in 60s (type `approve`, pause, trust shortcut hidden); `destructive`/`blocked`/`unknown` never get trust shortcuts. TTY prompts are process-wide serialized. Everything shown in an approval (TTY/WebSocket/Telegram prompts, batch cards, MCP/sandbox approval prompts, denial error strings) goes through `danger.SanitizeForDisplay`/`SanitizeInline`, which escape control, bidi and invisible characters so the human reads the bytes that run; the read_only non-interactive carve-out is keyed on the native tool name, never the model-supplied description.
- **Sub-agent caps** — `delegate_tasks` carries trust_level + max_risk enforced via the sub-agent's DangerousConfig; MCP tools withheld from untrusted sub-agents; API keys handed off via unlinked-tempfile FD, never env. Sub-agent results over ~2000 chars are delivered as registry-backed artifacts the parent reads via `artifact_read`.
- **MCP hardening** — subprocess env sanitization (secret-pattern stripping), tool-name/description/inputSchema validation + injection scans, per-tool approval for every server (keys hash command/args/env + schema hash + description text + all four limit fields), per-server limits with absolute ceilings, per-server `enabled` toggle, artifact-ref fail-closed validation.
- **Config trust split** — `./odek.json` is untrusted: sensitive sections (provider, providers, llm, base_url, api_key, system, dangerous, memory, telegram, web_search, embedding, sessions, skills.dirs, verify, profiles, guard, schedules) ignored with warnings; sandbox knobs gated behind explicit operator approval (incl. implicit `Dockerfile.odek` builds, content-hash keyed); project limits may only lower global budgets, project prices rejected outright. Global config/secrets permission-checked; config files size-capped.
- **Serve / network surface** — per-instance CSRF token on `/ws` and all `/api/*`, loopback Host checks, local-origin requirement for mutations, per-session auth tokens + rate limiting, clickjacking headers, WS message-size caps. SSRF dial guard (DNS-rebinding-safe, internal-IP refusal, proxy refusal) on browser/http_request/web_search. WebUI done-frame stats are markup-escape hardened; WebUI task supervision renders sub-agent state without trusting frame content.
- **Budgets, events, refs (v1.24.0)** — budget clamp merge (see above); event stream carries SHA-256 arg hashes + sizes only (never raw args), redact applied, JSONL sink 0600/no-symlink/fsync-per-event, drop-on-full dispatch; external refs validated and never dereferenced.
- **Resource bounds** — pervasive size caps (shell output 1 MiB/stream, perf-tool files 10 MiB, session files 32 MiB, skill files 1 MiB, browser snapshots/history/elements, tree width, search results, write_file content, patch expansion) to keep hostile input from OOMing the process.
- **Resource bounds** — pervasive size caps (shell output 1 MiB/stream, perf-tool files 10 MiB, session files 32 MiB, skill files 1 MiB, browser snapshots/history/elements, tree width, search results, write_file content, patch expansion), classifier input (commands over `danger.MaxCommandBytes` = 64 KiB are `unknown`/denied; one analysis examines at most 4096 tokens; here-doc, substitution and brace scanning are budgeted; read ledger 4096 paths × 1024 sessions; prompt text capped by `danger.DisplayMaxBytes`/`InlineMaxBytes`) to keep hostile input from OOMing the process.
- **Telegram** — chat-scoped sessions/plans/media, callback binding to originating user, outbound media allowlist + approval, secret-subtree rejection, singleton flock, 0600 logs.
- **Redaction** — `internal/redact` (20+ patterns: provider keys, cloud creds, PEM, JWT, DB URLs, …) applied to sessions, logs, and the event stream.

Expand Down Expand Up @@ -245,9 +267,16 @@ go test -fuzz=FuzzEventJSON -fuzztime=30s ./internal/events/
go test -fuzz=FuzzExternalRefValidate -fuzztime=30s ./internal/session/
go test -fuzz=FuzzParseExternalRefFlag -fuzztime=30s ./cmd/odek/
go test -fuzz=FuzzClampProjectLimits -fuzztime=30s ./internal/config/
# Classifier monotonicity targets (run with a non-root HOME, see below)
HOME=/home/user go test -fuzz=FuzzSeparatorThenWipe -fuzztime=30s ./internal/danger/
HOME=/home/user go test -fuzz=FuzzPipeIntoShell -fuzztime=30s ./internal/danger/
HOME=/home/user go test -fuzz=FuzzHarmlessPrefixKeepsRank -fuzztime=30s ./internal/danger/
HOME=/home/user go test -fuzz=FuzzAnalyzeBounded -fuzztime=30s ./internal/danger/
```

CI also runs `golangci-lint` (staticcheck) and `govulncheck` on every push/PR — run both locally before pushing.
CI also runs `golangci-lint` (staticcheck) and `govulncheck` on every push/PR — run both locally before pushing. `golangci-lint` may fail to run on this module's Go version (it can crash while loading packages); then run `go vet` and `gofmt -l` on the changed packages and rely on CI for staticcheck.

`internal/danger` tests must run with a non-root `HOME` (`HOME=/home/user go test -count=1 ./internal/danger/`): `/root` is a system prefix and counts as the current user's home only when `HOME` resolves there, so the path-classification expectations assume a home elsewhere. The `internal/danger` fuzz targets seed from every string literal in the package's regression tests, so new regression cases extend the corpus automatically.

Note: MCP client E2E tests build the fakeserver from `internal/mcpclient/testdata/main.go` at test time (the extension mock is env-gated via `FAKE_ARTIFACT_MODE=1`). macOS temp dirs are classified as `LocalWrite` (not `SystemWrite`), and the Docker availability check verifies daemon reachability (5s timeout) before running sandbox tests.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ odek is not a framework. It's a **runtime** — the smallest possible surface ar
Every session can run in an isolated Docker container: no network, no host mounts beyond the working directory, zero capabilities, destroyed on exit. Sandboxing is **on by default** for `odek run`, `odek repl`, and `odek serve`; opt out with `--no-sandbox` / `ODEK_NO_SANDBOX=1` (unsandboxed runs warn loudly, and `ODEK_REQUIRE_SANDBOX=1` makes them fatal). `--ctx` files are auto-injected into the container at `/workspace/`. Full security model in [docs/SANDBOXING.md](docs/SANDBOXING.md).

### 🛡️ Prompt-Injection-Aware
External content the agent ingests (`browser`, `read_file`, `shell`, `search_files`, `transcribe`, `vision`, `web_search`, `session_search`, MCP tools) is wrapped in per-call nonce'd `<untrusted_content>` boundaries so the model can distinguish data from instructions. Redirect hops are re-classified (`browser`/`http_request`), MCP tool descriptions are scanned for injection at registration, and the MCP error channel is wrapped too. The danger classifier resists common shell-evasion tricks (`$()`/backtick substitution, `$IFS`, brace expansion, `command`/`env` wrappers, `\rm`, basenamed absolute paths, and more). Approvers engage friction mode after 3 same-class approvals in 60 s. Memory episodes from tainted sessions are stored but never auto-replayed. Imported and project skills track provenance — untrusted ones stay excluded from trigger matching until explicit `odek skill promote --force`. `odek audit <session-id>` surfaces every ingest + per-turn divergence heuristic. Full threat model in [docs/SECURITY.md](docs/SECURITY.md).
External content the agent ingests (`browser`, `read_file`, `shell`, `search_files`, `transcribe`, `vision`, `web_search`, `session_search`, MCP tools) is wrapped in per-call nonce'd `<untrusted_content>` boundaries so the model can distinguish data from instructions. Redirect hops are re-classified (`browser`/`http_request`), MCP tool descriptions are scanned for injection at registration, and the MCP error channel is wrapped too. The danger classifier parses shell syntax (quoting, substitutions, brace and `$IFS` tricks, wrappers, loops and conditionals, here-documents) and judges every command a line would run; it fails closed on what it cannot parse, and uploads, secret reads, and unread scripts get their own prompts. Approval prompts show the command with control and bidi characters escaped. Approvers engage friction mode after 3 same-class approvals in 60 s. Memory episodes from tainted sessions are stored but never auto-replayed. Imported and project skills track provenance — untrusted ones stay excluded from trigger matching until explicit `odek skill promote --force`. `odek audit <session-id>` surfaces every ingest + per-turn divergence heuristic. Full threat model in [docs/SECURITY.md](docs/SECURITY.md).

### 🧩 Sub-Agent Delegation
Parallel OS-process sub-agents via `delegate_tasks`. True isolation — each sub-agent is a fresh `odek subagent` process with its own config, tools, and termination timeout. Up to 8 concurrent workers. Operator-defined **capability profiles** (top-level `profiles` config) override a sub-agent's permissions by name and fail closed on unknown names — a curated starter set of 21 task profiles ships in [`profiles.template.json`](profiles.template.json). See [docs/SUBAGENTS.md](docs/SUBAGENTS.md) and [docs/SECURITY.md](docs/SECURITY.md).
Expand Down Expand Up @@ -89,7 +89,7 @@ Attach files to any prompt with `--ctx` / `-c` (CLI), `@filename` inline referen
**Server** (`odek mcp`) — expose odek's built-in tools over stdio to Claude Code, Cursor, or any MCP client. **Client** (`mcp_servers` in `~/.odek/config.json` or `./odek.json`) — spawn external MCP servers and register their tools as `<server>__<tool>`. Per-server limits and fail-closed `file://` artifact refs: [odek-extension/v1](docs/EXTENSIONS.md). Both directions in one binary. [docs/MCP.md](docs/MCP.md)

### 🔍 Native Tools
Built-in `read_file`, `write_file`, `search_files`, `patch`, `shell`, and `browser` tools. All gated by a unified security layer (`dangerous` config) — classify operations as `allow` / `deny` / `prompt` per risk class. No third-party dependencies. [docs/SECURITY.md](docs/SECURITY.md)
Built-in `read_file`, `write_file`, `search_files`, `patch`, `shell`, and `browser` tools. All gated by a unified security layer (`dangerous` config) — classify operations as `allow` / `deny` / `prompt` per risk class (`safe`, `local_write`, `install`, `network_egress`, `network_upload`, `code_execution`, `system_write`, `unread_exec`, `persistence`, `unknown`, `destructive`, `blocked`). No third-party dependencies. [docs/SECURITY.md](docs/SECURITY.md)

### 🌐 Local Web Search
`web_search` queries a **self-hosted [SearXNG](https://docs.searxng.org/) metasearch instance** — no cloud search API, no keys. Returns ranked results (title, url, snippet) the agent then fetches with `browser`; `http_request` checks status and size only; results are wrapped as untrusted content and gated as `network_egress`. The Docker Compose setup runs a SearXNG sidecar and enables it out of the box; standalone installs point `web_search.base_url` at any SearXNG instance. [docs/CHEATSHEET.md](docs/CHEATSHEET.md#web-search)
Expand Down
Loading
Loading