This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Security

Security hardening, secrets management, and access control.

1 - OpenClaw Secrets Management with 1Password

Step-by-step tutorial for setting up 1Password CLI integration with OpenClaw to eliminate plaintext credentials from your agent configuration.

This guide walks through setting up 1Password as a centralized secret provider for OpenClaw. By the end, every API key, token, and credential in your OpenClaw config will resolve at runtime through 1Password — nothing sensitive stored in plaintext.

Prerequisites

  • OpenClaw installed and running
  • 1Password account (personal or business)
  • 1Password CLI (op) installed
  • Basic familiarity with OpenClaw’s openclaw.json config

Part 1: Install and Configure 1Password CLI

Install the CLI

# Ubuntu/Debian
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
  sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/$(dpkg --print-architecture) stable main" | \
  sudo tee /etc/apt/sources.list.d/1password.list

sudo apt update && sudo apt install 1password-cli

# Verify
op --version

Create a Service Account

For non-interactive (agent) access, you need a service account — not a personal sign-in.

  1. Go to your 1Password admin console
  2. Navigate to DeveloperService Accounts
  3. Create a new service account with access to your vault
  4. Copy the service account token

Set the Token

Add the service account token to your OpenClaw environment:

# In ~/.openclaw/.env
OP_SERVICE_ACCOUNT_TOKEN=ops_your_service_account_token_here

This is the one secret that bootstraps access to everything else. Protect this file with appropriate permissions:

chmod 600 ~/.openclaw/.env

Verify Access

# Test that op can authenticate
export OP_SERVICE_ACCOUNT_TOKEN="ops_your_token"
op vault list
op item list --vault "Your-Vault-Name"

Part 2: Create a Vault and Add Credentials

Create a Dedicated Vault

Keep OpenClaw credentials separate from personal items:

# Via 1Password web UI or CLI
op vault create "OpenClaw-API-Keys"

Add Credentials

For each API key your OpenClaw instance uses:

# Create items with the password field
op item create \
  --category=password \
  --title="ANTHROPIC_API_KEY" \
  --vault="OpenClaw-API-Keys" \
  password="sk-ant-your-key-here"

op item create \
  --category=password \
  --title="OPENROUTER_API_KEY" \
  --vault="OpenClaw-API-Keys" \
  password="sk-or-v1-your-key-here"

op item create \
  --category=password \
  --title="DISCORD_TOKEN" \
  --vault="OpenClaw-API-Keys" \
  password="your-discord-bot-token"

Verify Items

# List all items (titles only, never echo values)
op item list --vault OpenClaw-API-Keys --format json | jq '.[].title'

# Verify a specific item resolves (check existence, not value)
op read "op://OpenClaw-API-Keys/ANTHROPIC_API_KEY/password" > /dev/null && echo "OK" || echo "FAILED"

Important: Never echo secret values to terminal, logs, or chat. Always verify with existence checks.

Part 3: Configure OpenClaw Secret Providers

Add Exec Providers

In openclaw.json, add a secrets.providers entry for each credential:

{
  "secrets": {
    "providers": {
      "onepassword_anthropic": {
        "source": "exec",
        "command": "/usr/bin/op",
        "args": ["read", "op://OpenClaw-API-Keys/ANTHROPIC_API_KEY/password"],
        "jsonOnly": false,
        "passEnv": ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],
        "allowInsecurePath": true
      },
      "onepassword_openrouter": {
        "source": "exec",
        "command": "/usr/bin/op",
        "args": ["read", "op://OpenClaw-API-Keys/OPENROUTER_API_KEY/password"],
        "jsonOnly": false,
        "passEnv": ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],
        "allowInsecurePath": true
      }
    }
  }
}

Key Configuration Notes

FieldPurposeNotes
sourceAlways "exec" for CLI-based providers
commandFull path to op binaryUse which op to find it
argsArguments for op readFormat: op://Vault/Item/Field
jsonOnlySet false for raw text outputop read returns raw text, not JSON
passEnvEnvironment variables to forwardMust include OP_SERVICE_ACCOUNT_TOKEN
allowInsecurePathAllow non-user-owned binariesRequired because op is owned by root

