Skip to content

Commit ad2e380

Browse files
committed
J7F9A6
1 parent 8f3f678 commit ad2e380

25 files changed

Lines changed: 732 additions & 175 deletions

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111

1212
## Features
1313

14-
- **Sandboxed WASM Runtime**: Isolated memory execution using the Wasmer compiler. Note that the sandbox confines the plugin's memory space and relies on a host FFI permission gate for system capabilities.
15-
- **Granular Permissions Model**: Checks plugin FFI import access (e.g., tab management or host execution) against manifest declarations (`plugin.toml`).
14+
- **Sandboxed WASM Runtime**: Isolated memory execution using Wasmer 4.3 (Cranelift). WASI preview1 imports are explicitly allowlisted — only a safe subset of 48 syscalls exposed; dangerous ones (filesystem, network, process) blocked.
15+
- **Granular Permissions Model**: Enforces manifest-declared permissions at load time (import validation) AND call time (runtime gate) for ALL host imports including `host_get_platform`. WASI imports blocked unless in explicit allowlist.
16+
- **Supply Chain Integrity**: Plugin registry (`pluglists.json`) verified via minisign/Ed25519 signature before any content trusted.
17+
- **SSRF Protection**: `net_post` enforces HTTPS-only, blocks private/reserved IPs (RFC1918, loopback, link-local), limits response to 1 MiB.
18+
- **Path Containment**: `cd` command restricted to current working directory jail; traversal escapes blocked.
1619
- **Multitab Desktop Shell**: Launch and run multiple independent plugins concurrently in separate workspace tabs.
1720
- **Cross-Platform Native UI**: Compiles to Windows (Win32 GDI) and Linux (GTK4) with zero browser engine footprint.
1821

@@ -84,3 +87,17 @@ pub extern "C" fn run() {
8487
```
8588

8689
Refer to [docs/PLUGIN_DEVELOPMENT.md](docs/PLUGIN_DEVELOPMENT.md) for details on permissions configuration (`plugin.toml`) and FFI imports.
90+
91+
---
92+
93+
## Security Architecture
94+
95+
See [docs/security.md](docs/security.md) for the complete threat model, sandbox architecture, and security controls summary.
96+
97+
### Key Guarantees
98+
- **No shell invocation**`host_exec` uses direct `execvp`/`CreateProcess`
99+
- **Allowlist-only execution** — manifest `allowed_commands` with canonical path + args regex
100+
- **WASI allowlist** — only 48 safe syscalls exposed; `path_open`, `sock_connect`, `proc_raise` etc. blocked
101+
- **Registry signature verification** — minisign/Ed25519 baked pubkey
102+
- **SSRF defense** — HTTPS only, private IP blocking, response size limit
103+
- **Input bounds** — all FFI string reads capped (64 KiB general, 2 KiB URL, 16 KiB JSON, 1 MiB response)

docs/ARCHITECTURE.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,16 @@ The framework splits operations into two layers connected via C FFI:
2828

2929
Inputs starting with `/` invoke built-in commands (e.g., `/tab` or `/plug`). Other inputs are dispatched to the WASM plugin owning the active tab.
3030

31-
If a plugin requests access to sensitive FFI imports (such as `host_exec` to run local shell subprocesses, `get_env` to read environment variables, or `net_post` for outbound HTTP), the runtime validates its manifest permissions list (`plugin.toml`) at load time and again at call time before delegating to the host OS.
31+
If a plugin requests access to sensitive FFI imports (such as `host_exec` to run local shell subprocesses, `get_env` to read environment variables, `net_post` for outbound HTTP, or `host_get_platform` to detect host OS), the runtime validates its manifest permissions list (`plugin.toml`) at load time (import validation) and again at call time (runtime gate) before delegating to the host OS.
32+
33+
### WASI Sandbox Enforcement
34+
The `wasi_snapshot_preview1` import namespace is **not** automatically exposed. Only an explicit allowlist of safe WASI functions is provided (see `plugin_mgr.rs:ALLOWED_WASI`). Dangerous syscalls (`path_open`, `sock_connect`, `proc_raise`, `random_get`, etc.) are blocked at module load time regardless of manifest.
35+
36+
### Supply Chain Security
37+
Plugin registry (`pluglists.json`) is verified via minisign/Ed25519 signature (public key baked in binary) before any plugin metadata or hashes are trusted. Downloaded WASM is verified against registry-pinned SHA256.
38+
39+
### Network Hardening
40+
`net_post` enforces: HTTPS-only scheme, private/reserved IP blocking (RFC1918, loopback, link-local, multicast), 1 MiB response size limit, 30s timeout.
41+
42+
### Filesystem Containment
43+
`cd` command handler resolves target via `canonicalize` then verifies result stays within process CWD jail. Traversal attempts (`../../etc`) are rejected.

docs/PLUGIN_DEVELOPMENT.md

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ version = "1.0.0"
3939
author = "Dev"
4040
api_version = "xxx"
4141
permissions = [
42-
"host_exec", # Spawning local commands (CMD / PowerShell / shell)
43-
"host_add_tab", # Spawning new tabs
44-
"host_set_tab_owner", # Taking tab ownership
45-
"host_get_tab_label", # Reading active tab label
46-
"get_env", # Reading host environment variables
47-
"net_post" # HTTP POST requests from plugin runtime
42+
"host_exec", # Spawning local commands (allowlist-only, no shell)
43+
"host_add_tab", # Spawning new tabs
44+
"host_set_tab_owner", # Taking tab ownership
45+
"host_get_tab_label", # Reading active tab label
46+
"host_get_platform", # Detecting host OS (0=Windows, 1=Linux)
47+
"get_env", # Reading host environment variables
48+
"net_post" # HTTP POST requests (HTTPS only, no private IPs)
4849
]
4950
```
5051

@@ -67,3 +68,34 @@ rustc --target wasm32-unknown-unknown \
6768
-C strip=symbols \
6869
-C link-arg=--allow-undefined
6970
```
71+
72+
73+
---
74+
75+
## Security Notes
76+
77+
### WASI Imports
78+
The runtime **does not** expose the full WASI preview1 API. Only a safe subset is available (stdin/stdout/stderr, clocks, args, env stubs). Attempting to import blocked WASI functions (`path_open`, `sock_connect`, `proc_raise`, etc.) will cause plugin load failure.
79+
80+
### Command Execution (`host_exec`)
81+
- No shell interpretation: commands run via `execvp`/`CreateProcess` with argv array directly
82+
- Allowlist-only: manifest must declare `allowed_commands` with canonical path + args regex
83+
- Example:
84+
```toml
85+
[[plugin]]
86+
# ... other fields ...
87+
allowed_commands = [
88+
{ path = "/usr/bin/git", args_pattern = "^(status|log|diff)" },
89+
{ path = "C:\\Program Files\\Git\\bin\\git.exe", args_pattern = "^(status|log|diff)" }
90+
]
91+
```
92+
93+
### Network (`net_post`)
94+
- HTTPS only (HTTP rejected)
95+
- Private IPs blocked (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16, link-local, multicast)
96+
- Response capped at 1 MiB
97+
- 30 second timeout
98+
99+
### Filesystem
100+
- `cd` command restricted to current working directory jail
101+
- No direct filesystem WASI access exposed to plugins

docs/security.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Security Model & Threat Analysis
2+
3+
## Threat Model
4+
5+
### Assets
6+
- Host filesystem (read/write/execute)
7+
- Host network (internal/external)
8+
- Host process execution
9+
- Plugin integrity (supply chain)
10+
- User data in plugin tabs
11+
12+
### Actors
13+
- **Malicious plugin author**: Publishes plugin to registry
14+
- **Compromised registry**: GitHub repo / CDN hijacked
15+
- **Local attacker**: Code execution on host machine
16+
- **Network attacker**: MITM on plugin download
17+
18+
### Trust Boundaries
19+
```
20+
┌─────────────────────────────────────────────────────┐
21+
│ HOST OS │
22+
│ ┌─────────────────────────────────────────────┐ │
23+
│ │ PLUG RUNTIME (Rust) │ │
24+
│ │ ┌─────────────────────────────────────┐ │ │
25+
│ │ │ │ │ WASM SANDBOX (Wasmer) │ │ │
26+
│ │ │ - Linear memory (isolated) │ │ │
27+
│ │ │ - No direct syscalls │ │ │
28+
│ │ │ - Host imports ONLY via FFI gate │ │ │
29+
│ └─────────────────────────────────────┘ │
30+
│ │ ┌─────────────────────────────────────┐ │ │
31+
│ │ │ PERMISSION GATE │ │ │
32+
│ │ │ - Load-time import validation │ │ │
33+
│ │ │ - Call-time runtime checks │ │ │
34+
│ │ │ - WASI allowlist enforcement │ │ │
35+
│ │ └─────────────────────────────────────┘ │
36+
│ └─────────────────────────────────────────────┘
37+
└─────────────────────────────────────────────────────┘
38+
```
39+
40+
## Security Controls
41+
42+
### 1. WASM Sandbox (Wasmer 4.3 Cranelift)
43+
- Linear memory isolation (no host pointer access)
44+
- No direct syscall instruction execution
45+
- All host interaction via explicit FFI imports
46+
47+
### 2. Import Validation (Load Time)
48+
**Env namespace** (`env.*`):
49+
- Every import checked against manifest `permissions[]`
50+
- Missing permission → load failure
51+
- Imports: `host_exec`, `host_add_tab`, `host_set_tab_owner`, `host_get_tab_label`, `host_get_platform`, `get_env`, `net_post`, `print_info`, `print_error`, `get_args`
52+
53+
**WASI namespace** (`wasi_snapshot_preview1.*`):
54+
- **Explicit allowlist only** (see `plugin_mgr.rs:ALLOWED_WASI`)
55+
- Blocked: `path_open`, `path_readlink`, `path_rename`, `path_unlink_file`, `path_create_directory`, `path_remove_directory`, `path_symlink`, `path_link`, `sock_connect`, `sock_bind`, `sock_listen`, `sock_accept`, `proc_raise`, `random_get` (stubbed), etc.
56+
- Allowed: `fd_write`/`fd_read` (stdout/stderr only), `proc_exit`, `clock_time_get`, `args_*`, `environ_*` (stubs), `poll_oneoff`, `sched_yield`, `sock_*` (stubs returning ENOSYS)
57+
58+
### 3. Runtime Gates (Call Time)
59+
Each sensitive import re-checks permission before executing:
60+
```rust
61+
if !env_data.permissions.iter().any(|p| p == "host_exec") {
62+
print_error("[SECURITY] Plugin attempted to call host_exec without permission");
63+
return;
64+
}
65+
```
66+
67+
### 4. Command Execution Hardening (`host_exec`)
68+
- **No shell**: Direct `Command::new(exe).args(args)` — no `cmd /c`, `sh -c`
69+
- **Allowlist-only**: Manifest `allowed_commands` with canonical path + args regex
70+
- **Path canonicalization**: `resolve_binary_path()``fs::canonicalize()`
71+
- **No blacklist**: Blacklists are bypassable; removed entirely
72+
73+
### 5. Network Hardening (`net_post`)
74+
- HTTPS only (scheme validation via `url::Url`)
75+
- Private IP blocking (RFC1918, RFC3927, RFC6598, loopback, multicast, reserved)
76+
- Hostname blocking: `localhost`, `localhost.localdomain`
77+
- Response size limit: 1 MiB
78+
- Timeout: 30s (configurable via `DEFAULT_TIMEOUT`)
79+
80+
### 6. Filesystem Containment
81+
- `cd` command: `canonicalize()` + prefix check against process CWD
82+
- No WASI `path_*` functions exposed
83+
- Plugin working directory tracked per-tab (`TAB_CWDS`)
84+
85+
### 7. Supply Chain Integrity
86+
- Registry (`pluglists.json`) signed with minisign/Ed25519
87+
- Public key baked into binary (`REGISTRY_PUBKEY`)
88+
- Signature verified before parsing any registry content
89+
- Plugin WASM verified against registry-pinned SHA256
90+
- Atomic write with same-FS verification (`write_atomic`)
91+
92+
### 8. Input Validation
93+
- All FFI string reads bounded by constants:
94+
- `MAX_FFI_STRING_LEN = 64 KiB`
95+
- `MAX_URL_LEN = 2 KiB`
96+
- `MAX_JSON_PAYLOAD_LEN = 16 KiB`
97+
- `MAX_RESPONSE_BUF_LEN = 1 MiB`
98+
- `MAX_TAB_LABEL_LEN = 256 B`
99+
- Prevents OOB reads and allocation DoS
100+
101+
## Known Limitations / Residual Risk
102+
103+
| Risk | Mitigation | Residual |
104+
|------|-----------|----------|
105+
| WASI stubs return ENOSYS | Plugins expecting real syscalls fail gracefully | Low (breaks compat, not security) |
106+
| Allowlist regex ReDoS | `regex` crate is linear-time (no backtracking) | Low |
107+
| Registry key rotation | Not implemented; requires binary rebuild | Medium |
108+
| Side-channel via `host_get_platform` | Now permission-gated | Low |
109+
| TOCTOU in `write_atomic` cross-FS | Same-FS check + randomized temp name | Low |
110+
| Malicious plugin DoS (infinite loop) | No fuel metering / epoch interruption | Medium |
111+
| Memory exhaustion via large allocations | `MAX_FFI_STRING_LEN` bounds; Wasmer memory limit not set | Medium |
112+
113+
## Security Checklist for Plugin Review
114+
115+
- [ ] Manifest declares minimal permissions
116+
- [ ] `allowed_commands` uses canonical paths, restrictive regex
117+
- [ ] No WASI imports beyond allowlist (verify with `wasm-objdump -x plugin.wasm | grep wasi_snapshot_preview1`)
118+
- [ ] `net_post` URLs are HTTPS, external domains only
119+
- [ ] Plugin does not attempt `cd` traversal
120+
- [ ] SHA256 in registry matches published WASM
121+
122+
## Incident Response
123+
124+
1. **Malicious plugin detected**: Revoke registry entry, rotate minisign key, rebuild host
125+
2. **Registry compromise**: Rotate minisign key immediately, audit all plugins
126+
3. **Sandbox escape**: Isolate host, analyze WASM module, patch Wasmer/import gate

0 commit comments

Comments
 (0)