- Host filesystem (read/write/execute)
- Host network (internal/external)
- Host process execution
- Plugin integrity (supply chain)
- User data in plugin tabs
- Malicious plugin author: Publishes plugin to registry
- Compromised registry: GitHub repo / CDN hijacked
- Local attacker: Code execution on host machine
- Network attacker: MITM on plugin download
- Linear memory isolation (no host pointer access)
- No direct syscall instruction execution
- All host interaction via explicit FFI imports
Env namespace (env.*):
- Every import checked against manifest
permissions[] - Missing permission → load failure
- 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
WASI namespace (wasi_snapshot_preview1.*):
- Explicit allowlist only (see
plugin_mgr.rs:ALLOWED_WASI) - 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. - Allowed:
fd_write/fd_read(stdout/stderr only),proc_exit,clock_time_get,args_*,environ_*(stubs),poll_oneoff,sched_yield,sock_*(stubs returning ENOSYS)
Each sensitive import re-checks permission before executing:
if !env_data.permissions.iter().any(|p| p == "host_exec") {
print_error("[SECURITY] Plugin attempted to call host_exec without permission");
return;
}- No shell: Direct
Command::new(exe).args(args)— nocmd /c,sh -c - Allowlist-only: Manifest
allowed_commandswith canonical path + args regex - Path canonicalization:
resolve_binary_path()→fs::canonicalize() - No blacklist: Blacklists are bypassable; removed entirely
- HTTPS only (scheme validation via
url::Url) - Private IP blocking (RFC1918, RFC3927, RFC6598, loopback, multicast, reserved)
- Hostname blocking:
localhost,localhost.localdomain - Response size limit: 1 MiB
- Timeout: 30s (configurable via
DEFAULT_TIMEOUT)
cdcommand:canonicalize()+ prefix check against process CWD- No WASI
path_*functions exposed - Plugin working directory tracked per-tab (
TAB_CWDS)
- Registry (
pluglists.json) signed with minisign/Ed25519 - Public key baked into binary (
REGISTRY_PUBKEY) - Signature verified before parsing any registry content
- Plugin WASM verified against registry-pinned SHA256
- Atomic write with same-FS verification (
write_atomic)
- All FFI string reads bounded by constants:
MAX_FFI_STRING_LEN = 64 KiBMAX_URL_LEN = 2 KiBMAX_JSON_PAYLOAD_LEN = 16 KiBMAX_RESPONSE_BUF_LEN = 1 MiBMAX_TAB_LABEL_LEN = 256 B
- Prevents OOB reads and allocation DoS
| Risk | Mitigation | Residual |
|---|---|---|
| WASI stubs return ENOSYS | Plugins expecting real syscalls fail gracefully | Low (breaks compat, not security) |
| Allowlist regex ReDoS | regex crate is linear-time (no backtracking) |
Low |
| Registry key rotation | Not implemented; requires binary rebuild | Medium |
Side-channel via host_get_platform |
Now permission-gated | Low |
TOCTOU in write_atomic cross-FS |
Same-FS check + randomized temp name | Low |
| Malicious plugin DoS (infinite loop) | No fuel metering / epoch interruption | Medium |
| Memory exhaustion via large allocations | MAX_FFI_STRING_LEN bounds; Wasmer memory limit not set |
Medium |
- Manifest declares minimal permissions
-
allowed_commandsuses canonical paths, restrictive regex - No WASI imports beyond allowlist (verify with
wasm-objdump -x plugin.wasm | grep wasi_snapshot_preview1) -
net_postURLs are HTTPS, external domains only - Plugin does not attempt
cdtraversal - SHA256 in registry matches published WASM
- Malicious plugin detected: Revoke registry entry, rotate minisign key, rebuild host
- Registry compromise: Rotate minisign key immediately, audit all plugins
- Sandbox escape: Isolate host, analyze WASM module, patch Wasmer/import gate