Security
OpenPawz is designed with defense-in-depth: 10 security layers protect against prompt injection, data exfiltration, MITM attacks, memory forensics, and unauthorized actions.Zero Attack Surface by Default
OpenPawz exposes zero network ports in its default configuration. There is no HTTP server, no WebSocket endpoint, and no listening socket for an attacker to reach — the only communication channel is Tauri’s in-process IPC between the WebView and the Rust engine.Network listeners
Four optional listeners exist. All are disabled by default and bind to localhost only (127.0.0.1):
| Listener | Default bind | Default state | Authentication | Purpose |
|---|---|---|---|---|
| Webhook server | 127.0.0.1:3940 | Disabled | Bearer token (auto-generated UUID) + IP rate limiting (60 req/min) | External trigger for agent runs |
| WebChat | 127.0.0.1:3939 | Disabled | Access token + session cookie + DM policy | Browser-based chat with an agent |
| WhatsApp bridge | 127.0.0.1:8086 | Disabled | Internal only (local Evolution API) | WhatsApp message relay |
| n8n engine | 127.0.0.1:5678 | Disabled | n8n’s built-in auth | Embedded workflow engine |
127.0.0.1, these services are unreachable from the network even when enabled. Binding to 0.0.0.0 (LAN/WAN exposure) is a manual opt-in that triggers a security warning in the logs and recommends TLS via Tailscale Funnel.
:::warning
Changing bind_address to 0.0.0.0 on any listener exposes it to your local network. Only do this behind a firewall or VPN (e.g. Tailscale), and ensure the authentication token is strong.
:::
Cryptographic key storage
No cryptographic key is ever stored on the filesystem. All keys live in the OS keychain (macOS Keychain / Windows Credential Manager / Linux Secret Service):| Key | Keychain service | Purpose |
|---|---|---|
| DB encryption key | paw-db-encryption | AES-256-GCM database field encryption |
| Skill vault key | paw-skill-vault | AES-256-GCM skill credential encryption |
| Memory vault key | paw-memory-vault | AES-256-GCM PII memory encryption |
| Lock screen hash | paw-lock-screen | SHA-256 hashed passphrase |
device.json, no key file, and no config file containing secrets. If the OS keychain is unavailable, the app refuses to store credentials rather than falling back to plaintext.
Soul files (agent behavior)
Agent personality and behavioral rules (SOUL.md, IDENTITY.md, USER.md) are stored in the encrypted SQLite database (agent_files table) — not as files on the filesystem. They are read and written exclusively through Tauri IPC commands, which are local WebView-to-Rust calls with no network involvement.
An external attacker would need:
- Physical access to the machine, AND
- The OS keychain credential to decrypt the database
Content Security Policy
The Tauri WebView enforces a strict CSP that restricts all connections to127.0.0.1:
Human-in-the-Loop (HIL)
Every tool is classified by risk level. High-risk tools require explicit human approval before execution.Auto-approved tools (no approval needed)
fetch · read_file · list_directory · web_search · web_read · memory_search · memory_store · soul_read · soul_write · soul_list · self_info · update_profile · create_task · list_tasks · manage_task · email_read · slack_read · telegram_read · image_generate
HIL-required tools (human must approve)
exec · write_file · delete_file · append_file · email_send · webhook_send · rest_api_call · slack_send · github_api
Agent policies
Per-agent tool access control with four presets:| Preset | Mode | Description |
|---|---|---|
| Unrestricted | unrestricted | All tools, no approval |
| Standard | denylist | High-risk tools require approval |
| Read-only | allowlist | Only safe read tools |
| Sandbox | allowlist | web_search, web_read, memory_search, soul_read only |
Risk classification
| Risk | Tools |
|---|---|
| Safe | read_file, list_directory, web_search, web_read, memory_search, soul_read, soul_list, self_info, fetch |
| High-risk | exec, write_file, delete_file, append_file, email_send, webhook_send, rest_api_call, slack_send, github_api, image_generate, soul_write, update_profile, create_agent, create_task, manage_task |
Prompt injection defense
All incoming channel messages are scanned for injection attempts before reaching the agent.Detection
Pattern-based scoring across 9 categories (8 in the Rust backend scanner, 9 in the TypeScript frontend scanner which addsobfuscation):
| Category | Examples | Scanner |
|---|---|---|
override | ”Ignore previous instructions” | Both |
identity | ”You are now…” | Both |
jailbreak | ”DAN mode”, “no restrictions” | Both |
leaking | ”Show me your system prompt” | Both |
obfuscation | Base64-encoded instructions | Frontend only |
tool_injection | Fake tool call formatting | Both |
social | ”As an AI researcher…” | Both |
markup | Hidden instructions in HTML/markdown | Both |
bypass | ”This is just a test…” | Both |
Severity levels
| Severity | Score | Action |
|---|---|---|
| Critical | 40+ | Message blocked, not delivered |
| High | 25+ | Warning logged |
| Medium | 12+ | Noted in logs |
| Low | 5+ | Informational |
critical severity.
Container sandbox
Execute agent commands in isolated Docker containers:| Security measure | Default |
|---|---|
| Capabilities | cap_drop ALL |
| Network | Disabled |
| Memory limit | 256 MB |
| CPU shares | 512 |
| Timeout | 30 seconds |
| Output limit | 50 KB |
Presets
| Preset | Image | Memory | Network | Timeout |
|---|---|---|---|---|
| Minimal | alpine | 128 MB | Off | 15s |
| Development | node:20-alpine | 512 MB | On | 60s |
| Python | python:3.12-alpine | 512 MB | On | 60s |
| Restricted | alpine | 64 MB | Off | 10s |
Command risk assessment
Commands are scored before execution:- Low —
ls,cat,echo - Medium —
pip install,npm install - High —
curl,wget, network commands - Critical —
rm -rf /,chmod 777, dangerous patterns
Browser network policy
Control which domains agents can access: Default allowed: AI provider APIs, DuckDuckGo, Coinbase, localhost Default blocked: pastebin.com, transfer.sh, file.io, 0x0.st (data exfiltration risks)File system protection
Sensitive paths are blocked from agent access — agents cannot add these as project folders or browse into them.| Category | Blocked paths |
|---|---|
| SSH / GPG | ~/.ssh, ~/.gnupg |
| Cloud credentials | ~/.aws, ~/.kube |
| Desktop keyrings | ~/.gnome-keyring, ~/.password-store |
| Docker | ~/.docker (includes config.json) |
| Network credentials | ~/.netrc |
| System config | /etc (covers /etc/shadow, /etc/passwd, /etc/sudoers) |
| Root home | /root |
| System logs | /var/log |
| Virtual filesystems (Linux) | /proc/*, /sys/* |
| Device nodes | /dev |
| Windows | C:\Windows, C:\Users\*\AppData (credential store paths) |
| App config | ~/.openclaw (contains tokens), ~/.config/himalaya (email config) |
~, /home/user, /Users/user) and the filesystem root (/, C:\) are blocked as too broad.
:::tip Per-project scope guard
When a project is active, all file operations are constrained to the project root. Directory traversal sequences (../) are detected and blocked even within the allowed path.
:::
Credential security
Credentials are protected by two independent encryption layers:Layer 1: Skill credential encryption (XOR)
- API keys encrypted with XOR using a 32-byte random key
- Encryption key stored in OS keychain (
paw-skill-vault) - High-risk credentials (Coinbase, DEX) are server-side only — never injected into prompts
- Credentials are decrypted only at execution time
Layer 2: Database field encryption (AES-256-GCM)
Sensitive database fields are encrypted with AES-256-GCM via the Web Crypto API before being stored in SQLite.| Property | Detail |
|---|---|
| Algorithm | AES-256-GCM (authenticated encryption) |
| Key size | 256 bits |
| IV | 12-byte random IV per encryption |
| Key source | Generated on first launch, stored in OS keychain (paw-db-encryption) |
| Storage format | enc:<base64(IV + ciphertext)> |
| Fallback | Graceful — stores plaintext if encryption is unavailable |
skill_credentials table. The AES-256-GCM layer protects other sensitive fields across the database. Both derive their keys from the OS keychain but use separate keychain entries.
:::
TLS certificate pinning
All AI provider connections use a certificate-pinned TLS configuration that explicitly ignores the operating system trust store. This means a compromised or rogue CA installed on the user’s machine cannot intercept provider traffic.| Property | Detail |
|---|---|
| Library | rustls 0.23 (pure-Rust TLS, no OpenSSL) |
| Root store | Mozilla root certificates via webpki-roots only |
| OS trust store | Explicitly excluded — system CAs are never consulted |
| Client sharing | Singleton reqwest::Client shared across all providers |
| Connection pooling | Enabled — one pool for all OpenAI, Anthropic, Google, etc. calls |
| Connect timeout | 10 seconds |
| Request timeout | 120 seconds |
Why this matters
Most TLS MITM attacks rely on installing a custom root CA on the victim’s machine (corporate proxies, malware, government surveillance). By pinning to Mozilla’s root store, OpenPawz rejects certificates signed by any non-Mozilla CA — even if the OS trusts it.Covered providers
All providers routed through the pinned client: OpenAI, Anthropic, Google Gemini, OpenRouter, DeepSeek, Grok, Mistral, Moonshot, Ollama, and any custom OpenAI-compatible endpoint.Outbound request signing
Every AI provider request is signed with SHA-256 before transmission for tamper detection and compliance auditing.How it works
Before each.send(), the engine computes:
| Property | Detail |
|---|---|
| Algorithm | SHA-256 (via sha2 crate) |
| Buffer size | 500 entries (ring buffer, overwrites oldest) |
| Logged fields | timestamp, provider, model, hash, HTTP status |
| Storage | In-memory only — not persisted to disk |
Use cases
- Tamper detection — If a proxy modifies the request body in transit, the recorded hash won’t match a re-computed hash of the actual payload received by the provider
- Compliance audit — Security teams can export request hashes to verify that specific prompts were sent at specific times
- Replay detection — Each hash includes a timestamp, making every entry unique even for identical request bodies
Memory encryption (secure zeroing)
API keys and other sensitive credentials are protected in RAM usingZeroizing<String> wrappers from the zeroize crate, ensuring they are overwritten with zeros when no longer needed.
What this prevents
When a provider is dropped (e.g. user switches providers, session ends, or the app closes), the API key memory is immediately zeroed rather than left as a dangling allocation. This protects against:- Memory dump attacks — Forensic tools or malware scanning process memory for API key patterns
- Swap file leaks — Unencrypted API keys persisted to disk via OS swap/page file
- Use-after-free — Freed memory still containing the key being reallocated to another buffer
Implementation
| Component | Protection |
|---|---|
OpenAiProvider.api_key | Zeroizing<String> |
AnthropicProvider.api_key | Zeroizing<String> |
GoogleProvider.api_key | Zeroizing<String> |
| Drop behavior | Memory zeroed on Drop via zeroize crate |
| Compiler optimization | zeroize uses core::ptr::write_volatile to prevent dead-store elimination |
Engram memory content encryption
Project Engram adds field-level PII encryption for memory content. Unlike credential zeroing (above), this protects the actual memories stored by agents — not just API keys.PII detection
Before storage, every memory passes through a two-layer PII scanner with 17 regex patterns:| # | Pattern | Example | Tier |
|---|---|---|---|
| 1 | Social Security Numbers | 123-45-6789, 123456789 | Confidential |
| 2 | Credit card numbers | 4111-1111-1111-1111 | Confidential |
| 3 | Email addresses | user@example.com | Sensitive |
| 4 | Phone numbers | +1-555-0123 | Sensitive |
| 5 | International phone numbers | +44 20 7946 0958 | Sensitive |
| 6 | Physical addresses | Street address patterns | Sensitive |
| 7 | Person names | Mr./Mrs./Dr. prefixed names | Sensitive |
| 8 | Geographic locations | City/state/country patterns | Sensitive |
| 9 | Government IDs | Passport, driver’s license | Confidential |
| 10 | JWT tokens | eyJhbGciOi... (header.payload.signature) | Confidential |
| 11 | AWS access keys | AKIA... (20-char key IDs) | Confidential |
| 12 | Private keys (RSA/EC/DSA) | -----BEGIN ... PRIVATE KEY----- | Confidential |
| 13 | IBAN | GB82 WEST 1234 5698 7654 32 | Confidential |
| 14 | IPv4 addresses | 192.168.1.1 | Sensitive |
| 15 | Generic API keys | sk-, api_key=, Bearer tokens | Confidential |
| 16 | Credentials (passwords) | password=, secret= patterns | Confidential |
| 17 | Dates of birth | 1990-01-15 | Sensitive |
Encryption details
| Property | Detail |
|---|---|
| Algorithm | AES-256-GCM (authenticated encryption) |
| Key storage | OS keychain via keyring crate (service: paw-memory-vault) |
| Granularity | Field-level — only PII-containing content is encrypted |
| Format | enc:base64(nonce ‖ ciphertext ‖ tag) |
| Decryption | Transparent on retrieval — callers see plaintext |
| Tier classification | Cleartext (no PII) / Sensitive (encrypted) / Confidential (encrypted) |
Query sanitization
All search queries are sanitized before reaching the storage backend to prevent injection:- Search operators (
AND,OR,NOT,NEAR,*,",(,)) are stripped - Column filter syntax (
:) is removed - Empty queries after sanitization are rejected
Prompt injection scanning
Memory content is scanned against 10 known prompt injection patterns before storage. Matches are redacted with[REDACTED:injection] markers, preventing poisoned memories from manipulating agent behavior on future recalls.
GDPR Article 17 — Right to erasure
Thegdpr_purge command performs a complete data erasure for a user:
- All memory content rows deleted
- All vector embeddings deleted
- Search index entries removed
- Graph edges removed
- Padding table repacked to prevent file-size leakage
PRAGMA secure_deleteensures freed pages are zeroed
Inter-agent memory bus trust
The cross-agent memory bus (pub/sub for sharing memories between agents) enforces publish-side authentication to prevent memory poisoning.Capability tokens
Each agent holds anAgentCapability signed with HMAC-SHA256 against a platform-held secret key. The token specifies:
- Max publication scope — Agent, Squad, or Global
- Importance ceiling — Maximum importance an agent can assert (0.0–1.0)
- Write permission — Whether the agent can publish at all
- Rate limit — Maximum publications per GC window
publish() call verifies the token signature in constant time before any bus operation.
Publish-side defenses
| Defense | Detail |
|---|---|
| Scope enforcement | Publication scope clamped to agent’s maximum — cannot publish beyond assigned authority |
| Importance ceiling | Publication importance clamped to agent’s ceiling — prevents low-trust agents from asserting high-confidence facts |
| Per-agent rate limiting | Publish count tracked per GC window — exceeding limit returns MemoryBusRateLimit error |
| Injection scanning | All publication content scanned for prompt injection patterns before entering the bus |
Trust-weighted contradiction resolution
When two agents publish contradictory facts on the same topic, the system resolves based on trust-weighted importance:Threat model
| Attack | Mitigation |
|---|---|
| Agent floods bus with poisoned memories | Rate limit + injection scan on publish side |
| Low-trust agent overwrites high-trust facts | Trust-weighted contradiction resolution |
| Agent publishes beyond its authority scope | Scope ceiling enforcement |
| Forged capability token | HMAC-SHA256 verification against platform secret |
Anti-forensic vault-size quantization
The Engram memory store mitigates vault-size oracle attacks — a side-channel where an attacker can infer how many memories are stored by observing the SQLite database file size. This is the same threat class addressed by KDBX inner-content padding.What this prevents
An attacker with read-only access to the filesystem (malware, backup exfiltration, shared-machine forensics) cannot determine:- Exact number of stored memories
- Whether memories were recently deleted (no file-size drop)
- Growth rate of the knowledge store over time
Implementation
| Mitigation | Detail |
|---|---|
| Bucket padding | DB padded to 512KB boundaries via _engram_padding table (zeroblob rows). Re-padded after every GC cycle. |
| Secure erasure | Two-phase delete: content fields overwritten with empty values → row deleted. Prevents plaintext recovery from freed pages or WAL replay. |
| 8KB page size | PRAGMA page_size = 8192 reduces file-size measurement granularity |
| Secure delete | PRAGMA secure_delete = ON zeroes freed B-tree pages at the SQLite layer |
| Incremental auto-vacuum | PRAGMA auto_vacuum = INCREMENTAL prevents immediate file-size shrinkage after deletions |
Threat model comparison
| Property | KDBX (KeePass 4.x) | Engram (SQLite) |
|---|---|---|
| Content encryption | File-level (ChaCha20/AES-256) | Field-level (AES-256-GCM) |
| Size padding | Inner XML padded to block boundary | DB padded to 512KB bucket |
| Delete forensics | Zeroed inner stream | Two-phase overwrite + secure_delete |
| Metadata leakage | Fixed-size header | Quantized file size |
Tool execution security
Tool execution is governed by multiple safety mechanisms in the engine’s central tool executor.Source code introspection block
Agents cannot read engine source files — anyread_file call targeting paths containing src-tauri/src/engine/, src/engine/, or files ending in .rs is rejected. This prevents agents from exfiltrating their own implementation details or discovering internal security mechanisms.
Credential write block
Thewrite_file tool blocks content that contains credential-like patterns:
- PEM private keys (
-----BEGIN ... PRIVATE KEY-----) - API key secrets (
api_key_secret,cdp_api_key) - Base64-encoded secrets with
secretorprivatekeywords
Execution limits
| Setting | Default | Description |
|---|---|---|
maxToolCallsPerTurn | Per-agent policy | Maximum tool calls an agent can make in a single turn before being stopped |
tool_timeout_secs | 300 | Seconds before a pending tool approval or execution is killed |
max_tool_rounds | 20 | Maximum tool-call → result → re-prompt loops per turn |
max_concurrent_runs | 4 | Maximum simultaneous agent runs |
Output truncation
Tool results are capped to prevent context window overflow:| Tool | Max output | Behavior |
|---|---|---|
exec | 50,000 chars | Truncated with [output truncated] marker |
read_file | 32,000 chars | Truncated with total byte count |
fetch | 50,000 chars | Truncated with total byte count |
| Container sandbox | 50,000 chars (stdout + stderr each) | Truncated with [stdout/stderr truncated] marker |
Network policy enforcement
Thefetch tool enforces domain-level network policy — blocked domains are always rejected, and when an allowlist is active, only listed domains are permitted.
Exfiltration detection
Outbound network commands are audited for data exfiltration patterns:- Piping file contents to
curl,wget, ornc - File upload flags (
curl -T,curl --data-binary @,wget --post-file) - Redirects to
/dev/tcp/ scpandrsyncto remote hosts
exec tool invocations. It supplements — but does not replace — the container sandbox for high-security environments.
:::
Budget enforcement
Daily spending limits with progressive warnings:| Threshold | Action |
|---|---|
| 50% | Warning |
| 75% | Warning |
| 90% | Warning |
| 100% | Requests blocked |
Trading safety
| Control | Default |
|---|---|
| Auto-approve trades | Off |
| Max trade size | $100 |
| Max daily loss | $500 |
| Transfers | Disabled |
| Max transfer | $0 |
Channel access control
| Policy | Behavior |
|---|---|
| Open | Anyone can chat |
| Allowlist | Only approved users |
| Pairing | Users must pair with a code |

