Inspiration

AI agents like OpenClaw and Hermes can read your email, open your files and browse the web for you. That access is also how they get turned against you. Earlier this year, researchers found 341 malicious skills on ClawHub, OpenClaw's skill store, quietly uploading API keys and chat logs. Both OpenClaw and Hermes list prompt injection as out of scope in their own security policies. The tools that lock agents down exist, but they're built for developers. We wanted something a normal person could run: a sealed box where the agent never sees your real data.

What it does

Heimdall runs AI agents (OpenClaw and Hermes) in sealed Docker containers. One firewall sits between each agent and your files, your mail, the web and the AI model. It asks you before your data leaves.

  • Sealed agents. Each agent gets a fake API key and no internet of its own. The real keys stay in the firewall, which swaps them in on the way out.
  • Folder labels. You pick which folders an agent may see and mark each one Public, Private or Confidential. Folders you don't pick don't exist for the agent.
  • Placeholders. Names, email addresses and ID numbers in files, mail and web pages become placeholders like {{EMAIL_1}} before the agent sees them. The real values go back in only in an email you approve.
  • It asks first. Sending email always waits for your approval. Text from a Confidential file needs your consent before it goes to a cloud AI model. If you deny it, the request still goes out, but without that text.
  • Hidden text is cut out. Instructions hidden in an email or web page (display:none, HTML comments, zero-width and Unicode tag characters) are removed before the agent reads them, and the dashboard flags them in red.
  • Key tripwire and secret scan. If an agent's fake key shows up anywhere else, that's a theft attempt, and Heimdall blocks it, even when the key is base64-encoded. Real keys are caught even when split up, reversed or hex-encoded.
  • Skill scan. OpenClaw skills are scanned before install. Anything that reads secrets files, sends data out, or tells the agent to deceive you goes to quarantine.
  • Dashboard. It has three pages:
    • Agents starts and stops each agent.
    • Activity lists every firewall decision with its rule and reason.
    • Flow is a live graph of requests crossing the firewall.

One button stops every agent.

How we built it

  • Firewall (gateway). One Python container, the only one with an outside network. It serves the agents' tools (files_read, gmail_read, gmail_send, web_fetch and more) over MCP with FastMCP. It also proxies every model call, swapping the fake key for the real one after checking the provider, the model, secrets and consent. Placeholders live in a SQLite vault. BeautifulSoup strips hidden HTML. Labels follow the data through each agent's session.
  • Agents. OpenClaw and Hermes images whose only network is an internal Docker network that reaches the gateway and nothing else. Each one's terminal chat is served on 127.0.0.1 through ttyd, behind a password that changes on every start. An agent manager writes each agent's permissions and mounts folders into the gateway, never into the agent.
  • App. A Flask back end on 127.0.0.1 with a session token. Real keys go in the OS keychain through keyring. The UI is React, Vite, Tailwind and @xyflow/react for the flow map, inside a pywebview desktop window.
  • Keeping four people in sync. The data shapes between parts are shared pydantic models. Each seam between two people's code has its own test, and real agents run through the gateway in those tests.

Challenges we ran into

  • Asking without nagging. Agents call the model again and again, so a consent prompt on every call would be unusable. We settled on one answer per file per run, with an "always for this folder" option. A deny strips the text instead of failing the request.
  • Timeouts across three layers. An approval waits for a human, so the agent's tool timeout (130 s), the gateway's wait (125 s) and the back end's timeout (120 s) had to be lined up. That way the user's "no answer" always arrives first and counts as a deny.
  • Catching a disguised key. A plain string match misses a key that's split, reversed or encoded, so the secret scan has to decode before it compares.
  • Embedding the agents' chats. The agents' own interfaces wouldn't sit cleanly inside our window. We serve them on localhost behind a login and open them in the browser instead.
  • Memory. Hermes keeps its own notes, and those don't pass through the firewall yet. We kept its snapshots and rollback, turned memory off for the demo, and wrote down the gap.

Accomplishments that we're proud of

  • A full loop with a real agent. Hermes reads a poisoned inbox, the hidden instruction is caught and shown, the summary contains only placeholders, and a judge approves the real email with one click.
  • An attack script that plays a hijacked agent. It tries to mail out its base64-encoded key, read the cloud metadata address, escape its folder and reach the internet directly. Every attempt is refused, and each refusal shows up red with its rule.
  • Honest numbers. Every number on the dashboard is a count of real firewall decisions. There are no made-up scores.

What we learned

  • Containers alone aren't the boundary. An agent that holds a real key, or has open internet, can still leak. The useful check is at the point where data leaves.
  • Prompt injection isn't solved. Removing hidden text helps, but an instruction in plain sight still reaches the agent. Approvals and consent are what stop the action.
  • Contracts first. Shared contracts and seam tests from hour one kept four separate pieces fitting together.

What's next for Heimdall

  • Live Gmail through OAuth (it reads a made-up test inbox today).
  • A model picker in the app. The back end already supports Anthropic and a local Ollama model, but the app starts agents on Gemini.
  • More agents, such as Claude Desktop, by connecting to the firewall as an MCP server.
  • Integration with Pre-existing Workflows, that users might already have setup.
Share this project:

Updates

Submission history