This document describes the security model, sandboxing mechanisms, and permission policies implemented in OpenSkills Runtime.
OpenSkills Runtime implements a defense-in-depth security model with multiple layers:
- Native Script Sandbox (Primary) - OS-level sandboxing (Seatbelt on macOS) for Python/Shell scripts - production-ready
- WASM Sandbox (Experimental) - Capability-based isolation for WASM modules (WASI 0.3) - available for specific use cases
- Permission Enforcement - Tool-based access control via
allowed-toolsand risky tool detection - Context Isolation - Forked contexts prevent skill output pollution
Note: Native scripts via seatbelt are the primary and recommended execution method. WASM sandboxing is experimental.
Status: Experimental feature. Native scripts are the primary execution method.
WASM modules execute in a capability-based sandbox using WASI 0.3 (WASIp3) with the component model. This is available for specific use cases requiring determinism, but is not suitable for full Python ecosystem or native libraries.
See README.md for detailed discussion of WASM's role and limitations.
Read Access:
- Default: No read access
- Granted based on
allowed-tools:Read,Grep,Glob,LS→ Read access to skill root directoryBash,Terminal→ Read access to skill root directory
- Can be extended via skill manifest
wasm.filesystem.readconfiguration
Write Access:
- Default: No write access
- Granted based on
allowed-tools:Write,Edit,MultiEdit→ Write access to skill root directoryBash,Terminal→ Write access to skill root directory
- Can be extended via skill manifest
wasm.filesystem.writeconfiguration
Path Resolution:
- Relative paths are resolved relative to the skill root directory
- Absolute paths are used as-is (if allowed)
Default: No network access
Granted when:
allowed-toolsincludesWebSearchorFetch- Skill manifest
wasm.network.allowspecifies allowed hosts
Host Matching:
- Exact host match:
api.example.com - Subdomain match:
*.example.commatchessub.api.example.com - Wildcard
*allows all hosts (whenWebSearchorFetchtools are used)
Default: No environment variables exposed
Granted via:
- Skill manifest
wasm.env.allowlist
- Memory: Default 128MB, configurable via
wasm.memory_mb - Timeout: Default 30 seconds, configurable via
wasm.timeout_ms - Random Seed: Optional deterministic seed via
wasm.random_seed
Status: Production-ready, primary execution method.
Native Python and Shell scripts execute under macOS Seatbelt sandbox profiles following Claude Code's security model. This is the recommended approach for most skills, providing full access to the Python ecosystem and native tools.
The seatbelt profile uses a "allow broad reads, deny specific sensitive paths" approach:
- Deny default - All operations denied by default
- Allow broad file reads - Python and interpreters need system library access
- Deny specific sensitive paths - Credentials and config files explicitly blocked
- Allow writes only to specific paths - Temp directories, skill root, configured paths
All native scripts receive these base permissions:
sysctl-read- System information queriesprocess-exec- Execute the interpreter binaryprocess-fork- Fork child processesmach-lookup- Mach port lookups (required for process execution)signal- Signal handling
Allowed:
- Broad read access (
allow file-read*) - Required for Python/interpreters to access:- System libraries (
/usr/lib,/System/Library) - Python frameworks (
/Library/Frameworks/Python.framework) - Homebrew installations (
/opt/homebrew) - Standard system paths (
/usr/bin,/bin,/sbin) - User directories (
/Users) - Temporary directories (
/tmp,/private/tmp)
- System libraries (
Explicitly Denied (Sensitive Paths):
~/.ssh # SSH keys
~/.gnupg # GPG keys
~/.aws # AWS credentials
~/.azure # Azure credentials
~/.config/gcloud # Google Cloud credentials
~/.kube # Kubernetes config
~/.docker # Docker config
~/.npmrc # npm credentials
~/.pypirc # PyPI credentials
~/.netrc # Network credentials
~/.gitconfig # Git config
~/.git-credentials # Git credentials
~/.bashrc # Shell config
~/.zshrc # Shell config
~/.profile # Shell config
~/.bash_profile # Shell config
~/.zprofile # Shell config
Note: ~ is expanded to the user's home directory at runtime.
Allowed:
/dev/null- Output redirection- Temporary directories:
/tmp/private/tmp/private/var/tmp/private/var/folders
- Skill root directory (where the skill is located)
- Explicitly configured write paths (from skill manifest)
Denied:
- All other paths (including system directories, user home, etc.)
Default: No process execution
Granted when:
- Script type is
ShellorPython(interpreter execution required) allowed-toolsincludesBashorTerminal- Full
process*permissions granted (allows subprocess spawning)
Default: No network access
Granted when:
allowed-toolsincludesWebSearchorFetchallow network*added to seatbelt profile
Skills can restrict which tools they're allowed to use via the allowed-tools field in their manifest:
allowed-tools:
- Read
- Grep
- GlobBehavior:
- Empty list = All tools allowed (no restriction)
- Non-empty list = Only listed tools allowed
- Tool calls for unlisted tools are denied with
PermissionDeniederror
Some tools are classified as "risky" and require explicit permission via a callback:
Low Risk:
Read,Grep,Glob,LS- Read-only operations
Medium Risk:
Write,Edit,MultiEdit- File modificationWebSearch,Fetch- Network access
High Risk:
Bash,Terminal- Arbitrary command executionDelete- File deletion
Permission Flow:
- Agent calls
check_tool_permission(skill_id, tool, description) - Runtime checks if tool is in
allowed-tools(if list is non-empty) - If tool is risky, runtime calls permission callback:
DenyAllCallback- Always denies (strict mode)CliPermissionCallback- Prompts user for approval- Custom callback - User-defined logic
- Returns
trueif allowed,falseor error if denied
Tools are mapped to WASI capabilities as follows:
| Tool | Filesystem Read | Filesystem Write | Network |
|---|---|---|---|
Read, Grep, Glob, LS |
✅ Skill root | ❌ | ❌ |
Write, Edit, MultiEdit |
✅ Skill root | ✅ Skill root | ❌ |
Bash, Terminal |
✅ Skill root | ✅ Skill root | ❌ |
WebSearch, Fetch |
❌ | ❌ | ✅ All hosts |
Skills with context: fork execute in isolated contexts:
Isolation:
- Tool calls recorded in forked context
- Intermediate outputs (stdout, stderr) captured separately
- Only summary returned to parent context
- Prevents context pollution from verbose tool outputs
Summary Generation:
- Extracts only
Resulttype outputs - Excludes
ToolCall,Stdout,Stderrfrom summary - Falls back to stdout if no explicit results recorded
Use Case:
- Instruction-only skills (no WASM module or native script)
- Agent executes tools and records outputs
- Final summary returned to parent agent
✅ Credentials - SSH keys, AWS/Azure/GCP credentials, API keys
✅ Config Files - Git config, shell configs, Docker/Kubernetes configs
✅ System Directories - Write access to system paths denied
✅ Network - No network access unless explicitly allowed
✅ Process Execution - Limited to required interpreters unless Bash/Terminal allowed
✅ System Libraries - Read access for interpreter execution
✅ Skill Directory - Read/write access to skill root
✅ Temporary Files - Write access to /tmp and variants
✅ Standard Input/Output - /dev/null for redirection
All skill executions are logged with:
- Skill ID and version
- Input/Output hashes (SHA-256)
- Permissions used (tools, filesystem paths, network hosts)
- Execution status (success, timeout, permission denied, failed)
- Timing (start time, duration)
- Outputs (stdout, stderr)
Audit records are sent to the configured audit sink (default: no-op sink).
- Minimize
allowed-tools- Only request tools you actually need - Avoid risky tools - Prefer
ReadoverBashwhen possible - Use native scripts - Recommended for most skills; full Python ecosystem access
- Specify filesystem paths - For WASM (experimental): use
wasm.filesystem.read/writefor minimal access - Restrict network - For WASM (experimental): use
wasm.network.allowwith specific hosts, not* - Use
context: fork- For instruction-only skills to prevent context pollution
- Review
allowed-tools- Understand what each skill can do - Configure permission callbacks - Use
CliPermissionCallbackfor interactive approval - Monitor audit logs - Review execution records for suspicious activity
- Trust skill sources - Only load skills from trusted repositories
The seatbelt profile is generated dynamically based on:
- Skill root directory
- Configured read/write paths
allowed-tools(for network/process permissions)- Script type (Python/Shell)
Profile follows this structure:
(version 1)
(deny default)
(allow sysctl-read)
(allow process-exec)
(allow process-fork)
(allow mach-lookup)
(allow signal)
(allow file-read*)
(deny file-read* (subpath "~/.ssh"))
... (more sensitive path denials)
(allow file-write* (subpath "/tmp"))
... (more write path allowances)
(allow process*) # if Bash/Terminal allowed
(allow network*) # if WebSearch/Fetch allowed
WASM modules receive preopened directories via WASI 0.3:
- Read-only directories: Mapped to skill root or configured read paths
- Write directories: Mapped to skill root or configured write paths
- No access to parent directories or system paths
- Architecture - Overall system design
- Skill Flow - Execution workflows
- Developers Guide - API usage examples
Last Updated: 2026-01-18