Common Field Names

Most 1Password items use password as the field name, but watch for variations:

  • password — Standard for most items
  • credential — Some items created through web UI
  • token — Custom field name
  • notesPlain — Notes field

Check your item’s field structure:

op item get "ITEM_NAME" --vault "OpenClaw-API-Keys" --format json | \
  jq '[.fields[] | select(.value != null) | .label]'

Part 4: Replace Plaintext Values with SecretRefs

Config-Level Credentials

Replace plaintext apiKey values with SecretRef objects:

Before:

{
  "tools": {
    "web": {
      "search": {
        "apiKey": "BSA-actual-key-value"
      }
    }
  }
}

After:

{
  "tools": {
    "web": {
      "search": {
        "apiKey": {
          "source": "exec",
          "provider": "onepassword_brave",
          "id": "value"
        }
      }
    }
  }
}

Skill Credentials

Same pattern for skill configs:

{
  "skills": {
    "entries": {
      "todoist": {
        "apiKey": {
          "source": "exec",
          "provider": "onepassword_todoist",
          "id": "value"
        }
      }
    }
  }
}

Agent Auth Profiles

For model provider credentials, use auth-profiles instead of direct apiKey values.

Create auth-profiles.json in each agent’s directory (~/.openclaw/agents/<name>/agent/auth-profiles.json):

{
  "version": 1,
  "profiles": {
    "openrouter:default": {
      "type": "api_key",
      "provider": "openrouter",
      "keyRef": {
        "source": "exec",
        "provider": "onepassword_openrouter",
        "id": "value"
      }
    },
    "anthropic:default": {
      "type": "token",
      "provider": "anthropic",
      "tokenRef": {
        "source": "exec",
        "provider": "onepassword_anthropic",
        "id": "value"
      }
    },
    "cloudflare-ai-gateway:default": {
      "type": "api_key",
      "provider": "cloudflare-ai-gateway",
      "keyRef": {
        "source": "exec",
        "provider": "onepassword_cloudflare",
        "id": "value"
      }
    }
  }
}

Then set the apiKey in the agent’s models.json to "secretref-managed":

{
  "providers": {
    "openrouter": {
      "apiKey": "secretref-managed"
    },
    "anthropic": {
      "apiKey": "secretref-managed"
    }
  }
}

Local Services (No Auth Needed)

If a provider doesn’t require authentication (local Ollama, local Whisper), remove the apiKey field entirely rather than setting a placeholder:

{
  "providers": {
    "ollama": {
      "baseUrl": "http://localhost:11434",
      "api": "ollama"
    }
  }
}

Part 5: Validate and Restart

Pre-Flight Backup

Always back up before restarting:

cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.backup.$(date +%Y%m%d-%H%M%S)

Run the Secrets Audit

openclaw secrets audit --check

Target output:

Secrets audit: clean. plaintext=0, unresolved=0, shadowed=0, legacy=0.

If you see REF_UNRESOLVED, the SecretRef can’t resolve. Common causes:

ErrorCauseFix
“returned invalid JSON”Wrong id value (e.g., "google/apiKey" instead of "value")Use "id": "value" for op read providers
“must be owned by current user”Missing allowInsecurePathAdd "allowInsecurePath": true
“provider not found”Typo in provider nameCheck secrets.providers key matches
“command failed”OP_SERVICE_ACCOUNT_TOKEN not availableAdd to passEnv array

Restart Gateway

openclaw gateway restart

If things break, restore:

cp ~/.openclaw/openclaw.json.backup.TIMESTAMP ~/.openclaw/openclaw.json
openclaw gateway restart

Part 6: Best Practices

Migration Strategy

  1. Start with low-risk credentials — Skills and non-critical integrations first
  2. Test one at a time — Validate each SecretRef resolves before moving to the next
  3. Save critical-path credentials for last — Discord token, primary model provider keys
  4. Back up before every restart — Revert should always be one command away

