Rebrand the project from Fence to Greywall, the sandboxing layer of the GreyHaven platform. This updates: - Go module path to gitea.app.monadical.io/monadical/greywall - Binary name, CLI help text, and all usage examples - Config paths (~/.config/greywall/greywall.json), env vars (GREYWALL_*) - Log prefixes ([greywall:*]), temp file prefixes (greywall-*) - All documentation, scripts, CI workflows, and example files - README rewritten with GreyHaven branding and Fence attribution Directory/file renames: cmd/fence → cmd/greywall, pkg/fence → pkg/greywall, docs/why-fence.md → docs/why-greywall.md, example JSON files, and banner.
9.3 KiB
Configuration
Greywall reads settings from ~/.config/greywall/greywall.json by default (or ~/Library/Application Support/greywall/greywall.json on macOS). Legacy ~/.greywall.json is also supported. Pass --settings ./greywall.json to use a custom path. Config files support JSONC.
Example config:
{
"network": {
"allowedDomains": ["github.com", "*.npmjs.org", "registry.yarnpkg.com"],
"deniedDomains": ["evil.com"]
},
"filesystem": {
"denyRead": ["/etc/passwd"],
"allowWrite": [".", "/tmp"],
"denyWrite": [".git/hooks"]
},
"command": {
"deny": ["git push", "npm publish"]
},
"ssh": {
"allowedHosts": ["*.example.com"],
"allowedCommands": ["ls", "cat", "grep", "tail", "head"]
}
}
Config Inheritance
You can extend built-in templates or other config files using the extends field. This reduces boilerplate by inheriting settings from a base and only specifying your overrides.
Extending a template
{
"extends": "code",
"network": {
"allowedDomains": ["private-registry.company.com"]
}
}
This config:
- Inherits all settings from the
codetemplate (LLM providers, package registries, filesystem protections, command restrictions) - Adds
private-registry.company.comto the allowed domains list
Extending a file
You can also extend other config files using absolute or relative paths:
{
"extends": "./base-config.json",
"network": {
"allowedDomains": ["extra-domain.com"]
}
}
{
"extends": "/etc/greywall/company-base.json",
"filesystem": {
"denyRead": ["~/company-secrets/**"]
}
}
Relative paths are resolved relative to the config file's directory. The extended file is validated before merging.
Detection
The extends value is treated as a file path if it contains / or \, or starts with .. Otherwise it's treated as a template name.
Merge behavior
- Slice fields (domains, paths, commands) are appended and deduplicated
- Boolean fields use OR logic (true if either base or override enables it)
- Integer fields (ports) use override-wins semantics (0 keeps base value)
Chaining
Extends chains are supported—a file can extend a template, and another file can extend that file. Circular extends are detected and rejected. Maximum chain depth is 10.
See templates.md for available templates.
Network Configuration
| Field | Description |
|---|---|
allowedDomains |
List of allowed domains. Supports wildcards like *.example.com |
deniedDomains |
List of denied domains (checked before allowed) |
allowUnixSockets |
List of allowed Unix socket paths (macOS) |
allowAllUnixSockets |
Allow all Unix sockets |
allowLocalBinding |
Allow binding to local ports |
allowLocalOutbound |
Allow outbound connections to localhost, e.g., local DBs (defaults to allowLocalBinding if not set) |
httpProxyPort |
Fixed port for HTTP proxy (default: random available port) |
socksProxyPort |
Fixed port for SOCKS5 proxy (default: random available port) |
Wildcard Domain Access
Setting allowedDomains: ["*"] enables relaxed network mode:
- Direct network connections are allowed (sandbox doesn't block outbound)
- Proxy still runs for apps that respect
HTTP_PROXY deniedDomainsis only enforced for apps using the proxy
Warning
Security tradeoff: Apps that ignore
HTTP_PROXYwill bypassdeniedDomainsfiltering entirely.
Use this when you need to support apps that don't respect proxy environment variables.
Filesystem Configuration
| Field | Description |
|---|---|
denyRead |
Paths to deny reading (deny-only pattern) |
allowWrite |
Paths to allow writing |
denyWrite |
Paths to deny writing (takes precedence) |
allowGitConfig |
Allow writes to .git/config files |
Command Configuration
Block specific commands from being executed, even within command chains.
| Field | Description |
|---|---|
deny |
List of command prefixes to block (e.g., ["git push", "rm -rf"]) |
allow |
List of command prefixes to allow, overriding deny |
useDefaults |
Enable default deny list of dangerous system commands (default: true) |
Example:
{
"command": {
"deny": ["git push", "npm publish"],
"allow": ["git push origin docs"]
}
}
Default Denied Commands
When useDefaults is true (the default), greywall blocks these dangerous commands:
- System control:
shutdown,reboot,halt,poweroff,init 0/6 - Kernel manipulation:
insmod,rmmod,modprobe,kexec - Disk operations:
mkfs*,fdisk,parted,dd if= - Container escape:
docker run -v /:/,docker run --privileged - Namespace escape:
chroot,unshare,nsenter
To disable defaults: "useDefaults": false
Command Detection
Greywall detects blocked commands in:
- Direct commands:
git push origin main - Command chains:
ls && git pushorls; git push - Pipelines:
echo test | git push - Shell invocations:
bash -c "git push"orsh -lc "ls && git push"
SSH Configuration
Control which SSH commands are allowed. By default, SSH uses allowlist mode for security - only explicitly allowed hosts and commands can be used.
| Field | Description |
|---|---|
allowedHosts |
Host patterns to allow SSH connections to (supports wildcards like *.example.com, prod-*) |
deniedHosts |
Host patterns to deny SSH connections to (checked before allowed) |
allowedCommands |
Commands allowed over SSH (allowlist mode) |
deniedCommands |
Commands denied over SSH (checked before allowed) |
allowAllCommands |
If true, use denylist mode instead of allowlist (allow all commands except denied) |
inheritDeny |
If true, also apply global command.deny rules to SSH commands |
Basic Example (Allowlist Mode)
{
"ssh": {
"allowedHosts": ["*.example.com"],
"allowedCommands": ["ls", "cat", "grep", "tail", "head", "find"]
}
}
This allows:
- SSH to any
*.example.comhost - Only the listed commands (and their arguments)
- Interactive sessions (no remote command)
Denylist Mode Example
{
"ssh": {
"allowedHosts": ["dev-*.example.com"],
"allowAllCommands": true,
"deniedCommands": ["rm -rf", "shutdown", "chmod"]
}
}
This allows:
- SSH to any
dev-*.example.comhost - Any command except the denied ones
Inheriting Global Denies
{
"command": {
"deny": ["shutdown", "reboot", "rm -rf /"]
},
"ssh": {
"allowedHosts": ["*.example.com"],
"allowAllCommands": true,
"inheritDeny": true
}
}
With inheritDeny: true, SSH commands also check against:
- Global
command.denylist - Default denied commands (if
command.useDefaultsis true)
Host Pattern Matching
SSH host patterns support wildcards anywhere:
| Pattern | Matches |
|---|---|
server1.example.com |
Exact match only |
*.example.com |
Any subdomain of example.com |
prod-* |
Any hostname starting with prod- |
prod-*.us-east.* |
Multiple wildcards |
* |
All hosts |
Evaluation Order
- Check if host matches
deniedHosts→ DENY - Check if host matches
allowedHosts→ continue (else DENY) - If no remote command (interactive session) → ALLOW
- Check if command matches
deniedCommands→ DENY - If
inheritDeny, check globalcommand.deny→ DENY - If
allowAllCommands→ ALLOW - Check if command matches
allowedCommands→ ALLOW - Default → DENY
Other Options
| Field | Description |
|---|---|
allowPty |
Allow pseudo-terminal (PTY) allocation in the sandbox (for MacOS) |
Importing from Claude Code
If you've been using Claude Code and have already built up permission rules, you can import them into greywall:
# Preview import (prints JSON to stdout)
greywall import --claude
# Save to the default config path
greywall import --claude --save
# Import from a specific file
greywall import --claude -f ~/.claude/settings.json --save
# Save to a specific output file
greywall import --claude -o ./greywall.json
# Import without extending any template (minimal config)
greywall import --claude --no-extend --save
# Import and extend a different template
greywall import --claude --extend local-dev-server --save
Default Template
By default, imports extend the code template which provides sensible defaults:
- Network access for npm, GitHub, LLM providers, etc.
- Filesystem protections for secrets and sensitive paths
- Command restrictions for dangerous operations
Use --no-extend if you want a minimal config without these defaults, or --extend <template> to choose a different base template.
Permission Mapping
| Claude Code | Greywall |
|---|---|
Bash(xyz) allow |
command.allow: ["xyz"] |
Bash(xyz:*) deny |
command.deny: ["xyz"] |
Read(path) deny |
filesystem.denyRead: [path] |
Write(path) allow |
filesystem.allowWrite: [path] |
Write(path) deny |
filesystem.denyWrite: [path] |
Edit(path) |
Same as Write(path) |
ask rules |
Converted to deny (greywall doesn't support interactive prompts) |
Global tool permissions (e.g., bare Read, Write, Grep) are skipped since greywall uses path/command-based rules.
See Also
- Config templates:
docs/templates/ - Workflow guides:
docs/recipes/