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:
## Security Rules- Never send emails without explicit user approval- Never delete files without confirmation- Never execute shell commands that modify the systemSeems 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 Type | Storage Location | Enforcement Level | Can Override? |
|---|---|---|---|
| Critical security | config.json | System-level check | No |
| Behavioral preferences | SOUL.md | Prompt context | Yes (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
{ "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:
## 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 dateThese 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:
┌─────────────────────────────────────────┐│ 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:
- Earlier instructions get compressed or summarized
- Recent context gets prioritized
- Rules in the middle can be “forgotten”
But config-level permissions operate outside this context window:
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 Type | Use config.json | Use SOUL.md | Why? |
|---|---|---|---|
| Email sending | ✅ | ❌ | Irreversible, affects external systems |
| File deletion | ✅ | ❌ | Irreversible data loss |
| Shell commands | ✅ | ❌ | System modification, security risk |
| API key exposure | ✅ | ❌ | Credential leak, security breach |
| Coding style | ❌ | ✅ | Preference, can evolve |
| Tone/voice | ❌ | ✅ | Subjective, project-specific |
| Preferred libraries | ❌ | ✅ | Team preference, not security |
| Documentation format | ❌ | ✅ | Conventions, not critical |
Common Mistakes I Made (So You Don’t Have To)
Mistake 1: Over-relying on SOUL.md for Security
## 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:
{ "security": { "actionApproval": { "required": [ "file.write:/etc/*", "api.key.expose" ] }, "blockedPatterns": [ "api_key.*log", "password.*console" ] }}Mistake 2: Over-constraining with Config Permissions
{ "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:
{ "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:
# Trigger the action that should be blocked# Verify it actually requires approval# Check logs to confirm permission check ranThe Combined Approach: Defense in Depth
My current setup uses both layers:
{ "security": { "actionApproval": { "required": [ "email.send", "file.delete", "shell.exec", "api.write", "file.write:/etc/*", "file.write:/home/*" ] } }}## 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 actionsThe 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:
- The action is irreversible - Email sent, file deleted, data modified
- The action affects external systems - API calls, webhooks, emails
- The action could expose secrets - Logging, debugging output
- The action modifies system state - Shell commands, file writes
- The rule must never be bypassed - No exceptions, no “emergency” overrides
Use SOUL.md when:
- The rule is a preference - Coding style, documentation format
- The rule should evolve - Project-specific conventions
- The rule needs context - “Use React hooks except when…”
- The rule is about quality - Code review standards, testing requirements
- 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!
Comments