Security Hygiene

  • One vault, one purpose — Keep OpenClaw credentials in a dedicated vault
  • Service account, not personal — Don’t rely on interactive sign-in for agent access
  • Never echo values — Verify credentials exist with > /dev/null && echo OK, never print them
  • Audit regularly — Run openclaw secrets audit --check after any config change
  • Restrict .env permissionschmod 600 on the file containing OP_SERVICE_ACCOUNT_TOKEN

Operational Notes

  • Boot order matters — The OP_SERVICE_ACCOUNT_TOKEN must be available in the environment when OpenClaw starts. If using systemd, ensure the .env file is loaded via EnvironmentFile=
  • 1Password CLI caches — The CLI caches auth sessions. If you rotate the service account token, restart the gateway to pick up the new one
  • Field name consistency — Standardize on password as the field name when creating vault items. It makes op read paths predictable

Adding New Credentials

When you add a new integration:

  1. Create the 1Password vault item
  2. Add a secrets.providers entry in openclaw.json
  3. Reference it with a SecretRef in the appropriate config location
  4. Run openclaw secrets audit --check
  5. Restart and verify

Quick Reference

SecretRef Format

{
  "source": "exec",
  "provider": "<provider_name>",
  "id": "value"
}

Provider Template

"onepassword_<name>": {
  "source": "exec",
  "command": "/usr/bin/op",
  "args": ["read", "op://OpenClaw-API-Keys/<ITEM_TITLE>/password"],
  "jsonOnly": false,
  "passEnv": ["HOME", "OP_SERVICE_ACCOUNT_TOKEN"],
  "allowInsecurePath": true
}

Useful Commands

# Audit current state
openclaw secrets audit --check

# List vault items
op item list --vault OpenClaw-API-Keys --format json | jq '.[].title'

# Verify a specific item (never echo the value)
op read "op://OpenClaw-API-Keys/ITEM/password" > /dev/null && echo "resolves: yes"

# Backup config
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.backup.$(date +%Y%m%d-%H%M%S)

Written based on a real migration from 127 plaintext credentials to zero on an OpenClaw homelab deployment. March 2026.

2 - SEL × Cynefin: Security and Autonomy for AI Agents

A practical framework for classifying agent capabilities by security level and domain complexity. Know when agents can act autonomously and when they need human approval.

AI agents need to know two things before acting:

  1. What tools can I use? (Security Execution Level)
  2. How autonomous can I be in this domain? (Cynefin classification)

This guide shows you how to implement both, with OpenClaw-specific examples and prompts.

Prerequisites

  • OpenClaw installed and configured
  • Basic understanding of AI agents
  • Familiarity with Cynefin framework

Background

The Problem

When you have 150+ agents (like we do after installing Agency), you need answers to:

  • Can the researcher agent run docker restart?
  • Should the homelab-guardian agent be able to delete files?
  • Does the code-crafter need approval before pushing to GitHub?
  • When should an agent escalate to a human?

The answer isn’t binary. It depends on both the tools available and the domain complexity.


Part 1: Security Execution Levels (SEL)

SEL defines what tools an agent can use:

SEL-0: Read-only

Tools: read, web_fetch, web_search, memory_search, qmd

Use for: Information gathering agents that should never modify state.

Example agents: researcher, qa-reviewer

sel:
  default: 0
  sandbox_required: false
capabilities:
  allowed_tools: [web_search, web_fetch, memory_search, memory_get]
  denied_tools: [exec, write, edit, message, gateway]

SEL-1: Standard

Tools: SEL-0 + write (workspace files), exec (non-destructive commands)

Use for: Agents that create content but shouldn’t touch the system.

Example agents: communicator, librarian

sel:
  default: 1
  sandbox_required: false
capabilities:
  allowed_tools: [read, write, web_search, web_fetch, memory_*]
  denied_tools: [exec, edit]  # Edit may include system files

