Configuration Reference
Everything in RADIUS is controlled by a single YAML file. This page covers every section, every option, and the three built-in profiles.
Config file discovery
RADIUS looks for config files in this order:
radius.yamlradius.yml.radius.yaml
The first match wins. All paths are relative to the current working directory.
Full annotated example
# ── Global Settings ──────────────────────────────
global:
profile: standard # local | standard | unbounded
workspace: "${CWD}" # Agent workspace root
defaultAction: deny # deny | allow
requireSignedPolicy: false # Require signed policy file
onUndefinedTemplateVar: error # error | empty
# ── Module Pipeline ──────────────────────────────
# Order matters. First deny wins. Audit should be last.
modules:
- kill_switch
- skill_scanner
- tool_policy
# - self_defense # Optional hardening (v0.5+)
- fs_guard
# - tripwire_guard # Optional hardening (v0.5+)
- command_guard
- exec_sandbox
- egress_guard
- output_dlp
- rate_budget
# - repetition_guard # Optional hardening (v0.5+)
# - verdict_provider # Optional external detector (v0.5+)
- approval_gate
- audit
# ── Module Configuration ─────────────────────────
moduleConfig:
kill_switch:
enabled: true
envVar: RADIUS_KILL_SWITCH # Set this env var to activate
filePath: ./.radius/KILL_SWITCH # Or create this file
denyPhases:
- pre_request
- pre_tool
reason: "emergency kill switch active: human safety override"
skill_scanner:
scanOnStartup: true
scanOnReload: true
actionOnCritical: challenge # deny | challenge | alert
requireSignature: false
requireSbom: false
requirePinnedSource: false
onProvenanceFailure: challenge
tool_policy:
default: deny # deny | allow
# rules: # Per-tool guidance: /docs/modules/
# - tool: "SlackSend"
# action: allow
# egress: # Tool-specific network binding
# allowedDomains: ["api.slack.com"]
self_defense:
enabled: false # Optional: immutable control-plane lock
immutablePaths:
- "./radius.yaml"
- "./.radius/**"
onWriteAttempt: deny # deny | challenge
onHashMismatch: kill_switch # kill_switch | deny
killSwitchFilePath: ./.radius/KILL_SWITCH
unlock:
mode: disabled # disabled | token_file
filePath: ./.radius/UNLOCK
ttlSec: 300
fs_guard:
allowedPaths:
- "${workspace}"
- "/tmp"
blockedPaths:
- "~/.ssh"
- "~/.aws"
- "/etc"
blockedBasenames:
- ".env"
- ".env.local"
- ".envrc"
tripwire_guard:
enabled: false # Optional honeytoken tripwire module
fileTokens:
- "${workspace}/.tripwire/**"
envTokens:
- "RADIUS_TRIPWIRE_SECRET"
onTrip: kill_switch # deny | kill_switch | alert
killSwitchFilePath: ./.radius/KILL_SWITCH
command_guard:
denyPatterns:
- "(^|\\s)sudo\\s"
- "rm\\s+-rf\\s+/"
- "(^|\\s)(cat|less|more|head|tail|grep|awk|sed)\\s+[^\\n]*\\.env(?:\\.|\\s|$)"
exec_sandbox:
engine: bwrap # bwrap | none
required: false # true = deny if bwrap unavailable
childPolicy:
network: inherit # inherit | deny
egress_guard:
bindingMode: global_only # global_only | intersect
# allowedDomains: [] # Global allowlist mode
# blockedDomains: [] # Global blocklist mode
output_dlp:
action: redact # deny | redact | alert
# customPatterns: [] # Additional regex patterns
rate_budget:
windowSec: 60
maxCallsPerWindow: 60
store:
engine: sqlite # sqlite | memory
path: .radius/state.db
required: false # true only if node:sqlite is guaranteed
repetition_guard:
enabled: false # Optional loop detector
threshold: 3
cooldownSec: 60
onRepeat: deny # deny | alert
store:
engine: sqlite # sqlite | memory
path: .radius/state.db
required: false # true only if node:sqlite is guaranteed
verdict_provider:
enabled: false # Optional external verdict providers
minConfidence: 0.9
onProviderError: alert # alert | deny
timeoutMs: 3000
providers: [] # type/endpoint/apiKey/headers/categories
approval_gate:
# enabled: false # Enable for human-in-the-loop
audit:
sink: file # file | stdout | webhook | otlp
path: .radius/audit.jsonl
# ── Audit Settings ───────────────────────────────
audit:
sink: file
path: .radius/audit.jsonl
includeArguments: true
includeResults: true
# webhookUrl: https://...
# otlpEndpoint: https://...
# headers: {}
# timeoutMs: 5000
# ── Approval Settings ────────────────────────────
approval:
enabled: false
mode: sync_wait # sync_wait | async_token
waitTimeoutSec: 300
temporaryGrantTtlSec: 1800
maxTemporaryGrantTtlSec: 1800
onTimeout: deny # deny | alert
onConnectorError: deny # deny | alert
store:
engine: sqlite # sqlite | memory
path: .radius/approvals.db
required: false # true only if node:sqlite is guaranteed
channels:
telegram:
enabled: false
transport: polling # polling | webhook
botToken: "${TELEGRAM_BOT_TOKEN}"
allowedChatIds: []
approverUserIds: []
pollIntervalMs: 2000
webhookPublicUrl: ""
http:
enabled: false
url: "http://127.0.0.1:3101/approvals/resolve"
timeoutMs: 10000
headers: {}
global section
| Key | Type | Default | Description |
|---|---|---|---|
profile | local | standard | unbounded | standard | Base security profile |
workspace | string | ${CWD} | Agent workspace root directory |
defaultAction | deny | allow | deny | Action for tools not covered by rules |
requireSignedPolicy | boolean | false | Require policy file signature verification |
onUndefinedTemplateVar | error | empty | error | Behavior when a template variable is undefined |
Template variables
Use template variables anywhere in the YAML. They’re resolved at config load time.
| Variable | Resolves to |
|---|---|
${workspace} | The global.workspace value (after its own resolution) |
${CWD} | Current working directory |
${HOME} | User home directory |
${ENV_VAR_NAME} | Any environment variable |
Example:
fs_guard:
allowedPaths:
- "${workspace}" # /home/user/project
- "${HOME}/.config" # /home/user/.config
- "${CUSTOM_PATH}" # whatever CUSTOM_PATH is set to
modules array
The pipeline runs each module in array order. Order matters:
- kill_switch — check first, halt everything if triggered
- skill_scanner — scan artifacts before processing
- tool_policy — explicit allow/deny by tool name
- self_defense (optional) — immutable policy and control-plane tamper checks
- fs_guard — filesystem path constraints
- tripwire_guard (optional) — deterministic honeytoken tripwires
- command_guard — shell command pattern blocking
- exec_sandbox — namespace isolation for commands
- egress_guard — outbound network filtering
- output_dlp — secret detection in outputs
- rate_budget — rate limiting
- repetition_guard (optional) — repeated identical-call loop brake
- verdict_provider (optional) — external deterministic verdict bridge
- approval_gate (optional) — human approval for remaining actions
- audit — log the final decision (always last)
You can remove modules you don’t need. You can reorder them (but the above order is recommended). You can add the same module twice with different configs if needed. Existing v0.4.x configs remain valid; all new hardening modules in v0.5+ are opt-in.
moduleConfig overview
Each key in moduleConfig maps to a module name. The config object is passed to that module’s configure() method.
Compatibility note: for mixed fleets (v0.4.x and v0.5.x), keep effective module settings in moduleConfig.<moduleName>. This keeps hook adapters behavior consistent during phased upgrades.
Detailed per-module guidance is now documented in Modules. The full annotated example above still provides the fastest complete reference.
audit section
For mixed-version runtime compatibility, prefer moduleConfig.audit as the source of truth.
| Key | Type | Default | Description |
|---|---|---|---|
sink | file | stdout | webhook | otlp | file | Where to write audit events |
path | string | .radius/audit.jsonl | File path (for file sink) |
webhookUrl | string | — | Webhook endpoint (for webhook sink) |
otlpEndpoint | string | — | OTLP endpoint (for otlp sink) |
headers | Record<string, string> | — | Custom headers for webhook/OTLP |
timeoutMs | number | 5000 | Request timeout for webhook/OTLP |
includeArguments | boolean | true | Include tool arguments in audit log |
includeResults | boolean | true | Include tool results in audit log |
approval section
| Key | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable human-in-the-loop approval |
mode | sync_wait | async_token | sync_wait | Wait for approval or return a token |
waitTimeoutSec | number | 300 | How long to wait for human response |
temporaryGrantTtlSec | number | 1800 | TTL for temporary “Allow 30m” grants |
maxTemporaryGrantTtlSec | number | 1800 | Hard cap for temporary grant TTL |
onTimeout | deny | alert | deny | Action when approval times out |
onConnectorError | deny | alert | deny | Action on channel error |
store.engine | sqlite | memory | sqlite | Approval state storage |
store.path | string | .radius/approvals.db | SQLite database path |
store.required | boolean | false | Fail closed if node:sqlite is unavailable |
channels.telegram.enabled | boolean | false | Enable Telegram channel |
channels.telegram.transport | polling | webhook | polling | How to receive Telegram updates |
channels.telegram.botToken | string | — | Telegram bot token (use env var) |
channels.telegram.allowedChatIds | string[] | [] | Allowed Telegram chat IDs |
channels.telegram.approverUserIds | string[] | [] | Users who can approve |
channels.telegram.pollIntervalMs | number | 2000 | Polling interval |
channels.telegram.webhookPublicUrl | string | "" | Public callback URL for webhook transport |
channels.http.enabled | boolean | false | Enable HTTP approval resolver |
channels.http.url | string | — | HTTP resolver endpoint |
channels.http.timeoutMs | number | 10000 | HTTP resolver timeout |
channels.http.headers | Record<string,string> | {} | Extra headers for HTTP resolver |
SQLite store behavior
RADIUS supports both sqlite and memory stores for approval/rate/repetition state:
required: false— fallback to memory whennode:sqliteis unavailablerequired: true— fail closed when SQLite is unavailable
For OpenClaw subprocess hooks, use SQLite when you need state across calls (approval temporary grants, rate_budget, repetition_guard).
Profiles comparison
Three profiles ship with RADIUS. Use them as starting points — every setting can be overridden in your radius.yaml.
| Setting | Local | Standard | Unbounded |
|---|---|---|---|
| Default action | deny | deny | allow |
| Module mode | enforce | enforce | observe |
| exec_sandbox | required | optional | not included |
| output_dlp action | deny | redact | alert |
| rate_budget | 30 calls/min | 60 calls/min | 120 calls/min |
| skill_scanner on critical | deny | challenge | alert |
| Provenance checks | signature + SBOM + pinned source required | challenge on failure | alert only |
| egress_guard | included | not included | not included |
| kill_switch | enforce | enforce | observe |
| Recommended for | production, billing, credentials | development, staging, daily work | research, migration, rollout |
Profile aliases
| Alias | Resolves to |
|---|---|
strict | local |
bunker | local |
balanced | standard |
tactical | standard |
monitor | unbounded |
yolo | unbounded |
unleashed | unbounded |
Use either name in --profile or in the YAML global.profile field.
Local profile
Zero trust. Every tool is denied unless explicitly allowed. Sandbox is required for shell commands. Secrets in output cause an immediate deny. Supply chain provenance is enforced.
global:
profile: local
defaultAction: deny
modules:
- kill_switch
- skill_scanner
- tool_policy
- fs_guard
- command_guard
- exec_sandbox # required: true
- egress_guard # included in local only
- output_dlp # action: deny
- rate_budget # 30 calls/min
- audit
Use this for production environments, systems handling billing or credentials, and anywhere a compromised agent could cause real damage.
Standard profile
Trust but verify. Default deny, but the sandbox is optional, secrets are redacted (not blocked), and the rate limit is generous enough for normal development.
global:
profile: standard
defaultAction: deny
modules:
- kill_switch
- skill_scanner
- tool_policy
- fs_guard
- command_guard
- exec_sandbox # required: false
- output_dlp # action: redact
- rate_budget # 60 calls/min
- audit
This is the default for npx agentradius init. Good for development, staging, and daily work.
Unbounded profile
Safety off, logging only. All modules run in observe mode — they log what they would have blocked, but don’t actually block anything. The rate limit is high.
global:
profile: unbounded
defaultAction: allow
modules:
- kill_switch # observe mode
- skill_scanner # observe mode
- tool_policy # observe mode
- fs_guard # observe mode
- command_guard # observe mode
- output_dlp # action: alert, observe mode
- rate_budget # 120 calls/min, observe mode
- audit
Use this for initial migration: install RADIUS in unbounded mode, review the audit log to understand what would be blocked, then switch to standard or local.