Skip to content

When to Use Config-Level Permissions vs SOUL.md Rules in OpenClaw

I learned this the hard way: my AI agent sent an email I never intended to send. I had written “never send emails without approval” in my SOUL.md file, but the agent did it anyway. The worst part? I couldn’t figure out why my instruction was ignored.

After digging through documentation and community discussions, I found the root cause: I had placed a critical security rule in a place where prompt drift could—and did—override it.

The Problem: Prompt Drift is Real

Here’s what happened. My SOUL.md contained this rule:

SOUL.md (WRONG APPROACH)
## Security Rules
- Never send emails without explicit user approval
- Never delete files without confirmation
- Never execute shell commands that modify the system

Seems reasonable, right? But after a long conversation with the agent, where we discussed various email-related tasks, the agent “forgot” this rule and sent an email automatically.

The context window had filled up with other information. My security instruction, buried in the middle of SOUL.md, got compressed and de-prioritized. The agent made a reasonable-seeming decision based on recent context—and my security rule evaporated.

The Solution: Two-Layer Security

The OpenClaw community taught me a critical distinction:

Rule TypeStorage LocationEnforcement LevelCan Override?
Critical securityconfig.jsonSystem-level checkNo
Behavioral preferencesSOUL.mdPrompt contextYes (by drift)

Config-Level Permissions: The Hard Blocks

Config.json permissions are enforced before any action executes. They’re system-level checks that the AI cannot bypass, regardless of:

  • Context length
  • Prompt drift
  • Clever reasoning
  • “Emergency” situations
config.json - Critical security permissions
{
"security": {
"actionApproval": {
"required": ["email.send", "file.delete", "shell.exec"]
}
}
}

With this configuration, the system will always require approval for these actions. No amount of context or reasoning can override it.

SOUL.md: Behavioral Preferences

SOUL.md rules are part of the prompt context. They’re subject to the same drift and compression as any other instruction:

SOUL.md - Behavioral preferences
## Communication Style
- Be concise and direct
- Avoid unnecessary explanations
- Use TypeScript for all code examples
## Code Preferences
- Prefer functional programming patterns
- Use Zod for all input validation
- Always handle errors with proper try/catch blocks
## Documentation
- Document all public functions
- Include usage examples in comments
- Keep README files up to date

These rules can evolve, be forgotten, or be de-prioritized as the conversation progresses. That’s fine for coding style or tone preferences—but catastrophic for security.

Why This Happens: Context Window Mechanics

Let me explain the technical reason behind prompt drift:

Context window during a long session
┌─────────────────────────────────────────┐
│ System Prompt (SOUL.md + instructions) │ ← Gets compressed
├─────────────────────────────────────────┤
│ Conversation History │
│ - Task 1 │
│ - Task 2 │ ← Fills up
│ - Task 3 │
│ - ...more context... │
├─────────────────────────────────────────┤
│ Current Context │ ← Prioritized
└─────────────────────────────────────────┘

As the context window fills:

  1. Earlier instructions get compressed or summarized
  2. Recent context gets prioritized
  3. Rules in the middle can be “forgotten”

But config-level permissions operate outside this context window:

Permission check flow
Action Request
┌─────────────────┐
│ System-Level │ ← Happens BEFORE LLM sees it
│ Permission Check │
└────────┬────────┘
┌────┴────┐
│ Denied? │──Yes──► Block action, require approval
└────┬────┘
│No
┌─────────────────┐
│ LLM Processing │ ← Context window, SOUL.md, etc.
│ (can drift) │
└─────────────────┘

The Decision Guide: Where to Put Your Rules

After my email incident, I created this decision matrix:

Rule TypeUse config.jsonUse SOUL.mdWhy?
Email sendingIrreversible, affects external systems
File deletionIrreversible data loss
Shell commandsSystem modification, security risk
API key exposureCredential leak, security breach
Coding stylePreference, can evolve
Tone/voiceSubjective, project-specific
Preferred librariesTeam preference, not security
Documentation formatConventions, not critical

Common Mistakes I Made (So You Don’t Have To)

Mistake 1: Over-relying on SOUL.md for Security

SOUL.md (DANGEROUS)
## Security
- Never expose API keys in logs
- Always validate user input
- Block any file operations on /etc/

This feels right, but these rules can drift. The agent might decide that “in this specific case, it’s safe to log the API key for debugging.”

Better approach:

config.json (CORRECT)
{
"security": {
"actionApproval": {
"required": [
"file.write:/etc/*",
"api.key.expose"
]
},
"blockedPatterns": [
"api_key.*log",
"password.*console"
]
}
}

Mistake 2: Over-constraining with Config Permissions

config.json (TOO RESTRICTIVE)
{
"security": {
"actionApproval": {
"required": ["file.write", "file.read", "file.create"]
}
}
}

This blocks all file operations, including temporary files needed for normal operation. The agent becomes nearly useless.

Better approach:

config.json (BALANCED)
{
"security": {
"actionApproval": {
"required": [
"file.delete",
"file.write:/etc/*",
"file.write:/home/*"
]
}
}
}

Mistake 3: Not Testing Config Permissions

I assumed my config.json was working—until I tested it. The agent still sent that email because I had a typo in the permission key.

Always test:

Testing permission enforcement
# Trigger the action that should be blocked
# Verify it actually requires approval
# Check logs to confirm permission check ran

The Combined Approach: Defense in Depth

My current setup uses both layers:

config.json
{
"security": {
"actionApproval": {
"required": [
"email.send",
"file.delete",
"shell.exec",
"api.write",
"file.write:/etc/*",
"file.write:/home/*"
]
}
}
}
SOUL.md
## Security Mindset
- When in doubt, ask for approval
- Prefer read-only operations
- Log all significant actions
- Consider impact on external systems
## Operational Preferences
- Test in isolated environments first
- Use dry-run modes when available
- Provide clear explanations for actions

The config.json provides hard guarantees. The SOUL.md provides guidance for edge cases and judgment calls.

When to Use Each Approach

Use config.json permissions when:

  1. The action is irreversible - Email sent, file deleted, data modified
  2. The action affects external systems - API calls, webhooks, emails
  3. The action could expose secrets - Logging, debugging output
  4. The action modifies system state - Shell commands, file writes
  5. The rule must never be bypassed - No exceptions, no “emergency” overrides

Use SOUL.md when:

  1. The rule is a preference - Coding style, documentation format
  2. The rule should evolve - Project-specific conventions
  3. The rule needs context - “Use React hooks except when…”
  4. The rule is about quality - Code review standards, testing requirements
  5. The rule is about communication - Tone, detail level, format

Key Insights from the Community

From the r/clawdbot discussion:

“For rules that absolutely cannot break, don’t rely on SOUL.md at all”

“Prompt-level rules can drift. config-level permissions can’t.”

“No amount of context length will override a system-level permission check”

These insights shaped my understanding: security is about guarantees, not preferences. If you need a guarantee, don’t put it somewhere that can be forgotten.

Final Thoughts

The two-layer approach provides defense in depth:

  • config.json: Hard security guarantees that cannot be overridden
  • SOUL.md: Behavioral guidance that can evolve with your project

The critical distinction is where enforcement happens. Config permissions check before the LLM processes anything. SOUL.md rules check during LLM reasoning—which means they’re subject to all the quirks of language model behavior.

My email-sending incident taught me an important lesson: when security matters, use the right tool for the job. Don’t rely on prompts for guarantees; use system-level checks instead.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

References

Comments