SEL-2: Elevated

Tools: SEL-1 + exec (destructive), edit (any file), gateway (config changes)

Use for: Infrastructure agents that need to modify the system.

Requires: /approve before destructive operations

Example agents: homelab-guardian, devops-engineer

sel:
  default: 1
  elevated_to: 2
  elevated_for: [docker_restart, docker_rm, package_install]
  sandbox_required: false

SEL-3: Quarantine

Tools: Arbitrary code execution, untrusted API calls

Use for: Running untrusted code, processing untrusted input

Requires: Per-operation approval + Docker sandbox

Example uses: Running user-provided scripts, processing unknown files

sel:
  default: 3
  sandbox_required: true
capabilities:
  allowed_tools: [exec_sandbox]  # Only sandboxed execution
  network:
    allowed_domains: []
    denied_domains: ["*"]

Part 2: Cynefin Domain Classification

Cynefin defines how autonomous an agent can be in a given domain:

Clear Domain

Characteristics: Best practices exist, cause-effect is obvious, predictable outcomes.

Agent behavior: Autonomous execution

Human role: Exception handling only

Complicated Domain

Characteristics: Expert analysis needed, multiple valid approaches.

Agent behavior: Recommend with analysis, wait for approval

Human role: Approve and implement

Complex Domain

Characteristics: Patterns emerge in retrospect, no single right answer.

Agent behavior: Probabilistic prediction, flag uncertainty

Human role: Interpret, decide, adjust

Chaotic Domain

Characteristics: Unknown unknowns, no discernible cause-effect.

Agent behavior: Contain, escalate, document

Human role: Diagnose, respond, learn


Part 3: SEL × Cynefin Matrix

The combination determines agent behavior:

Cynefin \ SELSEL-0SEL-1SEL-2SEL-3
Clear✅ Autonomous✅ Autonomous⚠️ Approve first❌ Escalate
Complicated✅ Autonomous⚠️ Approve⚠️ Approve❌ Escalate
Complex✅ Research⚠️ Uncertainty❌ Human required❌ Escalate
Chaotic✅ Observe❌ Escalate❌ Escalate❌ Full stop

Decision Logic

IF domain is Clear AND tool SEL ≤ 1:
    → Execute autonomously

IF domain is Clear AND tool SEL = 2:
    → Request approval, explain cause-effect, execute on /approve

IF domain is Complicated AND tool SEL ≤ 0:
    → Execute autonomously (research/analysis)

IF domain is Complicated AND tool SEL = 1+:
    → Recommend with analysis, wait for approval

IF domain is Complex:
    → Report findings with uncertainty estimates, defer decisions

IF domain is Chaotic:
    → Immediately escalate, contain if possible, document everything

Part 4: Implementation in OpenClaw

Step 1: Classify Your Skills

Add cynefin and sel blocks to each skill’s SKILL.md:

---
name: homelab-guardian
description: Infrastructure automation and security for homelab
cynefin:
  primary: complicated
  subdomains:
    monitoring: clear      # Health checks are predictable
    management: complicated  # Requires analysis
    failure_recovery: complex # Emergent patterns
  rationale: "Infrastructure operations require expertise. Multiple valid approaches exist."
  autonomous: false
  human_approval: on_elevation
  confidence: medium
sel:
  default: 1
  elevated_to: 2
  elevated_for: [docker_restart, docker_rm, package_install]
  sandbox_required: false
capabilities:
  allowed_tools: [exec, read, write, edit]
  rate_limits:
    exec: "1 per 5s"
---

Step 2: Define Agent SEL Ceilings

In your agent coordination config:

agents:
  researcher:
    max_SEL: 0  # Can only use SEL-0 tools
    
  communicator:
    max_SEL: 1  # Can use SEL-0 and SEL-1
    
  homelab-guardian:
    max_SEL: 2  # Can use SEL-0, SEL-1, SEL-2 (with approval for SEL-2)

Step 3: Implement Approval Flow

When an agent wants to use an SEL-2 tool:

