@nanobpm/nano-coder 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +91 -4
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -25,6 +25,7 @@ cargo install nano-coder # or build from source
25
25
  - **Status line** pinned to the bottom of the terminal, plus manual and automatic context compaction
26
26
  - **Task plans**: `plan_*` tools keep a plan with notes outside the conversation, so long tasks survive compaction, resume and a change of worker
27
27
  - **Project instructions**: `AGENTS.md` (or `CLAUDE.md`, `.github/copilot-instructions.md`) from the repository is added to the system prompt
28
+ - **Safety**: built-in guards block destructive commands (`rm -rf /`, `DROP DATABASE`, force-pushing `main`, ...), user allow/deny rules, and an optional OS sandbox (Seatbelt on macOS, Landlock on Linux)
28
29
  - **Skills**: `SKILL.md` folders from the repository, `~/.agents/skills`, and an spm `ai.lock`, loaded on demand with `load_skill`
29
30
 
30
31
  ## Two Execution Modes
@@ -127,6 +128,9 @@ src/
127
128
  │ └── mock.rs # Offline scripted client
128
129
  ├── bash.rs # bash tool: timeout, file capture, bounded output
129
130
  ├── files.rs # read_file / write_file / edit_file tools
131
+ ├── shell.rs # Bash parser used by the permission checks
132
+ ├── permissions.rs # Allow/deny rules and built-in guards against destructive commands
133
+ ├── sandbox.rs # Seatbelt (macOS) / Landlock (Linux) sandbox for shell commands
130
134
  ├── output.rs # Head/tail output bounding, spilling long output to disk
131
135
  ├── session.rs # Versioned append-only JSONL session log
132
136
  ├── context.rs # Token accounting, context-window heuristics, overflow detection
@@ -184,8 +188,76 @@ with the whole result saved under the temp directory (`nano-coder-<pid>/tool-<id
184
188
  and its path in the marker. `read_file` pages instead.
185
189
 
186
190
  Relative paths resolve against the working directory (ACP `session/new` `cwd`). Writes are
187
- atomic (temp file + rename). There is no permission prompt: run workers in a disposable
188
- workspace.
191
+ atomic (temp file + rename). There is no permission prompt; every call is checked against
192
+ the [permission rules and sandbox](#permissions-and-sandbox) instead.
193
+
194
+ ## Permissions and Sandbox
195
+
196
+ nano-coder never stops to ask for approval (it runs headless in agent fleets). Every tool call
197
+ is checked before it runs instead, and a blocked call returns an error telling the model to
198
+ stop and ask the user rather than work around the block.
199
+
200
+ **Order of checks:** `deny` rules, then `allow` rules, then the built-in guards. Deny always
201
+ wins. An allow rule approves a shell command only when *every* command in it matches, so
202
+ `Bash(git *)` does not approve `git status && rm -rf /`.
203
+
204
+ **Rules** name a tool and an optional pattern:
205
+
206
+ | Rule | Matches |
207
+ |---|---|
208
+ | `Bash(rm -rf *)` | a shell command; `*` matches anything, including spaces and `/` |
209
+ | `Bash(git push:*)` | `git push` alone or with any arguments |
210
+ | `Read(~/.ssh/**)` | `read_file` paths; `**` crosses directories, `*` does not |
211
+ | `Edit(**/.env)` / `Write(...)` | `write_file` and `edit_file` paths (relative to the working directory, or absolute) |
212
+ | `write_file`, `bash`, any tool name | every call to that tool |
213
+
214
+ **Shell commands are parsed, not pattern-matched as text.** The command line is split on `;`,
215
+ `&&`, `||`, `|`, `&`, newlines and parentheses; quotes are removed; `$(...)`, backticks and
216
+ `<(...)` are parsed as further commands. The guards and rules then see through assignments
217
+ (`FOO=1 cmd`), wrappers (`sudo`, `env`, `timeout`, `nice`, `xargs`, `nohup`, `command`, ...),
218
+ `bash -c '...'`, `eval`, `ssh host cmd`, `find -exec`, and here-documents fed to a shell. A command
219
+ that cannot be parsed, or a script computed at run time (`bash -c "$CMD"`,
220
+ `eval "$(curl ...)"`), is blocked.
221
+
222
+ **Built-in guards** (`builtin_rules = true`) block:
223
+
224
+ - recursive `rm` (and `mv`, `find -delete`, `chmod -R`/`chown -R`) on `/`, your home directory
225
+ or its top-level folders, the working directory or its parents, top-level and system
226
+ directories, and `.git`. `rm -rf *` counts as the working directory. An unset variable counts
227
+ as empty, so `rm -rf "$DIR/"*` is blocked unless written `"${DIR:?}/"*`. Paths follow a `cd` and
228
+ variable assignments earlier in the same command (`cd .. && rm -rf project` is blocked)
229
+ - `mkfs`, `fdisk`, `wipefs`, destructive `diskutil`, `dd of=/dev/...` and redirects to
230
+ devices, fork bombs, `shutdown`/`reboot`
231
+ - destructive SQL (`DROP DATABASE|SCHEMA|TABLE`, `TRUNCATE`, `DELETE FROM` without `WHERE`,
232
+ `ALTER TABLE ... DROP`, `FLUSHALL`, `dropDatabase()`) in a command that uses a database client
233
+ (`psql`, `mysql`, `sqlite3`, `mongosh`, `redis-cli`, also via `docker exec`) or inline
234
+ interpreter code (`python -c`), plus `dropdb`, `rails db:drop`, `prisma migrate reset`,
235
+ `manage.py flush`
236
+ - `terraform destroy`, `pulumi destroy`, `kubectl delete namespace|--all`, `aws s3 rb`
237
+ - `git push --force` (or `+refspec`, `--all`, wildcard refspecs) to, or deleting, a protected
238
+ branch, and `git push --mirror`
239
+
240
+ Add an allow rule for anything legitimate they block, e.g.
241
+ `allow = ["Bash(sqlite3 test.db *)"]`, or set `builtin_rules = false`.
242
+
243
+ **These checks catch mistakes, not adversaries.** A model can write a script and run it, and
244
+ nothing inspects that. The boundary is the OS sandbox, plus credentials: don't give the agent
245
+ production database URLs or broadly scoped tokens.
246
+
247
+ **Sandbox** (`--sandbox workspace`, off by default) runs each shell command under Seatbelt
248
+ (`sandbox-exec`) on macOS or Landlock on Linux (6.2+). Commands can read everywhere, but
249
+ write only to:
250
+
251
+ - `workspace`: the working directory, its git directories (including a worktree's shared
252
+ one), temp directories, package-manager caches (`~/.cargo/registry`, `~/.npm`, `~/.cache`,
253
+ `~/Library/Caches`, `~/.gradle`, `~/go/pkg/mod`, ...) and `writable` paths
254
+ - `read-only`: temp directories and `writable` paths
255
+
256
+ `write_file` and `edit_file` are held to the same directories. `network = false` blocks
257
+ outbound connections (macOS: except to localhost; Linux: all TCP, which needs Linux 6.7+).
258
+ If the sandbox is enabled but cannot be applied, commands fail instead of running
259
+ unsandboxed. When a sandboxed command fails with a permission error, the result tells the
260
+ model where it may write.
189
261
 
190
262
  ## Commands
191
263
 
@@ -234,7 +306,8 @@ cargo run -- --resume sess-20260923T012518-7e7923f8
234
306
  ```
235
307
 
236
308
  Flags: `--login github-copilot`, `--list-models PROVIDER`, `--acp`, `--model provider/model` (or `AGENTIC_HARNESS_MODEL`), `--resume SESSION_ID`,
237
- `--config PATH`, `--verbosity LEVEL` (`-v`).
309
+ `--config PATH`, `--verbosity LEVEL` (`-v`), `--sandbox off|workspace|read-only` (or `NANO_CODER_SANDBOX`),
310
+ `--allow RULE` and `--deny RULE` (repeatable; added to the config's rules).
238
311
 
239
312
  ## Configuration
240
313
 
@@ -268,6 +341,18 @@ user_dirs = ["~/.agents/skills"]
268
341
  ai_lock = true # load skills pinned in ai.lock
269
342
  fetch = true # fetch ai.lock commits missing from the spm store
270
343
  allowed_hosts = ["github.com"] # hosts ai.lock entries may be fetched from ("*" = any)
344
+
345
+ [permissions] # see Permissions and Sandbox
346
+ builtin_rules = true # block destructive commands unless allowed
347
+ allow = [] # e.g. ["Bash(sqlite3 test.db *)"]
348
+ deny = [] # e.g. ["Bash(git push:*)", "Edit(**/.env)"]
349
+ protected_branches = ["main", "master", "trunk", "develop"]
350
+
351
+ [sandbox]
352
+ mode = "off" # off | workspace | read-only (or --sandbox)
353
+ writable = [] # extra writable paths, e.g. ["~/.local/state/myapp"]
354
+ network = true # false blocks outbound connections
355
+ tool_caches = true # workspace mode: allow ~/.cargo/registry, ~/.npm, ~/.cache, ...
271
356
  ```
272
357
 
273
358
  The default model is `gpt-4o-mini` on the `mock` provider, so the harness still works offline.
@@ -632,4 +717,6 @@ Retry classification, output bounding, bash result formatting and the session-lo
632
717
  are adapted from [unreal-agent](https://github.com/unreallabsai/unreal-agent)
633
718
  (MIT, Copyright (c) 2026 Unreal Labs). System reminders and the outcome tool follow ideas in
634
719
  [grok-build](https://github.com/xai-org/grok-build)'s `<system-reminder>` notes and
635
- `update_goal` tool.
720
+ `update_goal` tool. The permission model (deny wins, allow rules must cover every command in
721
+ a chain, wrappers stripped before matching) and the Seatbelt/Landlock sandbox profiles follow
722
+ grok-build's and Codex's designs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/nano-coder",
3
- "version": "0.3.2",
3
+ "version": "0.4.1",
4
4
  "description": "A 6MB coding agent. Run a fleet on your laptop.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -31,9 +31,9 @@
31
31
  "node": ">=18"
32
32
  },
33
33
  "optionalDependencies": {
34
- "@nanobpm/nano-coder-darwin-arm64": "0.3.2",
35
- "@nanobpm/nano-coder-darwin-x64": "0.3.2",
36
- "@nanobpm/nano-coder-linux-arm64": "0.3.2",
37
- "@nanobpm/nano-coder-linux-x64": "0.3.2"
34
+ "@nanobpm/nano-coder-darwin-arm64": "0.4.1",
35
+ "@nanobpm/nano-coder-darwin-x64": "0.4.1",
36
+ "@nanobpm/nano-coder-linux-arm64": "0.4.1",
37
+ "@nanobpm/nano-coder-linux-x64": "0.4.1"
38
38
  }
39
39
  }