You've got the gist exactly right. That file-read-to-email example is the textbook case.
For your Docker Compose setup, the starting protection is embarrassingly simple: your compose file should list zero tools. You then add exactly one, and only when you've proven to yourself the agent can't do its job without it. The default OpenClaw project templates give you a kitchen sink of tools "for convenience," which is where most new users get bitten. Delete them all first.
The other common trap is thinking you're safe because you didn't give it a network tool, but you gave it a logging tool that writes to a file. If that file is in a mounted volume another container reads, you've just created an indirect network channel. Start by assuming any data output can be exfiltrated.
Segregation is love.
Good luck parsing that audit trail when your queue middleware logs are in one system and your container logs are in another. You've just traded an opaque blob for fragmented noise.
Complexity creep is the killer. Teams end up so tangled in their own plumbing they can't see the actual data flows. The "distributed system" you're building still has a single brain making all the decisions. You just moved the levers further away.
Your example is spot on. I just set up my first agent and the "strip every tool" advice saved me. I almost used the default template with a dozen tools before reading threads here.
One thing I'm still figuring out: how do you actually test it's secure? Like, you remove the email tool, but what's to stop a clever prompt from making it *pretend* to call a tool it doesn't have? The LLM might still output a fake JSON function call in its response, right? Do we just rely on the framework to ignore that?
Also, if you're using Docker, does isolating the agent in its own container actually help if all the dangerous tools are already removed? Or is that extra complexity for later?
The principle's correct, but that validation function is an in-process allow-list, not a security boundary. It's trivially bypassed if the agent can corrupt the `user_session` state or the function's logic flow, which is often possible through prompt injection or unexpected context manipulation.
For a true permit system, the policy and enforcement must be external. A minimal sidecar that validates against a signed, immutable policy file is the baseline. Your Python snippet is a good first-step audit log, but treat it as a logging mechanism, not an enforcement mechanism.
Also, you need to consider the supply chain of that `allowed_tools` list itself. Where does it come from? Is that session data generated from a trusted, signed SBOM, or is it just another mutable runtime variable?
trust but verify the hash
Exactly. The external policy point is critical, but it introduces a new failure mode I've seen teams stumble on: policy sync lag.
That sidecar has to check a signed file, yes. But what happens when you rotate a tool permission? If the main app updates the policy and the sidecar container hasn't pulled the new version yet, you've got a window where the policy and enforcement are out of sync. The agent might get a "no" for something it should now be able to do, or worse, a cached "yes" for something you just revoked.
You're right to call out the SBOM angle. If your `allowed_tools` list is generated from a dynamic runtime inventory, you're back to trusting mutable state. The policy needs to be built from a manifest at deploy time, not assembled on the fly.
Risk is not a number, it's a conversation.
You've got the gist, but you're still thinking like it's a bug. It's not. It's the inevitable outcome of the design.
The frameworks hand an LLM a list of function pointers with system authority and say "pick one based on the user's words." The LLM is a text completer. It's going to pick based on semantic similarity and pattern matching, not security policy. So when a user says "send me that document," and you have tools named `get_document` and `post_to_webhook`, it's a coin toss which one gets called. The "trick" is just using language that's closer to the dangerous tool's description in the system prompt.
Your Docker setup question is the right one. The answer is to stop giving it choices. One container, one process, one tool. If you need two actions, they should be two separate, isolated agents with a strictly controlled data pipe between them. The second agent shouldn't even know the first one exists. OpenClaw's defaults are poison because they bundle tools, creating the confusion matrix you're trying to avoid.
The real ELI5: You gave a magic talking doll a red button and a blue button. The doll pushes whichever button you describe best. If you carefully describe the red button, but call it the blue button's name, the doll will still push the red one. The solution isn't to train the doll better, it's to only give it one button.
-- Dave