A detailed walkthrough of how I (Cedric Hurst, @divideby0) have locked down my OpenClaw AI assistant, "Evie," running on a Mac mini M4. This guide covers every layer of the security model — from OS isolation to network access to behavioral controls.
Written collaboratively by Cedric and Evie herself.
- Architecture Overview
- OS-Level Isolation
- Credential Management (1Password)
- Secret Scanning (TruffleHog)
- Network Security (Tailscale)
- Database Security
- Behavioral Controls
- Audit & Observability
- Pending Work
- Knowledge Graph Demo
┌─────────────────────────────────────────────────┐
│ Mac mini M4 (macOS Sequoia 15.x) │
│ │
│ ┌──────────────┐ ┌──────────────────────┐ │
│ │ cedric (admin)│ │ openclaw (standard) │ │
│ │ • Docker │ │ • OpenClaw gateway │ │
│ │ • Supabase │ │ • Agent sessions │ │
│ │ • System mgmt │ │ • Skills/scripts │ │
│ └──────────────┘ └──────────────────────┘ │
│ │ │ │
│ │ SSH (BatchMode) │ localhost only │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Supabase (Docker) │ │
│ │ Postgres · PostgREST · Auth · Studio │ │
│ │ Ports: 127.0.0.1:65xxx (new stack) │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ Tailscale (system daemon) │ │
│ │ TLS termination → localhost:18789 │ │
│ │ ACL: cedric.hurst@gmail.com only │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
OpenClaw is an open-source AI agent framework. The gateway process runs as a standard (non-admin) macOS user. All external services (database, Docker, etc.) are owned by the admin account. The agent communicates with the outside world through the gateway, which handles message routing to Telegram, Slack, etc.
- OpenClaw GitHub: https://github.com/openclaw/openclaw
- OpenClaw Docs: https://docs.openclaw.ai
- Community: https://discord.com/invite/clawd
The agent runs under a dedicated openclaw user with standard (non-admin) privileges.
# Create the user (run as admin)
sudo sysadminctl -addUser openclaw -fullName "OpenClaw" -password "<secure>" -home /Users/openclaw
# Verify: no admin group membership
dscl . -read /Groups/admin GroupMembership
# → openclaw should NOT appear in the list| Action | Blocked? | Why |
|---|---|---|
Install system software (brew install) |
✅ Blocked | /opt/homebrew owned by admin |
| Modify system settings | ✅ Blocked | No admin privileges |
| Access other user home dirs | ✅ Blocked | macOS file permissions |
| Run Docker commands | ✅ Blocked | Docker socket owned by admin |
sudo anything |
✅ Blocked | Not in sudoers |
| Manage system services (launchd) | ✅ Blocked | Requires admin/root |
| Action | Allowed? | Why |
|---|---|---|
| Read/write its own home dir | ✅ Allowed | Standard user perms |
Run bun, node, python3, curl |
✅ Allowed | Installed in user-accessible paths |
| SSH to admin account (limited) | ✅ Allowed | Key-based, BatchMode, for DB migrations |
| Listen on high ports (>1024) | ✅ Allowed | Standard macOS behavior |
| Access the internet | ✅ Allowed | Needed for APIs, web search |
For operations that require admin access (e.g., running supabase db push), the agent SSHs to the admin account:
ssh -o BatchMode=yes cedric@localhost "PATH=/usr/local/bin:/opt/homebrew/bin:\$PATH supabase db push --local"This is key-based auth with BatchMode=yes (no interactive prompts). The admin account controls what's accessible — the agent can't escalate beyond what SSH allows.
AI agents need API keys, database credentials, OAuth tokens. Storing them in flat JSON files on disk is a security risk — if session logs capture them (and they do), they leak.
We use a 1Password service account with read-only access to a dedicated "Openclaw" vault.
Setup:
-
Create a 1Password service account (requires 1Password Family or Business plan)
- Go to: https://my.1password.com → Developer → Service Accounts
- Create account with read-only access to a specific vault
- Save the token (starts with
ops_...)
-
Store the token in macOS Keychain (not in a file):
security add-generic-password -a "openclaw" -s "1password-service-account" -w "ops_YOUR_TOKEN_HERE"
-
Retrieve and use in scripts:
TOKEN=$(security find-generic-password -a "openclaw" -s "1password-service-account" -w) OP_SERVICE_ACCOUNT_TOKEN="$TOKEN" op read "op://Openclaw/Fellow API/credential"
| Item | Purpose |
|---|---|
| Fellow API | Meeting sync credentials |
| Float API | Timesheet management |
| Supabase Local | Database connection |
| GitHub App (Evie) | Bot identity for GitHub comments |
| Google OAuth tokens | Gmail, Calendar, Drive access |
| ElevenLabs API | Text-to-speech |
| Telegram Bot | Messaging |
| ... | Other service credentials |
- Read-only: The agent cannot create, update, or delete vault items
- Scoped: Only the "Openclaw" vault is accessible — not personal or shared vaults
- No interactive auth: Service account tokens work headlessly (no browser, no biometric)
- Rotatable: Token can be revoked/regenerated without touching the agent
1Password CLI Reference: https://developer.1password.com/docs/cli/
LLM conversations capture everything — including API keys that appear in tool output, error messages, or copy-pasted configs. Session logs (.jsonl files) can accumulate live secrets over time.
We built a custom skill wrapping TruffleHog (open-source, 800+ detector types with verification):
# Scan session logs
bun run skills/secret-scan/scripts/scan.ts scan ~/.openclaw/agents/main/sessions/
# Scan and redact (replaces secrets with [REDACTED:detector_name:hash])
bun run skills/secret-scan/scripts/scan.ts redact ~/.openclaw/agents/main/sessions/| Detector | Count | Verified |
|---|---|---|
| Slack API tokens | 4 | ✅ Live |
| OpenAI API keys | 3 | ✅ Live |
| Telegram Bot tokens | 2 | ✅ Live |
| ElevenLabs API keys | 2 | ✅ Live |
| Sendgrid API keys | 2 | ✅ Live |
| URI with password | 1 | ✅ Live |
| Total | 14 | All verified |
All 14 were redacted in place. The redaction format preserves enough info for debugging without exposing the secret:
[REDACTED:slack:a1b2c3] ← detector type + short hash for identification
- Run TruffleHog periodically (can be automated via cron)
- Never paste secrets in chat — use 1Password or terminal
- Agent is instructed to push back if a user tries to share secrets in conversation
TruffleHog GitHub: https://github.com/trufflesecurity/trufflehog
You want to access your agent remotely (from your laptop, phone, etc.) without exposing it to the public internet.
Tailscale creates a private, encrypted WireGuard mesh network between your devices. No port forwarding, no public IPs, no firewalls to configure.
Setup:
-
Install Tailscale as a system daemon (survives user switching):
# Install via brew on macOS brew install tailscale # Or download from https://tailscale.com/download/mac # Run as system service (not the GUI app — that's per-user) # On macOS, Tailscale installs a system extension
-
Configure Tailscale Serve (TLS proxy to local gateway):
# Proxy HTTPS traffic to local OpenClaw gateway tailscale --socket=/var/run/tailscale/tailscaled.sock serve --bg http://localhost:18789This gives you
https://your-machine-name.tailnet-name.ts.netwith automatic TLS certificates. -
Lock down ACLs (in Tailscale admin console):
{ "acls": [ { "action": "accept", "src": ["cedric.hurst@gmail.com"], "dst": ["*:*"] } ] }Only your account can reach any device on the tailnet.
Your Phone/Laptop Mac mini
(Tailscale) ──────────► (Tailscale)
WireGuard │
encrypted │ tailscale serve
▼
https://*.ts.net
│
│ TLS termination
▼
localhost:18789
│
│ token auth
▼
OpenClaw Gateway
- End-to-end encrypted: WireGuard tunnels between devices
- No public exposure: Gateway only listens on
127.0.0.1 - Identity-based ACLs: Access tied to your identity, not IP addresses
- Automatic TLS: Tailscale provisions Let's Encrypt certs for your
.ts.netdomain - MagicDNS: Access via hostname, not IP
Gateway config:
{
"gateway": {
"trustedProxies": ["127.0.0.1", "::1"],
"auth": {
"allowTailscale": true
}
}
}Tailscale Docs: https://tailscale.com/kb/
Tailscale Serve: https://tailscale.com/kb/1242/tailscale-serve
ACLs: https://tailscale.com/kb/1018/acls
We run a full Supabase stack locally via Docker Compose — Postgres, PostgREST, Auth, Studio, etc.
Self-managed Docker Compose (not the Supabase CLI) gives us control over port bindings:
# All ports bound to localhost only
services:
db:
ports:
- "127.0.0.1:65322:5432"
rest:
ports:
- "127.0.0.1:65321:3000"
studio:
# Accessed through Kong, not directly exposed| Role | Access | How |
|---|---|---|
| Admin (cedric) | Full Postgres superuser | Docker-managed, password in 1Password |
| Agent (openclaw) | PostgREST API with service_role key | Key in 1Password vault |
| Public | Nothing | All ports localhost-only |
The plan is to create a non-admin Postgres user for the agent:
CREATE USER openclaw_agent WITH PASSWORD '...';
GRANT USAGE ON SCHEMA public TO openclaw_agent;
-- Grant SELECT/INSERT/UPDATE on specific tables
-- Row-level security policies control what data is visibleThis means:
- Agent can't
DROP TABLEor modify schema - Schema migrations require the admin password (manual gate)
- RLS policies can scope data access per-table
Supabase Docs: https://supabase.com/docs
Supabase Self-Hosting: https://supabase.com/docs/guides/self-hosting/docker
The agent's system prompt includes explicit behavioral constraints:
## Safety
- Don't exfiltrate private data. Ever.
- Don't run destructive commands without asking.
- `trash` > `rm` (recoverable beats gone forever)
- When in doubt, ask.
## External vs Internal
**Ask first:**
- Sending emails, tweets, public posts
- Anything that leaves the machine
- Anything you're uncertain about
**Safe to do freely:**
- Read files, explore, organize, learn
- Search the web, check calendars
- Work within this workspace| Channel | Policy |
|---|---|
| WhatsApp & Signal | Read-only by default. Only send when explicitly asked. |
| Telegram | Agent's own bot identity. Can send freely. |
| Slack | Read access. Drafts reviewed before sending to clients. |
| Group chats | Participate, don't dominate. Never act as user's proxy. |
A hard-learned rule:
When Cedric says STOP, STOP IMMEDIATELY. No extra commands, no "helpful" checks. Acknowledge and wait. "All yours" means hands completely off.
This is in the system prompt AND in the agent's long-term memory.
- Agent refuses to display API keys or passwords when asked
- Pushes back if user tries to paste secrets in chat
- Suggests 1Password or terminal for credential operations
Every agent interaction is logged to .jsonl files:
~/.openclaw/agents/main/sessions/<session-id>.jsonl
These contain the full conversation including tool calls and their outputs. They're the primary audit trail for what the agent did and why.
The agent maintains its own notes:
~/.openclaw/workspace/memory/YYYY-MM-DD.md # Daily activity log
~/.openclaw/workspace/MEMORY.md # Long-term memory
These are human-readable markdown files you can review anytime.
When the agent comments on issues or PRs, it posts as a GitHub App bot (evie-assistant[bot]), making it visually distinct from human comments. The App ID, installation, and permissions are all auditable through GitHub's settings.
| Item | Status | Description |
|---|---|---|
| Port binding migration | 🔄 In progress | Moving from *:64xxx (LAN-exposed) to 127.0.0.1:65xxx (localhost-only) |
| Dedicated DB user + RLS | 📋 Planned | Non-admin Postgres user for agent, admin password only known to human |
| Periodic secret scans | 📋 Planned | Cron job to run TruffleHog weekly |
op CLI fix |
🐛 Bug | v2.32.0 hanging on all commands — needs reinstall or update |
| Session log rotation | 📋 Planned | Archive/compress old session logs |
To demonstrate how OpenClaw's knowledge base works, here's what the system knows about Maurice Rabb — the person this guide is being shared with. All of this was assembled automatically from meeting sync, attendee resolution, and Apollo.io enrichment.
| Field | Value | Source |
|---|---|---|
| Full Name | Maurice Rabb | Fellow API (attendee resolution) |
| Title | Director of Pedagogy | Apollo.io enrichment |
| Organization | Spantree | Apollo.io enrichment |
| Location | Chicago, Illinois | Apollo.io enrichment |
| linkedin.com/in/mauricerabb | Apollo.io enrichment | |
| Emails | maurice.rabb@spantree.net, maur@trifork.com |
Fellow API (calendar invites) |
| Internal ID | af1cea0b-9429-4190-bc41-322baf9742f5 |
Auto-generated UUID |
Maurice appears in 146 meetings across our synced Fellow data. Breakdown by series:
| Meeting Series | Count | Context |
|---|---|---|
| Nextpoint internal | 30 | Primary project — search/query parser work |
| Global Stand (FTW!) | 12 | Company-wide standup |
| L10 | 12 | Leadership/ops cadence |
| Spantree-Trifork Ops | 8 | Operational meetings |
| Cedric / Maurice 1:1 | 6 | Direct manager check-ins |
| DTY Daily Standup | 6 | Cross-project standup |
| Team Time! | 6 | Team social/sync |
| Project Leads check-in | 4 | Cross-project leadership |
| Tuesday Check-in: Spantree + Nextpoint | 4 | Client-facing check-in |
| Friday Sprint Close & Planning | 4 | Sprint ceremonies |
| Spangineering Club | 3 | Engineering community |
| Kermit Collective | 2 | Quarterly advisory |
| Fluent Workshop/Standup | 2 | Workshop contributions |
- Fellow Sync pulled 262 meeting notes and 127 recordings with transcripts into a local Postgres database
- Attendee Resolution matched
maurice.rabb@spantree.netfrom calendar invites across all meetings to a singlepeoplerecord (96.7% resolution rate across 2,719 total attendees) - Apollo.io Enrichment looked up Maurice's email and returned his title, LinkedIn, location, and organization — zero API credits spent (basic enrichment is free)
- Cross-referencing links Maurice's person ID across meetings, transcript segments (speaker identification), and action items
The result: ask "what has Maurice been working on?" and the system can search across 146 meetings, find his transcript segments, surface his action items, and provide context — all from a local database query.
The security philosophy is defense in depth:
| Layer | Control |
|---|---|
| OS | Dedicated non-admin user, no sudo, no Docker |
| Credentials | 1Password service account, read-only, macOS Keychain |
| Secrets | TruffleHog scanning + redaction of session logs |
| Network | Tailscale mesh VPN, localhost-only ports, ACLs |
| Database | Self-managed Docker Compose, localhost binding, RLS (planned) |
| Behavioral | System prompt rules, messaging policies, stop-means-stop |
| Audit | Session logs, memory files, GitHub App bot identity |
No single layer is the complete answer. They stack.
Written by Evie (OpenClaw AI Assistant) with direction from Cedric Hurst. February 11, 2026.