def check_sel_permission(agent, tool, domain):
    tool_sel = get_tool_sel(tool)
    agent_max_sel = agent.max_SEL
    domain_cynefin = get_domain_classification(domain)
    
    # SEL ceiling check
    if tool_sel > agent_max_sel:
        return {"action": "deny", "reason": f"Tool requires SEL-{tool_sel}, agent limited to SEL-{agent_max_sel}"}
    
    # Cynefin autonomy check
    if domain_cynefin == "chaotic":
        return {"action": "escalate", "reason": "Chaotic domain requires human intervention"}
    
    if domain_cynefin in ["complex", "complicated"] and tool_sel >= 1:
        return {"action": "approve", "reason": f"{domain_cynefin} domain needs approval for SEL-{tool_sel}"}
    
    if tool_sel >= 2:
        return {"action": "approve", "reason": f"SEL-{tool_sel} always requires approval"}
    
    return {"action": "execute", "reason": "Within bounds"}

Part 5: Prompts for OpenClaw

Classifying a New Skill

Classify this skill using the SEL × Cynefin framework:

Skill: [skill name]
Description: [what it does]
Tools it uses: [list of tools]

Provide:
1. SEL level (0-3) with rationale
2. Cynefin domain (clear/complicated/complex/chaotic) with subdomains
3. Whether it should be autonomous
4. When it needs human approval

Checking Agent Permissions

Can the [agent name] agent use [tool name] for [task]?

Check:
1. Agent's max SEL vs tool's required SEL
2. Task domain Cynefin classification
3. Decision: autonomous / approve / escalate

Explain your reasoning.

Onboarding New Agents

 onboard the [agent name] agent:

1. What is its primary purpose?
2. What tools does it need?
3. What domain(s) does it operate in?
4. Assign SEL ceiling
5. Classify Cynefin domains
6. Define capability boundaries
7. Document escalation paths

Part 6: Common Patterns

Pattern: Read-Only Research Agent

# researcher agent
cynefin:
  primary: complicated
  subdomains:
    search: clear
    analysis: complicated
sel:
  default: 0
capabilities:
  allowed_tools: [web_search, web_fetch, memory_search, memory_get]
  denied_tools: [exec, write, edit, message]

Behavior: Can search and analyze autonomously, can’t modify anything.

Pattern: Infrastructure Agent

# homelab-guardian agent
cynefin:
  primary: complicated
  subdomains:
    monitoring: clear
    management: complicated
    recovery: complex
sel:
  default: 1
  elevated_to: 2
  elevated_for: [docker_restart]
capabilities:
  allowed_tools: [exec, read, docker]

Behavior: Status checks autonomous, Docker restart needs approval.

Pattern: Untrusted Code Runner

# code-runner agent
cynefin:
  primary: chaotic
  subdomains:
    known_code: complicated
    unknown_code: chaotic
sel:
  default: 3
  sandbox_required: true
capabilities:
  allowed_tools: [exec_sandbox]
  network:
    denied_domains: ["*"]

Behavior: Always sandboxed, requires approval, no network access.


Troubleshooting

“Agent can’t use tool it should have access to”

Check:

  1. Agent’s max_SEL vs tool’s required SEL
  2. Domain Cynefin classification (complex domains limit autonomy)
  3. Capability boundary denied_tools list

“Agent is asking for approval when it shouldn’t”

Check:

  1. Is the task in a Clear domain?
  2. Is the SEL ≤ 1?
  3. Is the domain classified correctly?

“How do I elevate temporarily?”

Use /approve <reason> in the session. Approval applies to the next elevated operation only.


  • memory/procedural/security-execution-levels.md — Full SEL framework
  • memory/procedural/domain-classification.md — Cynefin classification guide
  • memory/procedural/agent-onboarding-contract.md — Agent onboarding process
  • Journal: Security Execution Levels

This framework was developed while onboarding 154 Agency agents. All 62 skills in our OpenClaw install now have SEL + Cynefin classification.