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:

  1. radius.yaml
  2. radius.yml
  3. .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

KeyTypeDefaultDescription
profilelocal | standard | unboundedstandardBase security profile
workspacestring${CWD}Agent workspace root directory
defaultActiondeny | allowdenyAction for tools not covered by rules
requireSignedPolicybooleanfalseRequire policy file signature verification
onUndefinedTemplateVarerror | emptyerrorBehavior when a template variable is undefined

Template variables

Use template variables anywhere in the YAML. They’re resolved at config load time.

VariableResolves 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:

  1. kill_switch — check first, halt everything if triggered
  2. skill_scanner — scan artifacts before processing
  3. tool_policy — explicit allow/deny by tool name
  4. self_defense (optional) — immutable policy and control-plane tamper checks
  5. fs_guard — filesystem path constraints
  6. tripwire_guard (optional) — deterministic honeytoken tripwires
  7. command_guard — shell command pattern blocking
  8. exec_sandbox — namespace isolation for commands
  9. egress_guard — outbound network filtering
  10. output_dlp — secret detection in outputs
  11. rate_budget — rate limiting
  12. repetition_guard (optional) — repeated identical-call loop brake
  13. verdict_provider (optional) — external deterministic verdict bridge
  14. approval_gate (optional) — human approval for remaining actions
  15. 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.

KeyTypeDefaultDescription
sinkfile | stdout | webhook | otlpfileWhere to write audit events
pathstring.radius/audit.jsonlFile path (for file sink)
webhookUrlstringWebhook endpoint (for webhook sink)
otlpEndpointstringOTLP endpoint (for otlp sink)
headersRecord<string, string>Custom headers for webhook/OTLP
timeoutMsnumber5000Request timeout for webhook/OTLP
includeArgumentsbooleantrueInclude tool arguments in audit log
includeResultsbooleantrueInclude tool results in audit log

approval section

KeyTypeDefaultDescription
enabledbooleanfalseEnable human-in-the-loop approval
modesync_wait | async_tokensync_waitWait for approval or return a token
waitTimeoutSecnumber300How long to wait for human response
temporaryGrantTtlSecnumber1800TTL for temporary “Allow 30m” grants
maxTemporaryGrantTtlSecnumber1800Hard cap for temporary grant TTL
onTimeoutdeny | alertdenyAction when approval times out
onConnectorErrordeny | alertdenyAction on channel error
store.enginesqlite | memorysqliteApproval state storage
store.pathstring.radius/approvals.dbSQLite database path
store.requiredbooleanfalseFail closed if node:sqlite is unavailable
channels.telegram.enabledbooleanfalseEnable Telegram channel
channels.telegram.transportpolling | webhookpollingHow to receive Telegram updates
channels.telegram.botTokenstringTelegram bot token (use env var)
channels.telegram.allowedChatIdsstring[][]Allowed Telegram chat IDs
channels.telegram.approverUserIdsstring[][]Users who can approve
channels.telegram.pollIntervalMsnumber2000Polling interval
channels.telegram.webhookPublicUrlstring""Public callback URL for webhook transport
channels.http.enabledbooleanfalseEnable HTTP approval resolver
channels.http.urlstringHTTP resolver endpoint
channels.http.timeoutMsnumber10000HTTP resolver timeout
channels.http.headersRecord<string,string>{}Extra headers for HTTP resolver

SQLite store behavior

RADIUS supports both sqlite and memory stores for approval/rate/repetition state:

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.

SettingLocalStandardUnbounded
Default actiondenydenyallow
Module modeenforceenforceobserve
exec_sandboxrequiredoptionalnot included
output_dlp actiondenyredactalert
rate_budget30 calls/min60 calls/min120 calls/min
skill_scanner on criticaldenychallengealert
Provenance checkssignature + SBOM + pinned source requiredchallenge on failurealert only
egress_guardincludednot includednot included
kill_switchenforceenforceobserve
Recommended forproduction, billing, credentialsdevelopment, staging, daily workresearch, migration, rollout

Profile aliases

AliasResolves to
strictlocal
bunkerlocal
balancedstandard
tacticalstandard
monitorunbounded
yolounbounded
unleashedunbounded

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.