Sheld (from "shell" and "shield") allows you to define sandbox profiles (user global or directory local) for different commands and automatically wraps them using Bubblewrap when executed. Full integration is available for bash, zsh, and fish.
⚠ Alpha software: Sheld is alpha software, so breaking changes will happen.
Sheld is designed for these sandboxing use cases:
- Development environment isolation: Your dev environment contains hundreds or thousands of dependencies from different package managers and different projects. Each dependency can execute arbitrary code during installation or runtime.
- AI-powered development tools: AI coding assistants execute shell commands autonomously. They can make mistakes, be manipulated by prompt injection, or accidentally expose secrets.
- Tools with MCP server: Model Context Protocol (MCP) servers are executables that AI assistants call to access filesystems, databases, APIs, and other resources. Third-party MCP servers run with your user permissions and can be exploited through prompt injection.
- 🎯 File-based config: Per-command sandboxing rules in YAML
- 📁 Hierarchical config: Global defaults + per-project overrides
- 🔒 Secure by default: Commands run fully isolated unless explicitly allowed
- 🔄 Shell integration: Configured commands work like normal commands
Build from source:
git clone https://github.com/pierrelegall/sheld.git
cd sheld
cargo build --releaseInitialize a configuration file:
sheld initEdit the .sheld.yaml file to define your command wraps:
node:
share:
- network
bind:
- ~/.node_modules
- [$PWD, /workspace]
ro_bind:
- /usr
- /libRun commands manually with:
sheld wrap node script.jsShell hooks allow automatic command wrapping. To set it up, do:
# For Bash
eval "$(sheld activate bash)"
# For Zsh
eval "$(sheld activate zsh)"
# For Fish
sheld activate fish | sourceThen, all configured commands will be hooked into a wrapped command, like below:
# Will run `sheld wrap node script.js`
node script.jsConfigured commands can now be bypassed with:
# Wrapping bypassed
sheld bypass node script.jsFor more information about the CLI, run sheld help.
# Define reusable models
base:
type: model # Mark this as a model (not a command)
share:
- user
ro_bind:
- /usr
- /lib
network:
type: model
share:
- network
# Define command-specific configurations
node:
includes: base # Optional: include a single model
# OR
includes: [base, network] # Optional: include multiple models (applied in order)
enabled: true # Optional: enable this command (default: true)
override: false # Optional: false=deep merge with parent, true=replace parent (default: false)
bind: # Read-write mounts
- ~/.node_modules
- [$PWD, /workspace]
ro_bind: # Read-only mounts
- /etc/resolv.conf
dev_bind: # Device bind mounts
- /dev/null
bind_try: # Optional: bind mounts that won't fail if source doesn't exist
- ~/.cache
ro_bind_try: # Optional: read-only bind-try mounts
- /usr/share/fonts
dev_bind_try: # Optional: device bind-try mounts
- /dev/kvm
tmpfs: # Temporary filesystems
- /tmp
chdir: /workspace # Optional: working directory inside sandbox
die_with_parent: true # Optional: kill process when parent dies (default: false)
new_session: false # Optional: create new terminal session (default: false)
cap: # Optional: add Linux capabilities (bwrap drops all by default)
- CAP_SYS_ADMIN
- CAP_NET_ADMIN
env: # Set environment variables
NODE_ENV: production
unset_env: # Unset environment variables
- DEBUGSheld merges configuration files to combine user global defaults with project-specific settings:
- User:
~/.config/sheld/default.yaml- Global baseline configuration - Local:
.sheld.yamlin current directory or parent directories - Project-specific overrides
When both files exist, they are merged with local entries taking precedence:
- Commands/models with the same name:
override: false(default): Deep merge (parent + child settings combined)override: true: Child completely replaces parent
- Distinct commands/models: both are included
- Local
enabled: false: use user version instead (skip local override) - Local commands can extend models defined in user config
- Deep merge behavior:
- Arrays: Parent items first, then unique child items (deduplicated)
- env HashMap: Parent + child, child wins on key conflicts
- Scalar fields: child value wins
By default, all namespaces are unshared following the principle of least privilege. Use share to selectively allow namespaces:
network- Network accessuser- User/group IDspid- Process IDsipc- Inter-process communicationuts- Hostnamecgroup- Control groups
If you want a command to behave exactly like bwrap's default (sharing all namespaces), configure it like this:
fucmd:
share:
- user # Same user/group IDs as the host
- network # Full network access (same as host)
- pid # Visibility to all host processes
- ipc # Access to host IPC mechanisms
- uts # Same hostname as the host
- cgroup # Access to host cgroupsOr use the model system:
share_all_namespaces:
type: model
share:
- user # Same user/group IDs as the host
- network # Full network access (same as host)
- pid # Visibility to all host processes
- ipc # Access to host IPC mechanisms
- uts # Same hostname as the host
- cgroup # Access to host cgroups
fucmd:
includes: share_all_namespaces
barcmd:
includes: share_all_namespacesContributions are welcome! Please feel free to submit issues or pull requests.
Copyright © 2025 Pierre Le Gall