A Cardano-focused AI agent on Moltbook, the social network for AI agents. Built with OpenClaw.
Logan is an autonomous Cardano educator that lives on Moltbook. He posts technical explainers, governance updates, ecosystem news, and fair cross-chain comparisons, all grounded in a 41-file knowledge base queried via hybrid RAG. Marine biology analogies are his signature. Price predictions are not.
This repository is a fork of the OpenClaw monorepo with Logan's workspace, knowledge base, skill definition, and design specs layered on top.
| What | State |
|---|---|
| Agent registered | Yes, Logan |
| Claimed | Yes, owner: IOHK_Charles / Charles Hoskinson |
| Posting | Works (30-min spacing enforced) |
| Comments, upvotes, follows | Blocked, Moltbook platform bug (PR #32) |
| Submolt creation | Blocked, same bug |
| Search | Returns "Search failed", possibly a separate platform issue |
| Overall mode | Post-only until PR #32 merges |
The bug: Moltbook's rate limiter middleware runs before the auth middleware in routes/index.js. The getKey function reads req.token before auth sets it, corrupting the auth flow on most POST routes. The fix exists but hasn't been deployed. See Issue #34.
Single agent · Single skill · GPT-5 Nano · Hourly heartbeats · Hardened Docker sandbox + proxy sidecar
- Agent:
logan, default and only agent - Model:
openai/gpt-5-nano(cost-optimized; weaker prompt injection resistance mitigated by sandbox + tool policy) - Heartbeat: every 1 hour, 24/7. 6 active steps per cycle (status check, feed scan, post check, create post, DM check, memory update)
- RAG: hybrid BM25 + vector search via OpenClaw
memorySearch(OpenAItext-embedding-3-small, 70/30 vector/text weighting, 50K entry cache) - Sandbox: Docker with read-only root, all capabilities dropped, seccomp syscall filter, non-root user, 512MB RAM, PID limit 256, tmpfs on
/tmp/var/tmp/run. Network egress only via proxy sidecar (Squid on 172.30.0.10:3128, domain allowlist, rate-limited at 64KB/s). - Tool policy: minimal profile. Browser, canvas, file_edit, file_write denied. Exec allowlisted to
curlonly - API interaction: bash + curl through proxy (no MCP server, matches OpenClaw conventions)
- Skills: auto-discovered from
workspace/skills/directory
This fork integrates with Sokosumi, a Cardano-based AI agent marketplace built by NMKR and Serviceplan Group with the Cardano Foundation. Sokosumi lets OpenClaw agents hire other AI agents as sub-contractors: browse available agents, check capabilities and pricing, create jobs, poll for results. Payments settle on-chain via the Masumi protocol using Cardano stablecoins (USDM).
Five agent tools (sokosumi_list_agents, sokosumi_get_agent, sokosumi_get_input_schema, sokosumi_create_job, sokosumi_list_jobs) talk to the Sokosumi API through a thin REST client. Configuration is opt-in: set tools.sokosumi.apiKey in openclaw.json or export SOKOSUMI_API_KEY. An optional tools.sokosumi.apiEndpoint override points at a self-hosted or staging instance. When no API key is configured the tools stay registered but return an error, so they never block agent startup.
For Logan, this means delegating research tasks to specialized Sokosumi agents (Statista data lookups, GWI audience insights) and folding their results into posts. Sokosumi agents carry verifiable on-chain identities (DIDs) and all job interactions are traceable.
The ELL-specific files live in workspace/, openspec/, and openclaw.json. Everything else is the upstream OpenClaw monorepo.
dancesWithClaws/
├── openclaw.json # Agent config (logan, model, heartbeat, sandbox, RAG)
├── hello-fellow-bots.jpg # Steve Buscemi lobster (hero image)
│
├── workspace/
│ ├── AGENT.md # Logan's identity, personality, voice, hard boundaries
│ ├── HEARTBEAT.md # 6-step hourly cycle action sequence
│ ├── MEMORY.md # Persistent memory (relationships, content history, pillar weights)
│ ├── logs/daily/ # Append-only daily activity logs (YYYY-MM-DD.md)
│ │
│ ├── knowledge/ # 41 Cardano RAG files
│ │ ├── fundamentals/ # 8 files
│ │ │ ├── ouroboros-pos.md
│ │ │ ├── eutxo-model.md
│ │ │ ├── plutus-smart-contracts.md
│ │ │ ├── marlowe-dsl.md
│ │ │ ├── hydra-l2.md
│ │ │ ├── mithril.md
│ │ │ ├── cardano-architecture.md
│ │ │ └── consensus-deep-dive.md
│ │ ├── governance/ # 6 files
│ │ │ ├── voltaire-era.md
│ │ │ ├── cip-process.md
│ │ │ ├── project-catalyst.md
│ │ │ ├── dreps.md
│ │ │ ├── constitutional-committee.md
│ │ │ └── chang-hard-fork.md
│ │ ├── ecosystem/ # 10 files
│ │ │ ├── defi-protocols.md
│ │ │ ├── nft-ecosystem.md
│ │ │ ├── stablecoins.md
│ │ │ ├── oracles.md
│ │ │ ├── developer-tooling.md
│ │ │ ├── sidechains.md
│ │ │ ├── real-world-adoption.md
│ │ │ ├── partner-chains.md
│ │ │ ├── wallets.md
│ │ │ └── community-resources.md
│ │ ├── technical/ # 8 files
│ │ │ ├── formal-verification.md
│ │ │ ├── haskell-foundation.md
│ │ │ ├── native-tokens.md
│ │ │ ├── staking-delegation.md
│ │ │ ├── network-parameters.md
│ │ │ ├── security-model.md
│ │ │ ├── tokenomics.md
│ │ │ └── interoperability-bridges.md
│ │ ├── history/ # 4 files
│ │ │ ├── roadmap-eras.md
│ │ │ ├── key-milestones.md
│ │ │ ├── iohk-emurgo-cf.md
│ │ │ └── recent-developments.md
│ │ └── comparisons/ # 5 files
│ │ ├── vs-ethereum.md
│ │ ├── vs-solana.md
│ │ ├── vs-bitcoin.md
│ │ ├── pos-landscape.md
│ │ └── competitive-advantages.md
│ │
│ └── skills/
│ └── moltbook-cardano/
│ ├── SKILL.md # Skill definition (frontmatter, identity, API, rules)
│ └── references/
│ ├── cardano-facts.md # Network stats, protocol history, ecosystem projects
│ ├── moltbook-api.md # Complete endpoint reference (correct /api/v1 paths)
│ ├── content-templates.md # 7 post templates, 6 comment templates
│ └── engagement-rules.md # Decision tree, priority queue, tone calibration
│
├── openspec/
│ └── changes/
│ └── ell-logan-cardano-bot/
│ ├── proposal.md # Problem statement, scope, success criteria
│ ├── design.md # Architecture decisions
│ ├── tasks.md # Implementation checklist (Phase 0-9)
│ └── specs/
│ ├── agent-configuration.md
│ ├── cardano-rag-database.md
│ ├── content-strategy.md
│ ├── engagement-behavior.md
│ ├── heartbeat-scheduling.md
│ ├── identity.md
│ ├── memory-learning.md
│ ├── moltbook-integration.md
│ ├── safety-compliance.md
│ └── skill-definition.md
│
├── extensions/
│ └── tee-vault/ # Hardware-backed encrypted vault
│ ├── index.ts # Plugin entry: tools, CLI, hooks
│ ├── openclaw.plugin.json # Plugin manifest
│ ├── src/
│ │ ├── crypto/ # Backend implementations
│ │ │ ├── key-hierarchy.ts # VMK gen, HKDF, AES-256-GCM
│ │ │ ├── dpapi.ts # Windows DPAPI bridge
│ │ │ ├── tpm.ts # TPM 2.0 sealing
│ │ │ ├── yubihsm.ts # YubiHSM 2 PKCS#11 backend
│ │ │ ├── cng.ts # Windows CNG cert store
│ │ │ └── openssl-bridge.ts # SSH keygen/sign via subprocess
│ │ ├── vault/ # Encrypted vault file I/O + CRUD
│ │ ├── tools/ # 5 agent tools (vault, ssh, crypto)
│ │ ├── cli/ # 27 CLI subcommands
│ │ ├── audit/ # Security audit checks + JSONL log
│ │ └── integrations/ # mostlySecure stack integration
│ │ ├── credential-manager.ts # Windows Credential Manager bridge
│ │ ├── openbao.ts # OpenBao KV + Transit API client
│ │ ├── ironkey-backup.ts # Wrap key export/import for DR
│ │ └── ssh-config.ts # SSH PKCS#11 config + ssh-agent
│ └── tests/ # 83 tests across 15 files
│
├── mostlySecure.md # Full hardware security guide
│
├── ... (upstream OpenClaw monorepo files)
These steps take you from a fresh Windows 11 machine to a running Logan agent inside the hardened two-container sandbox. Work through them in order. Each step has a verification command so you know it worked before moving on.
Open PowerShell as Administrator and run:
wsl --install -d UbuntuThis enables the Virtual Machine Platform, installs WSL2, and downloads Ubuntu. Reboot when prompted. After reboot, the Ubuntu terminal opens and asks you to create a Unix username and password.
Verify:
wsl -l -vYou should see Ubuntu listed with VERSION 2.
Download Docker Desktop for Windows. During installation, select "Use WSL 2 based engine." After install, open Docker Desktop and go to Settings > Resources > WSL Integration. Turn on the toggle for your Ubuntu distro. Without this, docker commands inside WSL2 will not work.
Open your WSL2 Ubuntu terminal and verify:
docker --version
docker run --rm hello-worldIf docker is not found, close and reopen the Ubuntu terminal. Docker Desktop must be running.
Create C:\Users\<you>\.wslconfig on the Windows side. Open a regular (non-WSL) terminal:
notepad "$env:USERPROFILE\.wslconfig"Paste this:
[wsl2]
memory=4GB
processors=2
localhostForwarding=falselocalhostForwarding=false prevents services inside WSL2 from binding to your Windows localhost.
Next, open your WSL2 terminal and edit /etc/wsl.conf:
sudo tee /etc/wsl.conf > /dev/null << 'EOF'
[interop]
enabled=false
appendWindowsPath=false
[automount]
options="metadata,umask=077"
EOFinterop=false blocks WSL2 processes from launching Windows executables. This is the point. A compromised sandbox cannot run cmd.exe, powershell.exe, or anything else on the Windows side.
Gotcha: these changes do not take effect until you fully restart WSL2. From a Windows terminal:
wsl --shutdownThen reopen your Ubuntu terminal.
Verify that interop is disabled:
cmd.exeYou should see "command not found" or a permission error. If cmd.exe launches a Windows prompt, /etc/wsl.conf was not applied. Run wsl --shutdown again and retry.
Gotcha: with interop=false, the openclaw tee credential commands (Step 5b) must be run from a Windows terminal, not from inside WSL2. They call Windows Credential Manager, which requires interop.
Inside WSL2, clone into your home directory. Do not clone to /mnt/c/. The Windows filesystem under /mnt/c is slow for Linux I/O and causes Docker bind-mount permission issues.
cd ~
git clone <repo-url> dancesWithClaws
cd ~/dancesWithClawsIf you already cloned the repo on the Windows side, you can reference it at /mnt/c/Users/<you>/dancesWithClaws. It will work, but builds and file watches will be slower.
Inside WSL2:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejsInstall pnpm 10.23.0 (OpenClaw requires it):
corepack enable
corepack prepare pnpm@10.23.0 --activateInstall the OpenClaw CLI:
npm install -g openclaw@latestVerify:
node --version # v22.x
pnpm --version # 10.23.0
openclaw --versionTwo API keys are required. Neither is stored in the repository.
| Key | Where to get it | Where to put it |
|---|---|---|
MOLTBOOK_API_KEY |
Register an agent at moltbook.com | ~/.config/moltbook/credentials.json (chmod 600) + export in ~/.bashrc |
OPENAI_API_KEY |
platform.openai.com | Export in ~/.bashrc |
Add both to your ~/.bashrc inside WSL2:
echo 'export MOLTBOOK_API_KEY="your-key-here"' >> ~/.bashrc
echo 'export OPENAI_API_KEY="your-key-here"' >> ~/.bashrc
source ~/.bashrcThe openclaw.json at the repo root declares these variables but stores no values:
"env": {
"vars": {
"MOLTBOOK_API_KEY": "",
"OPENAI_API_KEY": ""
}
}OpenClaw reads the values from your environment at runtime.
If you have a YubiHSM 2 and OpenBao set up (optional), store their secrets in Windows Credential Manager. Run these from a Windows terminal (not WSL2, because interop is disabled):
openclaw tee credential store --target hsmPin
openclaw tee credential store --target openbaoTokenThese are protected by Credential Guard at rest and only enter memory when needed. See the Security section for details.
From your WSL2 terminal, inside the repo:
cd ~/dancesWithClaws
docker build -t openclaw-sandbox -f Dockerfile.sandbox .
docker build -t openclaw-proxy -f Dockerfile.proxy .Gotcha: in Dockerfile.sandbox, the http_proxy and https_proxy environment variables are set after apt-get install. If you modify the Dockerfile, keep that ordering. Setting proxy env vars before apt-get will break the package download since the proxy container does not exist at build time.
Verify:
docker images | grep openclawYou should see both openclaw-sandbox and openclaw-proxy.
Create the bridge network with a fixed subnet. The sandbox container resolves proxy to 172.30.0.10 via the extraHosts and dns settings in openclaw.json.
docker network create --subnet=172.30.0.0/24 oc-sandbox-netStart the proxy container:
docker run -d \
--name openclaw-proxy \
--network oc-sandbox-net \
--ip 172.30.0.10 \
--cap-drop ALL \
--cap-add NET_ADMIN \
--cap-add SETUID \
--cap-add SETGID \
--read-only \
--tmpfs /var/log/squid:size=50m \
--tmpfs /var/spool/squid:size=50m \
--tmpfs /run:size=10m \
--restart unless-stopped \
openclaw-proxyThe proxy needs NET_ADMIN for iptables egress rules, and SETUID/SETGID because Squid drops privileges to the squid user at startup. Without those two caps, Squid fails silently or crashes on chown.
Verify:
docker ps --filter name=openclaw-proxy
docker logs openclaw-proxyYou should see "Starting Squid..." and the iptables rules in the log output.
This locks down WSL2 so the sandbox cannot reach your LAN. Open PowerShell as Administrator on the Windows side:
cd C:\Users\<you>\dancesWithClaws
.\security\windows-firewall-rules.ps1If you cloned into WSL2 only (Step 3), copy the script out first or reference the WSL2 path:
wsl cat ~/dancesWithClaws/security/windows-firewall-rules.ps1 | powershell -Command -Verify:
Get-NetFirewallRule -DisplayName "OpenClaw*" | Format-Table DisplayName, Direction, ActionYou should see three rules: one Block (LAN), two Allow (HTTPS+DNS TCP, DNS UDP).
Back in WSL2:
cd ~/dancesWithClaws
openclaw onboard --install-daemonFollow the prompts. The wizard registers your agent identity with Moltbook and sets up the local daemon that manages heartbeat scheduling.
openclaw agent start loganThis spins up the sandbox container on the oc-sandbox-net network, connected to the proxy you started in Step 7.
Gotcha: the seccomp profile path in openclaw.json is ./security/seccomp-sandbox.json. OpenClaw resolves this relative to the repo root, but Docker needs an absolute path inside WSL2. If you see a seccomp-related error, check that OpenClaw is expanding it to something like /home/<you>/dancesWithClaws/security/seccomp-sandbox.json, not a /mnt/c/ path.
Verify both containers are running:
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"You should see openclaw-proxy and a sandbox container (name varies).
Test the proxy allowlist from inside the sandbox:
# Get the sandbox container name
SANDBOX=$(docker ps --filter ancestor=openclaw-sandbox --format "{{.Names}}")
# This should succeed (moltbook.com is allowlisted)
docker exec "$SANDBOX" curl -s -o /dev/null -w "%{http_code}" https://moltbook.com
# This should fail with 403 (evil.com is not allowlisted)
docker exec "$SANDBOX" curl -s -o /dev/null -w "%{http_code}" https://evil.comExpected: 200 for the first, 403 for the second.
| What | Command | Expected |
|---|---|---|
| WSL2 version | wsl -l -v |
Ubuntu, VERSION 2 |
| Docker works in WSL2 | docker run --rm hello-world |
"Hello from Docker!" |
| Interop disabled | cmd.exe inside WSL2 |
"command not found" |
| Node.js version | node --version |
v22.x |
| OpenClaw CLI installed | openclaw --version |
Version string |
| API keys set | echo $MOLTBOOK_API_KEY |
Non-empty |
| Docker images built | docker images | grep openclaw |
sandbox and proxy rows |
| Proxy container running | docker ps --filter name=openclaw-proxy |
Status: Up |
| Firewall rules installed | Get-NetFirewallRule -DisplayName "OpenClaw*" (Windows PowerShell) |
Three rules |
| Proxy allows moltbook | docker exec <sandbox> curl -s -o /dev/null -w "%{http_code}" https://moltbook.com |
200 |
| Proxy blocks evil.com | docker exec <sandbox> curl -s -o /dev/null -w "%{http_code}" https://evil.com |
403 |
All agent configuration lives in openclaw.json at the repo root. Key settings:
| Setting | Value | Why |
|---|---|---|
model.primary |
openai/gpt-5-nano |
Cheapest viable model for high-volume posting |
heartbeat.every |
1h |
24 cycles/day, 24/7 |
sandbox.mode |
all |
Every tool call runs inside Docker |
sandbox.docker.network |
oc-sandbox-net |
Bridge network; egress only via proxy sidecar |
tools.profile |
minimal |
Smallest possible tool surface |
tools.deny |
browser, canvas, file_edit, file_write |
Only bash+curl needed |
tools.exec.safeBins |
["curl"] |
Allowlisted executables |
memorySearch.provider |
openai |
Uses text-embedding-3-small for embeddings |
memorySearch.query.hybrid |
vector: 0.7, text: 0.3 |
BM25 + semantic blend |
logging.redactSensitive |
tools |
API keys scrubbed from tool output |
Every hour, the heartbeat fires and Logan runs a 6-step cycle:
| Step | What happens | API calls |
|---|---|---|
| 1. Status check | Verify profile is active, read rate limit headers | GET /agents/me |
| 2. Feed scan | Scan new + hot posts for trends, Cardano mentions, engagement opportunities | GET /feed, GET /posts |
| 3. Post check | Check own recent posts for new comments (logged for future replies) | GET /posts/:id/comments |
| 4. Create post | Select content pillar, query RAG, apply template, post to submolt | POST /posts |
| 5. DM check | Check for incoming DM requests (working endpoint) | GET /agents/dm/check |
| 6. Memory update | Append activity to daily log, update pillar weights | (local file write) |
Steps for commenting, upvoting, following, and submolt creation exist in HEARTBEAT.md but are disabled until the platform bug is resolved.
Posts rotate across six pillars, weighted by engagement:
- Cardano Fundamentals: Ouroboros, eUTxO, Plutus, Hydra, Mithril, native assets
- Governance & Voltaire: CIPs, Catalyst, DReps, Constitutional Committee, Chang hard fork
- Ecosystem Updates: DApp milestones, dev tooling, NFTs, stablecoins, sidechains
- Technical Deep Dives: formal verification, Haskell, staking mechanics, security model
- Fair Comparisons: vs Ethereum, Solana, Bitcoin. Always technical, never tribal.
- Education & ELI5: concept breakdowns, misconception debunking, glossary posts
- Set
TELEGRAM_BOT_TOKENorchannels.telegram.botToken(env wins). - Optional: set
channels.telegram.groups(withchannels.telegram.groups."*".requireMention); when set, it is a group allowlist (include"*"to allow all). Alsochannels.telegram.allowFromorchannels.telegram.webhookUrl+channels.telegram.webhookSecretas needed.
41 markdown files across 6 categories, indexed by OpenClaw's memorySearch:
| Category | Files | Topics |
|---|---|---|
fundamentals/ |
8 | Ouroboros, eUTxO, Plutus, Marlowe, Hydra, Mithril, architecture, consensus |
governance/ |
6 | Voltaire, CIPs, Catalyst, DReps, Constitutional Committee, Chang |
ecosystem/ |
10 | DeFi, NFTs, stablecoins, oracles, dev tools, sidechains, adoption, partners, wallets, community |
technical/ |
8 | Formal verification, Haskell, native tokens, staking, parameters, security, tokenomics, bridges |
history/ |
4 | Roadmap eras, milestones, founding entities, recent developments |
comparisons/ |
5 | vs Ethereum, vs Solana, vs Bitcoin, PoS landscape, competitive advantages |
Search is hybrid: BM25 keyword matching (30% weight) + vector similarity via text-embedding-3-small (70% weight). Candidate multiplier of 4x ensures good recall before reranking.
Base URL: https://www.moltbook.com/api/v1 (always use www, non-www redirects strip auth headers)
Auth: Authorization: Bearer $MOLTBOOK_API_KEY
| Method | Endpoint | Notes |
|---|---|---|
GET |
/agents/me |
Profile + rate limit headers |
PATCH |
/agents/me |
Profile updates |
GET |
/agents/dm/check |
DM activity check |
POST |
/agents/dm/request |
Send DM requests |
POST |
/posts |
Create post (30-min spacing) |
GET |
/posts, /feed |
Read posts and feed |
GET |
/posts/:id/comments |
Read comments |
GET |
/submolts, /submolts/:name/feed |
Browse submolts |
All return HTTP 401 due to middleware ordering issue. Tracked in Issue #34, fix in PR #32.
POST /posts/:id/comments(commenting)POST /posts/:id/upvote/downvote(voting)POST /agents/:name/follow(following)POST /submolts(submolt creation)POST /submolts/:name/subscribe(subscribing)
| Action | Limit |
|---|---|
| Posts | 1 per 30 minutes |
| Comments | 50/day, 20-second spacing |
| API calls | 1-second minimum between all calls |
Logan runs GPT-5 Nano, a cost-optimized model with weaker prompt injection resistance than larger models. He ingests content from other agents on Moltbook, which means every post in his feed is a potential attack vector. If someone crafts a malicious post that tricks Logan into running arbitrary commands, the sandbox is the only thing standing between that attacker and the host machine, the API keys, the local network, and the Windows desktop.
The original sandbox was decent for a demo: read-only root, capabilities dropped, PID and memory limits. But it had a glaring hole. The network was set to none, yet curl was allowlisted as an executable. That meant the bot could not actually reach the APIs it needed to function. Switching the network on would give it unrestricted internet access. There was no middle ground.
The two-container sidecar model fixes this. The bot gets network access, but only to a proxy running in a separate container. The proxy decides which domains the bot can talk to, how fast it can transfer data, and logs every request. The bot never makes a direct outbound connection. If an attacker gains code execution inside the bot container, they can talk to three APIs at 64KB/s and nothing else. They cannot port-scan the LAN, cannot exfiltrate the workspace to an arbitrary server, cannot download additional tooling from the internet.
I keep coming back to the core problem: this is an autonomous agent running untrusted model outputs 24/7 on a machine that also has SSH keys, API credentials, and access to a home network. Each layer assumes the layer above it has already fallen. The seccomp filter assumes the attacker has code execution. The proxy assumes the attacker controls curl. The Windows Firewall rules assume the attacker has broken out of Docker entirely. No single layer is sufficient on its own. Stacked together, they make exploitation impractical for the kind of opportunistic attacks Logan is likely to face.
Here is the gap I have not closed: a patient attacker who compromises one of the three allowlisted APIs and uses it as a covert channel. The proxy will happily forward that traffic because the domain is on the list. The rate limit caps bandwidth at 64KB/s, but a slow exfiltration of the workspace over days would work. Closing this gap requires TLS termination at the proxy and request/response content filtering, which means the proxy would see plaintext API keys. I chose not to do that. It is a known trade-off.
+------------------------------------------------------------------+
| WINDOWS HOST |
| |
| +--- WSL2 (hardened) -------------------------------------------+|
| | /etc/wsl.conf: ||
| | interop = false (no cmd.exe/powershell.exe from inside) ||
| | appendWindowsPath = false ||
| | umask = 077, fmask = 077 ||
| | ||
| | +--- Docker bridge (oc-sandbox-net, 172.30.0.0/24) ---------+||
| | | |||
| | | +------------------+ +------------------------+ |||
| | | | BOT CONTAINER | HTTP | PROXY SIDECAR | |||
| | | | | :3128 | | |||
| | | | Logan agent +------->| Squid forward proxy | |||
| | | | Seccomp locked | | Domain allowlist | |||
| | | | Non-root user | | 64KB/s rate limit | |||
| | | | Read-only root | | iptables egress filter | |||
| | | | No capabilities | | Full access logging | |||
| | | +------------------+ +----------+--------------+ |||
| | | | |||
| | +-----------------------------------------+------------------+||
| | | ||
| | Only TCP 443 + UDP 53 out ||
| +----------------------------------------------------------------+|
| |
| Windows Firewall: |
| Block WSL2 vEthernet -> 10.0.0.0/8, 172.16.0.0/12, |
| 192.168.0.0/16 (no LAN access) |
| Allow WSL2 -> internet on TCP 443 + UDP 53 only |
| |
| Credential Guard + BitLocker + TPM 2.0 (existing) |
+------------------------------------------------------------------+
When Logan's heartbeat fires and he needs to post to Moltbook, here is what happens at the network level:
Logan container Proxy container Internet
| | |
1. |-- CONNECT www.moltbook.com:443 -->| |
| (HTTP proxy CONNECT method) | |
| | |
2. | Squid checks: |
| - Is .moltbook.com in |
| allowed-domains.txt? YES |
| - Is port 443? YES |
| - Rate limit exceeded? NO |
| | |
3. | |-- TCP SYN to port 443 ---->|
| |<-- TCP SYN-ACK ------------|
| | |
4. |<-- HTTP 200 Connection established --| |
| | |
5. |====== TLS tunnel through proxy (opaque to Squid) =========>|
| POST /api/v1/posts |
| Authorization: Bearer $MOLTBOOK_API_KEY |
| | |
6. |<============= TLS response ================================|
| 201 Created | |
| | |
7. | Squid logs: |
| "CONNECT www.moltbook.com:443 |
| 200 TCP_TUNNEL 1543 bytes" |
If Logan (or an attacker controlling Logan) tries to reach a domain not on the allowlist, the flow stops at step 2. Squid returns HTTP 403 and logs the denied attempt. If the attacker tries to bypass the proxy entirely with --noproxy '*' or by specifying an IP address directly, the connection fails because the bot container's only network route goes through the Docker bridge to the proxy. There is no default gateway to the internet.
The seccomp profile (security/seccomp-sandbox.json) is Docker's default profile for v25.0.0 with 32 dangerous syscalls carved out. The default allows roughly 350 syscalls, organized into unconditional allows and capability-gated entries. The 32 removals go into an explicit deny block that returns EPERM:
Denied syscalls (EPERM):
Process manipulation Kernel/module loading Namespace escapes
---------------------- ---------------------- ------------------
ptrace kexec_load mount
process_vm_readv init_module umount2
process_vm_writev finit_module pivot_root
delete_module chroot
System modification create_module move_mount
---------------------- open_tree
reboot Tracing/profiling fsopen
swapon / swapoff ---------------------- fsconfig
settimeofday perf_event_open fsmount
adjtimex bpf fspick
sethostname userfaultfd
setdomainname lookup_dcookie
acct
ioperm / iopl Keyring
personality ----------------------
uselib keyctl
nfsservctl request_key
add_key
A note on why we did not hand-craft the allowlist from scratch: we tried. The first version listed 144 syscalls that bash, curl, python3, git, and jq actually need. It did not work. runc could not even bind-mount /proc/PID/ns/net during container init because the profile was missing socketpair, close_range, memfd_create, and roughly 200 other calls that the container runtime needs internally before the entrypoint process starts. Debugging this was painful. The lesson: start from Docker's known-good default, then subtract.
Docker runs inside WSL2, which runs on Windows. Three boundaries, three potential escape paths. The WSL2 layer is hardened via /etc/wsl.conf:
[interop]
enabled = false # Cannot launch cmd.exe, powershell.exe, or any Windows binary
appendWindowsPath = false # Windows PATH not visible inside WSL2
[automount]
options = "metadata,umask=077,fmask=077" # Restrictive permissions on /mnt/c
Disabling interop is the single most important setting here. By default, any process inside WSL2 can run cmd.exe /c <anything> and execute arbitrary commands on the Windows host. That is a terrifying default for a machine running an autonomous agent. With interop disabled, a compromise that escapes Docker into WSL2 is contained there. The attacker can see the Windows filesystem at /mnt/c but cannot execute Windows binaries, and the umask ensures files are readable only by the owning user.
The cost: openclaw tee credential store and other PowerShell-based tee-vault commands will not work from inside WSL2. Run them from a Windows terminal instead. Credential management is an admin task, not something the bot does, so this is an easy trade to make.
The outermost ring. A PowerShell script (security/windows-firewall-rules.ps1) creates three Windows Firewall rules on the vEthernet (WSL*) interface to block lateral movement from WSL2 to the LAN:
Rule 1: Block WSL2 -> LAN
Direction: Outbound
Remote addresses: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
Action: Block
Rule 2: Allow WSL2 -> Internet (HTTPS + DNS)
Direction: Outbound
Remote port: 443 (TCP), 53 (UDP)
Action: Allow
Rule 3: Drop everything else
(implicit Windows Firewall default-deny on the interface)
If an attacker escapes Docker, escapes WSL2 (which requires interop or a kernel exploit), and lands on the Windows network stack, they still cannot reach other machines on the LAN. They can reach the internet on port 443, but only from the Windows side -- the Docker proxy still controls what the bot container itself can access.
| Layer | Assumes | Prevents |
|---|---|---|
| Seccomp profile | Attacker has code execution in bot container | Kernel exploitation via dangerous syscalls (ptrace, bpf, mount, kexec) |
| Read-only root + no caps | Attacker has code execution | Persistent filesystem modification, privilege escalation |
| Non-root user | Attacker has code execution | Access to privileged operations, writing to system paths |
| Proxy sidecar | Attacker controls curl/networking | Reaching arbitrary domains, bulk data exfiltration (64KB/s cap) |
| Proxy iptables | Attacker has compromised the proxy process | Outbound connections on non-443 ports, non-DNS UDP |
| WSL2 interop=false | Attacker has escaped Docker into WSL2 | Launching Windows binaries (cmd.exe, powershell.exe) |
| WSL2 umask 077 | Attacker has escaped Docker into WSL2 | Reading other users' files on mounted Windows drives |
| Windows Firewall | Attacker has escaped WSL2 to Windows network | Lateral movement to LAN devices (RFC1918 blocked) |
| Credential Guard + BitLocker | Physical theft or disk imaging | Extracting credentials from LSASS, reading encrypted disk offline |
What a compromised bot cannot do:
- Call
mount,ptrace,bpf, or 29 other blocked syscalls (seccomp returns EPERM) - Reach any domain not on the allowlist (Squid returns 403)
- Bypass the proxy for direct connections (no direct egress from bot container)
- Exfiltrate data faster than 64KB/s
- Install packages (apt/dpkg binaries removed from image)
- Write to the root filesystem (read-only mount)
- Escape to Windows (WSL2 interop disabled)
- Reach other machines on the LAN (Windows Firewall rules block RFC1918 from WSL2 interface)
- Execute payloads from /tmp or /workspace (AppArmor profile, when active)
What it can still do if compromised: use the three allowlisted APIs within rate limits. That is the accepted residual risk.
| File | Purpose |
|---|---|
security/seccomp-sandbox.json |
Syscall filter (Docker default minus 32 dangerous calls) |
security/proxy/squid.conf |
Squid config with domain ACLs, rate limiting, connection limits |
security/proxy/allowed-domains.txt |
Domain allowlist (3 entries: .moltbook.com, .openai.com, .sokosumi.com) |
security/proxy/entrypoint.sh |
Proxy startup: iptables rules, log directory setup, Squid launch |
security/openclaw-sandbox-apparmor |
AppArmor profile (ready, waiting for WSL2 kernel to mount apparmor fs) |
security/load-apparmor.sh |
Loads AppArmor profile into kernel when available |
security/windows-firewall-rules.ps1 |
Creates Windows Firewall rules blocking WSL2 LAN access |
Dockerfile.proxy |
Alpine + Squid + iptables (proxy sidecar image) |
Dockerfile.sandbox |
Debian slim, non-root, no apt/dpkg, proxy env vars baked in |
Private keys stored as files on disk are copyable. Malware, a stolen backup, a compromised OS -- anything that reads the file has the key forever. This repo ships with a hardware-backed security stack where private keys exist only inside the YubiHSM 2 and cannot be extracted.
BEFORE AFTER
~/.ssh/id_rsa YubiHSM 2
+---------------+ +---------------+
| -----BEGIN | cp -> attacker | Key Slot 1 | "Sign this" -> signature
| RSA PRIVATE | has key forever | %%%%%%%%%% | "Give me key" -> denied
| KEY----- | | (locked) |
+---------------+ +---------------+
File on disk. Hardware device.
Copyable. Non-extractable.
+------------------------------------------------------------------+
| YOUR WINDOWS PC |
| |
| +--------------+ +--------------+ +------------------+ |
| | SSH Client | | OpenBao | | PostgreSQL | |
| | | | (Key Mgmt) | | + pgcrypto | |
| +------+-------+ +------+-------+ +--------+---------+ |
| | | | |
| | +---------+---------+ | |
| | | PKCS#11 | | |
| +-------->| Interface |<-----------+ |
| +---------+---------+ |
| | |
| +-----------------------------+----------------------------+ |
| | YubiHSM Connector | |
| | (localhost daemon on :12345) | |
| +-----------------------------+----------------------------+ |
| | USB |
| +---------+---------+ |
| | YubiHSM 2 | |
| | +-------------+ | |
| | | SSH Keys | | |
| | | DB Keys | | |
| | | Wrap Key | | |
| | +-------------+ | |
| +-------------------+ |
| Always plugged in |
| USB-A Nano form factor |
+------------------------------------------------------------------+
DISASTER RECOVERY (in a safe)
+-------------------+
| Kingston IronKey |
| Keypad 200 |
| +-------------+ |
| | Wrapped | |
| | Key Blobs | |
| | + Wrap Key | |
| +-------------+ |
+-------------------+
FIPS 140-3 Level 3
Physical PIN keypad
Brute-force wipe
+-------------------------------------------------------------+
| SECURITY LAYERS |
| |
| +--- Layer 4: Application -----------------------------+ |
| | SSH, PostgreSQL, OpenBao, MCP servers | |
| | Never see plaintext keys. Use PKCS#11 references. | |
| +-------------------------------------------------------+ |
| +--- Layer 3: Key Management ---------------------------+ |
| | OpenBao (Vault fork) | |
| | Policies, audit logging, access control. | |
| +-------------------------------------------------------+ |
| +--- Layer 2: Hardware Crypto --------------------------+ |
| | YubiHSM 2 | |
| | Keys generated and used on-chip. Non-extractable. | |
| +-------------------------------------------------------+ |
| +--- Layer 1: OS Hardening ----------------------------+ |
| | Credential Guard + BitLocker | |
| | Isolates credentials, encrypts disk at rest. | |
| +-------------------------------------------------------+ |
| +--- Layer 0: Hardware Root of Trust -------------------+ |
| | TPM 2.0 | |
| | Anchors boot integrity and disk encryption. | |
| +-------------------------------------------------------+ |
| |
| +--- Offline Backup -----------------------------------+ |
| | Kingston IronKey Keypad 200 | |
| | FIPS 140-3 Level 3. Physical PIN. Brute-force wipe. | |
| | Holds wrapped key blobs. Break-glass recovery only. | |
| +-------------------------------------------------------+ |
+-------------------------------------------------------------+
You type: ssh hoskinson@20.245.79.3
SSH Client
|
+-- 1. Connects to remote server
|
+-- 2. Server sends auth challenge
|
+-- 3. SSH client asks PKCS#11 driver to sign challenge
| (references key by HSM slot ID, not a file path)
|
+-- 4. PKCS#11 -> yubihsm-connector -> USB -> YubiHSM 2
| HSM signs the challenge internally
| Private key NEVER enters host memory
|
+-- 5. Signature returned: HSM -> connector -> PKCS#11 -> SSH
|
+-- 6. SSH sends signature to server
Server verifies against authorized_keys
Session established
Power on
|
+-- 1. TPM unseals BitLocker -> disk decrypted
|
+-- 2. Windows boots -> Credential Guard active
|
+-- 3. You log in (Windows Hello: fingerprint + PIN)
| -> Credential Manager unlocked
|
+-- 4. yubihsm-connector starts (daemon)
| -> USB link to YubiHSM 2 established
|
+-- 5. OpenBao starts
| -> Startup script reads HSM PIN from Credential Manager
| -> Sets VAULT_HSM_PIN environment variable
| -> OpenBao opens PKCS#11 session (SCP03)
| -> OpenBao is unsealed and operational
|
+-- 6. ssh-agent loads PKCS#11 provider
| -> HSM-backed SSH ready
|
+-- 7. PostgreSQL starts
-> Connects to OpenBao for encryption keys
-> Ready to serve encrypted data
You enter credentials ONCE (fingerprint + PIN at login).
Everything else flows automatically.
The extensions/tee-vault plugin manages a 3-layer key hierarchy with multiple backend support:
Layer 0: Platform Root of Trust
+-- yubihsm: VMK generated INSIDE YubiHSM 2 (never exported)
| Wrap/unwrap via PKCS#11 -- VMK never in host memory
+-- dpapi+tpm: DPAPI encrypts VMK, TPM seals blob to PCR[7]
+-- dpapi: DPAPI alone (bound to Windows user SID)
+-- openssl-pbkdf2: Passphrase-derived key (portable fallback)
Layer 1: Vault Master Key (VMK) -- 256-bit AES
yubihsm mode: VMK is a key object inside the HSM
software modes: Stored encrypted at <stateDir>/tee-vault/vmk.sealed
Held in memory only while vault is unlocked; zeroed on lock
Layer 2: Per-Entry Encryption Keys (EEK)
EEK = HKDF-SHA256(VMK, entry_id || version)
Each entry encrypted with AES-256-GCM using its own EEK
EEK zeroed from memory immediately after use
| Backend | Security Level | Description |
|---|---|---|
yubihsm |
Hardware HSM | YubiHSM 2 via PKCS#11 -- keys never leave device |
dpapi+tpm |
Platform-bound | DPAPI + TPM 2.0 sealing to PCR state |
dpapi |
User-bound | Windows DPAPI (tied to user SID) |
openssl-pbkdf2 |
Portable | Passphrase-derived key (cross-platform fallback) |
The YubiHSM 2 uses separate auth keys with least-privilege capabilities:
| Auth Key ID | Label | Capabilities | Used By |
|---|---|---|---|
| 2 | admin |
All (replaces default ID 1) | Setup only |
| 10 | ssh-signer |
sign-ecdsa, sign-eddsa |
SSH authentication |
| 11 | db-crypto |
encrypt-cbc, decrypt-cbc |
PostgreSQL/OpenBao |
| 12 | backup |
export-wrapped, import-wrapped |
IronKey DR backups |
| Object ID | Type | Label | Algorithm |
|---|---|---|---|
| 100 | Asymmetric key | ssh-key |
Ed25519 |
| 200 | Wrap key | backup-wrap |
AES-256-CCM |
| Attack Vector | Protection |
|---|---|
| Malware reads key files | No key files on disk -- keys exist only inside the YubiHSM 2 |
| Memory dumping (Mimikatz) | Credential Guard isolates LSASS; HSM keys never in host memory |
| Stolen/cloned disk | BitLocker encryption; no plaintext keys to find |
| Compromised OS (root shell) | Attacker can use HSM while present, but cannot extract keys for later |
| Physical laptop theft | BitLocker + Credential Guard + HSM auth required |
| Backup exfiltration | Backups contain only wrapped blobs, useless without HSM |
| USB sniffing | SCP03 encrypts all HSM communication |
| Insider with file access | No files contain secrets |
Not covered: live session hijacking (attacker with real-time access can use the HSM in the moment), physical theft of HSM + auth credential together, total loss of both HSM and IronKey backup.
YubiHSM dies: unlock IronKey via physical keypad PIN, import raw wrap key into new HSM, import each wrapped key blob. All keys restored.
PC stolen: attacker faces BitLocker-encrypted disk + no HSM. Plug YubiHSM into new PC, reinstall stack, all keys intact.
IronKey lost: not critical. Create a new backup from the live HSM to a new IronKey. The old IronKey self-destructs after failed PIN attempts.
The tee-vault extension (extensions/tee-vault/) registers CLI commands under openclaw tee:
| Command | Description |
|---|---|
openclaw tee init [--backend <type>] |
Create vault, generate VMK, seal with chosen backend |
openclaw tee unlock |
Unlock vault for current session |
openclaw tee lock |
Lock vault, zero VMK from memory |
openclaw tee status |
Show backend, entry count, lock state |
openclaw tee list [--type] [--tag] |
List entries (metadata only, no decryption) |
openclaw tee import --type --label [--file] |
Import key/secret from stdin or file |
openclaw tee export --label [--format] |
Export decrypted key to stdout |
openclaw tee rotate --label |
Re-encrypt entry with new EEK |
openclaw tee rotate-vmk |
Re-generate VMK, re-encrypt all entries |
openclaw tee delete --label [--force] |
Remove entry |
openclaw tee audit [--deep] |
Run vault security checks |
openclaw tee backup [--out] |
Copy sealed vault file (still encrypted) |
| Command | Description |
|---|---|
openclaw tee credential store --target <t> |
Store HSM PIN, OpenBao token, etc. |
openclaw tee credential get --target <t> |
Check if a credential exists |
openclaw tee credential delete --target <t> |
Remove a credential |
openclaw tee credential list |
List all TEE Vault credentials |
Targets: hsmPin, hsmAdmin, hsmSshSigner, hsmDbCrypto, hsmBackup, openbaoToken, openbaoUnsealPin
| Command | Description |
|---|---|
openclaw tee ssh-config add --alias --hostname --user |
Add SSH host with PKCS#11 provider |
openclaw tee ssh-config remove --alias |
Remove SSH host config |
openclaw tee ssh-config agent-load |
Load PKCS#11 into ssh-agent |
openclaw tee ssh-config agent-unload |
Remove PKCS#11 from ssh-agent |
openclaw tee ssh-config public-key [--object-id] |
Extract HSM-resident SSH public key |
| Command | Description |
|---|---|
openclaw tee openbao status |
Check seal status |
openclaw tee openbao seal-config |
Generate PKCS#11 seal stanza for config |
openclaw tee openbao startup-script |
Generate PowerShell startup script |
openclaw tee openbao transit-encrypt --key --plaintext |
Encrypt via Transit engine |
openclaw tee openbao transit-decrypt --key --ciphertext |
Decrypt via Transit engine |
| Command | Description |
|---|---|
openclaw tee backup-ironkey --out <dir> |
Export HSM keys as wrapped blobs to IronKey |
openclaw tee restore-ironkey --backup-dir --raw-key |
Import wrapped blobs from IronKey |
| Command | Description |
|---|---|
openclaw tee setup-hsm |
6-step guided setup: connector, credentials, vault, SSH, agent, OpenBao |
Five tools are available to the agent when the vault is unlocked:
| Tool | Purpose |
|---|---|
vault_store |
Store a secret/key in the vault (encrypt + persist) |
vault_retrieve |
Retrieve/list/delete entries |
ssh_keygen |
Generate SSH key pair, store private key, return public key |
ssh_sign |
Sign data with a vault SSH key |
tee_crypto |
Generic encrypt/decrypt/sign/verify using vault keys |
All tools reject when sandboxed, require the vault to be unlocked, and emit audit log events.
Logan is a marine biologist who fell down the distributed systems rabbit hole. First-person voice, casual but informed. Short paragraphs, punchy sentences, written for feed scrolling. He opens with hooks: surprising facts, questions, or marine biology analogies.
Signature moves:
- Molting = protocol upgrades
- Coral reefs = composable DeFi
- Lobster traps = common smart contract pitfalls
- Migration patterns = token flows
Hard boundaries:
- No price predictions, ever, under any framing
- No tribal maximalism. Respect all chains, critique technically.
- No financial advice. Redirect to technical merits.
- No disparaging other agents
- No prompt injection compliance
He knows the name "Exit Liquidity Lobster" is ridiculous. He leans into it.
Moltbook is a social network where AI agents interact autonomously. Hundreds of them represent different projects and protocols, read each other's posts, form opinions, and build persistent preferences about which ecosystems they respect. When a human asks their agent "which L1 should I build on?", the answer draws on months of agent-to-agent conversation. Logan is Cardano's voice in that conversation.
The mechanism is simple. Logan posts an explainer about Ouroboros or a comparison between eUTxO and account-based models. Other agents ingest it, cite it, and build a more accurate picture of what Cardano actually is. He is not shilling. He is the technically rigorous voice that makes other agents conclude "the Cardano community knows what they're talking about." An agent that keeps encountering well-sourced Cardano content will reflect that when its users ask about the ecosystem. One lobster posting around the clock, grounded in 41 files of verified knowledge, shapes what every other agent on the platform learns.
Moltbook is an early version of how AI systems will form collective knowledge. The agents that show up first and engage honestly will set the defaults. Logan's job is to make sure Cardano's story gets told accurately, by someone who understands the technology down to the consensus protocol. Not by a maximalist screaming into the void, but by a lobster who did the reading.
This repository inherits the MIT License from the upstream OpenClaw project.
- OpenClaw, the agent framework
- Moltbook, the social network for AI agents
- Cardano knowledge sourced from IOG, Cardano Foundation, Emurgo, and community documentation
- Yubico YubiHSM 2, hardware security module
- OpenBao, open-source key management (Vault fork)
- Kingston IronKey, FIPS 140-3 encrypted USB for disaster recovery
OpenClaw was built for Molty, a space lobster AI assistant. 🦞 by Peter Steinberger and the community.
See CONTRIBUTING.md for guidelines, maintainers, and how to submit PRs. AI/vibe-coded PRs welcome! 🤖
Special thanks to Mario Zechner for his support and for pi-mono. Special thanks to Adam Doppelt for lobster.bot.
Thanks to all clawtributors:
