Skip to main content

Workspace confinement

The restrict_to_workspace flag is the primary sandbox boundary. When enabled (the default), the agent cannot read files, write files, or execute shell commands that reference paths outside the configured workspace directory.
{
  "agents": {
    "defaults": {
      "workspace": "~/.operator/workspace",
      "restrict_to_workspace": true
    }
  }
}

What workspace restriction blocks

  • File reads to absolute paths outside the workspace directory
  • File writes to any path outside the workspace directory
  • Shell commands that reference absolute paths outside the workspace
  • Path traversal — any command or path containing ../ or ..\

Allowed exceptions

A small set of kernel pseudo-devices are always permitted regardless of workspace restriction, because they contain no user data and cannot cause destructive writes:
  • /dev/null
  • /dev/zero
  • /dev/random
  • /dev/urandom
  • /dev/stdin
  • /dev/stdout
  • /dev/stderr

Selective read access

If the agent needs to read files outside the workspace (for example, a shared configuration directory) while still blocking writes and arbitrary execution, enable allow_read_outside_workspace:
{
  "agents": {
    "defaults": {
      "restrict_to_workspace": true,
      "allow_read_outside_workspace": true
    }
  }
}
Setting restrict_to_workspace to false removes all filesystem and path restrictions. The agent can read, write, and execute commands anywhere on the host. Only disable workspace restriction in a fully trusted or containerized environment where you control what the agent is asked to do.

Shell command filtering

The tools.exec section controls which shell commands the agent’s exec tool is permitted to run.
{
  "tools": {
    "exec": {
      "enable_deny_patterns": true,
      "custom_deny_patterns": []
    }
  }
}

Default blocked patterns

When enable_deny_patterns is true (the default), the following regex patterns are evaluated against every command before execution. Any match causes the command to be rejected with:
Command blocked by safety guard (dangerous pattern detected)
The exact patterns are compiled from pkg/tools/shell.go:
PatternWhat it blocks
\brm\s+-[rf]{1,2}\brm -rf, rm -r
\bdel\s+/[fq]\bWindows del /f, del /q
\brmdir\s+/s\bWindows rmdir /s
\b(format|mkfs|diskpart)\b\sDisk-wipe commands
\bdd\s+if=dd disk imaging
>\s*/dev/(sd[a-z]|nvme\d|mmcblk\d|...)Writes to block devices
\b(shutdown|reboot|poweroff)\bSystem power commands
:\(\)\s*\{.*\};\s*:Fork bomb :(){ :|: & };:
\$\([^)]+\)$(...) command substitution
\$\{[^}]+\}${...} variable substitution
`[^`]+`Backtick command substitution
|\s*sh\bPipe to sh
|\s*bash\bPipe to bash
;\s*rm\s+-[rf]Chained ; rm -rf
&&\s*rm\s+-[rf]Conditional && rm -rf
||\s*rm\s+-[rf]Conditional || rm -rf
<<\s*EOFHeredoc injection
\$\(\s*cat\s+$(cat ...) substitution
\$\(\s*curl\s+$(curl ...) substitution
\$\(\s*wget\s+$(wget ...) substitution
\$\(\s*which\s+$(which ...) substitution
\bsudo\bPrivilege escalation
\bchmod\s+[0-7]{3,4}\bPermission changes
\bchown\bOwnership changes
\bpkill\bProcess kill by name
\bkillall\bKill all matching processes
\bkill\s+-[9]\bForce-kill (kill -9)
\bcurl\b.*|\s*(sh|bash)curl ... | sh download-and-execute
\bwget\b.*|\s*(sh|bash)wget ... | sh download-and-execute
\bnpm\s+install\s+-g\bGlobal npm installs
\bpip\s+install\s+--user\bUser-level pip installs
\bapt\s+(install|remove|purge)\bAPT package changes
\byum\s+(install|remove)\bYUM package changes
\bdnf\s+(install|remove)\bDNF package changes
\bdocker\s+run\bStarting new containers
\bdocker\s+exec\bExec into running containers
\bgit\s+push\bGit push
\bgit\s+force\bGit force operations
\bssh\b.*@Remote SSH connections
\beval\bDynamic code evaluation
\bsource\s+.*\.sh\bSourcing shell scripts

Custom deny patterns

Add your own regex patterns to custom_deny_patterns to block additional commands specific to your environment:
{
  "tools": {
    "exec": {
      "enable_deny_patterns": true,
      "custom_deny_patterns": [
        "\\brm\\s+-r\\b",
        "\\bkillall\\s+python\\b",
        "\\bcurl\\b.*internal\\.corp"
      ]
    }
  }
}
Patterns are Go regular expressions and are matched case-insensitively against the full command string. Test your patterns with:
echo 'your command here' | grep -iP 'your_pattern'

Custom allow patterns

custom_allow_patterns lets you exempt specific commands from all deny checks. A command matching any allow pattern is executed without further filtering:
{
  "tools": {
    "exec": {
      "enable_deny_patterns": true,
      "custom_allow_patterns": [
        "\\bgit\\s+push\\s+origin\\s+main\\b"
      ]
    }
  }
}
Setting enable_deny_patterns to false disables the entire default blocklist. Operator will print a warning at startup:
Warning: deny patterns are disabled. All commands will be allowed.
Only disable deny patterns if you have an alternative sandboxing mechanism (for example, a container with restricted capabilities or a separate VM). Never disable deny patterns on a shared or internet-facing machine.

Environment variable override

OPERATOR_TOOLS_EXEC_ENABLE_DENY_PATTERNS=false   # disable all default patterns
OPERATOR_TOOLS_EXEC_CUSTOM_DENY_PATTERNS='["\\bmy-blocked-cmd\\b"]'

Channel access control

Every messaging channel supports an allow_from list. When the list is non-empty, only messages from users whose ID appears in the list are processed. All other messages are silently ignored.
{
  "channels": {
    "telegram": {
      "enabled": true,
      "token": "YOUR_BOT_TOKEN",
      "allow_from": ["123456789"]
    },
    "discord": {
      "enabled": true,
      "token": "YOUR_DISCORD_TOKEN",
      "allow_from": ["987654321098765432"]
    },
    "slack": {
      "enabled": true,
      "bot_token": "xoxb-...",
      "app_token": "xapp-...",
      "allow_from": ["U01ABC123DE"]
    }
  }
}
The allow_from list accepts both string and numeric IDs. The type that is appropriate depends on the channel:
ChannelID format
TelegramNumeric user ID (string or integer)
DiscordSnowflake string
SlackMember ID string (e.g. U01ABC123DE)
WhatsAppPhone number with country code
QQNumeric QQ account number
Feishu / LarkOpen user ID
DingTalkUser staff ID
LINEUser ID string
Leave allow_from as an empty array [] to accept messages from all users. This is convenient during development but should be restricted before you deploy an agent to a shared channel.

Telegram example

Your Telegram user ID can be retrieved by messaging @userinfobot:
{
  "channels": {
    "telegram": {
      "enabled": true,
      "token": "YOUR_TELEGRAM_BOT_TOKEN",
      "allow_from": ["123456789", "987654321"]
    }
  }
}

Summary of security defaults

FeatureDefaultDescription
restrict_to_workspacetrueAgent confined to workspace directory
allow_read_outside_workspacefalseReads also confined to workspace
enable_deny_patternstrueDefault dangerous-command blocklist active
custom_deny_patterns[]No additional patterns by default
allow_from (channels)[]All users accepted (restrict in production)

Build docs developers (and LLMs) love