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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to ShellClaw are documented here. Format follows [Keep a Cha
## [Unreleased]

### Fixed
- Shell `workspace_only` walks the first existing ancestor, collapses `..` lexically (without cancelling across a symlink), and scans quoted/embedded paths, `file:` URLs, `$HOME`/`$PWD` (including glued `$IFS`), and bare relative names such as `cat leak`. `strdup` OOM is fail-closed. Encoded-slash cat-and-mouse is frozen; Landlock is the kernel host-FS bound.
- Shell `sandbox_exec` applies a Landlock ruleset to the configured workspace (fail-closed) as the kernel host-FS bound, so symlink and `chr(47)+` host reads cannot skip the string scanner. Mount/network/PID namespaces fail closed (user namespace first when unprivileged). After `CLONE_NEWPID` the isolator forks so the command is PID 1. Isolation failure uses a control pipe, not `sh` exit 122/123. `/dev/null` stays writable under Landlock.
- Default `workspace_path` is `~/.shellclaw/workspace`, and file/shell tools still refuse `auth_tokens.json`, `shellclaw.pid`, `shellclaw.log`, and `.shellclaw` `config.toml` / `memory.db` (including `memory.db-*` sidecars) when a custom workspace contains them. A bare `cat config.toml` after the sandbox `chdir` is blocked, and a symlink at the workspace path is not accepted.
- Dashboard `PUT /api/config` merges JSON fields into `config.toml` and reloads live settings. Indented keys and `[section] # comment` headers are updated in place; a present field with the wrong JSON type returns 400; a saved file whose live reload fails returns 500. Gateway host and port still need a process restart to rebind.
- Unsandboxed `shell` no longer blocks forever in `waitpid` after the output cap fills; leftover children (including background grandchildren) are SIGKILL'd via the command process group, and truncated capture is NUL-terminated (#69).
Expand Down
20 changes: 15 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,12 @@ CRON_O := src/tools/cron.o
ASAP_INVOKE_O := src/tools/asap_invoke.o
ASAP_INVOKE_TEST_O := $(BINDIR)/asap_invoke_test.o
# Sandbox (Phase 3 §5)
SANDBOX_O := src/sandbox/sandbox.o
ALLOWLIST_O := src/sandbox/allowlist.o
SANDBOX_CORE_O := src/sandbox/sandbox.o
SANDBOX_LANDLOCK_O := src/sandbox/sandbox_landlock.o
SANDBOX_O := $(SANDBOX_CORE_O) $(SANDBOX_LANDLOCK_O)
ALLOWLIST_SCAN_O := src/sandbox/allowlist.o
ALLOWLIST_PATH_O := src/sandbox/allowlist_path.o
ALLOWLIST_O := $(ALLOWLIST_SCAN_O) $(ALLOWLIST_PATH_O)
MANIFEST_O := src/asap/manifest.o
MANIFEST_PROFILES_O := src/asap/manifest_profiles.o
MANIFEST_BUILD_O := src/asap/manifest_build.o
Expand Down Expand Up @@ -381,12 +385,18 @@ $(SHELL_O): src/tools/shell.c src/tools/tool.h src/tools/shell.h src/core/config
src/sandbox/sandbox.h src/sandbox/allowlist.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/tools/shell.c

$(SANDBOX_O): src/sandbox/sandbox.c src/sandbox/sandbox.h
$(SANDBOX_CORE_O): src/sandbox/sandbox.c src/sandbox/sandbox.h src/sandbox/sandbox_landlock.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/sandbox/sandbox.c

$(ALLOWLIST_O): src/sandbox/allowlist.c src/sandbox/allowlist.h
$(SANDBOX_LANDLOCK_O): src/sandbox/sandbox_landlock.c src/sandbox/sandbox_landlock.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/sandbox/sandbox_landlock.c

$(ALLOWLIST_SCAN_O): src/sandbox/allowlist.c src/sandbox/allowlist.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/sandbox/allowlist.c

$(ALLOWLIST_PATH_O): src/sandbox/allowlist_path.c src/sandbox/allowlist.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/sandbox/allowlist_path.c

$(WEBSEARCH_O): src/tools/web_search.c src/tools/tool.h src/tools/web_search.h src/core/config.h
$(CC) $(CFLAGS) $(INC) -c -o $@ src/tools/web_search.c

Expand Down Expand Up @@ -899,7 +909,7 @@ clean-root-dsym:
@rm -f shellclaw test_agent test_anthropic test_channel test_cli test_config test_file test_memory test_local_provider test_openai test_provider test_router test_shell test_skill test_telegram test_web_search test_ws

clean: clean-root-dsym
rm -f $(OBJS) $(PROVIDER_COMMON_O) $(STUB_O) $(ANTHROPIC_O) $(OPENAI_COMPAT_O) $(OPENAI_O) $(LOCAL_O) $(ROUTER_O) $(CJSON_O) $(TWEETNACL_O) $(ANTHROPIC_TEST_O) $(OPENAI_TEST_O) $(LOCAL_TEST_O) $(CONTEXT_TEST_OBJS) $(HEARTBEAT_TEST_O) $(CHANNEL_TG_TEST_O) $(CHANNEL_COMMON_O) $(CHANNEL_STUB_O) $(CHANNEL_CLI_O) $(CHANNEL_TG_O) $(CHANNEL_DISCORD_O) $(DISCORD_HELPERS_O) $(CHANNEL_HEARTBEAT_O) $(CHANNEL_WEBCHAT_O) $(AUTH_O) $(STATIC_O) $(HTTP_O) $(HTTP_LWS_O) $(ASAP_HTTP_BODY_O) $(ROUTES_O) $(ROUTES_HARDWARE_O) $(WS_O) $(MANIFEST_O) $(MANIFEST_PROFILES_O) $(MANIFEST_BUILD_O) $(MANIFEST_SIGN_O) $(MANIFEST_KEYS_O) $(ENVELOPE_O) $(ULID_O) $(CLIENT_O) $(ASAP_REGISTRY_O) $(SERVER_O) $(ASAP_LOG_O) $(RATE_LIMIT_O) $(SHELL_O) $(WEBSEARCH_O) $(FILE_O) $(REGISTRY_O) $(CONTEXT_O) $(CONTEXT_CACHE_O) $(CONTEXT_HTTP_O) $(CONTEXT_GEO_O) $(CRYPTO_O) $(JCS_O) $(HARDWARE_STUB_O) $(HARDWARE_INIT_O) $(HARDWARE_GPIO_SNAPSHOT_O) $(HARDWARE_TEGRASTATS_O) $(HARDWARE_TOOLS_O) $(BOARD_DETECT_O) src/hardware/hardware_libgpiod.o $(HARDWARE_I2C_O) $(HARDWARE_CAMERA_O) $(CRON_O) $(ASAP_INVOKE_O) $(SANDBOX_O) $(ALLOWLIST_O)
rm -f $(OBJS) $(PROVIDER_COMMON_O) $(STUB_O) $(ANTHROPIC_O) $(OPENAI_COMPAT_O) $(OPENAI_O) $(LOCAL_O) $(ROUTER_O) $(CJSON_O) $(TWEETNACL_O) $(ANTHROPIC_TEST_O) $(OPENAI_TEST_O) $(LOCAL_TEST_O) $(CONTEXT_TEST_OBJS) $(HEARTBEAT_TEST_O) $(CHANNEL_TG_TEST_O) $(CHANNEL_COMMON_O) $(CHANNEL_STUB_O) $(CHANNEL_CLI_O) $(CHANNEL_TG_O) $(CHANNEL_DISCORD_O) $(DISCORD_HELPERS_O) $(CHANNEL_HEARTBEAT_O) $(CHANNEL_WEBCHAT_O) $(AUTH_O) $(STATIC_O) $(HTTP_O) $(HTTP_LWS_O) $(ASAP_HTTP_BODY_O) $(ROUTES_O) $(ROUTES_HARDWARE_O) $(WS_O) $(MANIFEST_O) $(MANIFEST_PROFILES_O) $(MANIFEST_BUILD_O) $(MANIFEST_SIGN_O) $(MANIFEST_KEYS_O) $(ENVELOPE_O) $(ULID_O) $(CLIENT_O) $(ASAP_REGISTRY_O) $(SERVER_O) $(ASAP_LOG_O) $(RATE_LIMIT_O) $(SHELL_O) $(WEBSEARCH_O) $(FILE_O) $(REGISTRY_O) $(CONTEXT_O) $(CONTEXT_CACHE_O) $(CONTEXT_HTTP_O) $(CONTEXT_GEO_O) $(CRYPTO_O) $(JCS_O) $(HARDWARE_STUB_O) $(HARDWARE_INIT_O) $(HARDWARE_GPIO_SNAPSHOT_O) $(HARDWARE_TEGRASTATS_O) $(HARDWARE_TOOLS_O) $(BOARD_DETECT_O) src/hardware/hardware_libgpiod.o $(HARDWARE_I2C_O) $(HARDWARE_CAMERA_O) $(CRON_O) $(ASAP_INVOKE_O) $(SANDBOX_CORE_O) $(SANDBOX_LANDLOCK_O) $(ALLOWLIST_SCAN_O) $(ALLOWLIST_PATH_O)
rm -f src/gateway/ui_assets.h
find . -name '*.gcno' -o -name '*.gcda' -o -name '*.gcov' | xargs rm -f 2>/dev/null || true
rm -f $(WS_TEST_O) $(BINDIR)/asap_registry_test.o $(BINDIR)/asap_invoke_test.o $(CONTEXT_TEST_OBJS) $(HEARTBEAT_TEST_O) $(BINDIR)/shellclaw $(BINDIR)/test_tweetnacl_smoke $(BINDIR)/test_config $(BINDIR)/test_config_patch $(BINDIR)/test_memory $(BINDIR)/test_skill $(BINDIR)/test_provider $(BINDIR)/test_anthropic $(BINDIR)/test_openai $(BINDIR)/test_local_provider $(BINDIR)/test_router $(BINDIR)/test_heartbeat $(BINDIR)/test_crypto $(BINDIR)/test_hardware_stub $(BINDIR)/test_board_detect $(BINDIR)/test_hardware_libgpiod $(BINDIR)/test_hardware_i2c $(BINDIR)/test_hardware_camera $(BINDIR)/test_pin_tables $(BINDIR)/test_hardware_init $(BINDIR)/test_hardware_tools $(BINDIR)/test_registry $(BINDIR)/test_ws $(BINDIR)/test_agent $(BINDIR)/test_channel $(BINDIR)/test_cli $(BINDIR)/test_shell $(BINDIR)/test_file $(BINDIR)/test_telegram $(BINDIR)/test_discord_helpers $(BINDIR)/test_web_search $(BINDIR)/test_cron $(BINDIR)/test_context $(BINDIR)/test_manifest_build $(BINDIR)/test_manifest_keys $(BINDIR)/test_jcs $(BINDIR)/test_asap_envelope $(BINDIR)/test_asap_ulid $(BINDIR)/test_asap_client $(BINDIR)/test_asap_registry $(BINDIR)/test_asap_server $(BINDIR)/test_asap_invoke $(BINDIR)/test_asap_log $(BINDIR)/test_auth $(BINDIR)/test_gateway_http $(BINDIR)/test_static $(BINDIR)/test_sandbox $(BINDIR)/test_allowlist $(BINDIR)/test_rate_limit
Expand Down
10 changes: 6 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Shell commands run in a **Linux sandbox** (namespaces + cgroups v2). Hardware to
| `channels/` | Inbound/outbound I/O | CLI, Telegram, Discord, WebChat, heartbeat |
| `gateway/` | Embedded HTTP/WebSocket server, pairing auth, rate limits, static Web UI | `http_lws`, `routes.c`, `routes_hardware.c` |
| `asap/` | Protocol client/server, envelope, ULID, registry cache, signed manifest | `manifest_build_signed_json()`, `POST /asap` |
| `sandbox/` | Process isolation for shell tool | `sandbox_run()` — `unshare(CLONE_NEWNS\|NEWNET\|NEWPID)`, no `pivot_root` |
| `sandbox/` | Process isolation for shell tool | `sandbox_exec()` — user ns + `unshare(CLONE_NEWNS\|NEWNET\|NEWPID)` then fork for PID 1, Landlock workspace bound, no `pivot_root` |
| `hardware/` | Board abstraction: GPIO (libgpiod), I2C (`/dev/i2c-N`), camera (fixed-argv CLI spawn) | `hardware_init()`, `board_detect()` |
| `crypto/` | Ed25519 signing + JCS canonicalization for manifests | `manifest_keys_ensure_loaded()` (lazy on manifest GET), `jcs.c` |

Expand Down Expand Up @@ -137,9 +137,11 @@ v1.0 ships static manifest discovery; live cross-agent HTTP and full compliance

Linux path (`src/sandbox/sandbox.c`):

- Child: `unshare(CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWPID)` then `exec` shell command.
- **No** `mount()`, bind-mount, or `pivot_root()` — GPU device nodes and Argus socket are not injected.
- Allowlist in `src/sandbox/allowlist.c` blocks dangerous paths and Jetson GPU `/dev` literals.
- Isolator: user namespace when unprivileged, then `unshare(CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWPID)`, then **fork** so the command is PID 1.
- Isolation failure is fail-closed via a control pipe (not `sh` exit 122/123).
- Landlock workspace bound when `workspace_path` is set (RW workspace, RO `/bin` `/usr` `/lib*`, RW `/dev/null`).
- **No** `pivot_root()` — GPU device nodes are not granted by Landlock; the allowlist still blocks `/dev/nv*` literals.
- Allowlist in `src/sandbox/allowlist.c` is defense-in-depth (quoted paths, `$HOME`/`$PWD`, `file:` URLs).
- cgroups v2: `memory.max`, `cpu.max` when writable under `/sys/fs/cgroup`.

Non-Linux: plain `fork`/`exec` with a stderr warning.
Expand Down
Loading
Loading