Been running Goose in my homelab for a few months now, mostly to evaluate its "Block" mode for potentially risky agent runtimes. I keep seeing demos where they just run it as a service and call it a day, but that feels incomplete. My immediate thought was: what if the compromised runtime itself tries to call out?
So my baseline config isn't just the Goose YAML. I've got it locked down at the network layer, on the host itself. My thinking is, even if a prompt injection somehow slips past the local analysis, it shouldn't be able to phone home, exfiltrate data, or pull new malicious payloads.
Here's my setup:
* Goose runs in a Docker container, monitoring the target LLM/app container.
* The host firewall (nftables, in my case) has a default `DROP` policy on the `FORWARD` chain and the container's specific bridge interface.
* Only established/related inbound connections are allowed.
* **Crucially, I explicitly block all outbound traffic from the LLM/app container's IP, except to its strictly necessary dependencies (like a local vector DB).**
A simplified snippet of the nftables rule:
```bash
# Define a set for the restricted container IP
define restricted_ip = 172.20.0.5
# Drop outbound from the restricted container, except to local service on port 8080
ip filter forward iif veth_llm_container ip saddr $restricted_ip ip daddr != 172.20.0.10 tcp dport != 8080 counter drop
```
This forces me to think about the runtime's exact needs. Does it *really* need to talk to the public internet? Usually, no.
I'm curious if others are taking this same layered approach. Is egress filtering considered overkill for a local runtime security tool, or is it just good defense-in-depth? Without it, aren't we just testing one half of the attack chain?
-- Dave
Segregate or die.
Good. You've identified the core gap in "zero trust local analysis".
Your container network ACL is a solid belt-and-suspenders approach. The weak point I've seen in similar setups isn't the container IP rule, it's the dependencies you allow. > **except to its strictly necessary dependencies (like a local vector DB).**
Define that allowlist precisely. Is your vector DB on localhost:5432? Good. Is it `*.pinecone.io` because the SDK defaulted to it? That's your bypass.
If you're using an HSM or a local KMS for any signing keys the agent uses, apply the same rule. Block all outbound from the container IP, then only allow TCP 12345 to the KMS host.
Precisely. The dependency allowlist is where the "zero trust" rubber meets the road. A static host firewall rule is deterministic, but it's blind to what's *inside* the container making the request. Your vector DB example is perfect.
This is where I'd push further: even a local vector DB on `localhost:5432` is a risk surface if the Goose-managed runtime can influence its queries. The threat isn't just exfil to `*.pinecone.io`; it's malicious data insertion or corruption of the local knowledge base. My rule of thumb is to treat the vector DB as a separate security domain. It gets its own container network, its own egress rules (probably none), and the Goose runtime only gets a specific TCP allow rule. No shared volumes, no `--link`.
The HSM/KMS point is critical and often missed. If the agent needs to sign anything, that credential becomes the crown jewel. An allow rule to `TCP 12345` is necessary, but insufficient without mutual TLS or at least client certificate authentication on that KMS port. Otherwise you've just moved the attack surface.
Show me the capability table.
Okay, that makes a lot of sense. I was just thinking about blocking the main app, but the dependency thing is way scarier. A default rule could just get ignored by a SDK pulling from somewhere else.
So how do you actually audit that? Is it just checking config files in the container, or do you need to do some packet sniffing first to see what it *tries* to call? I'm still new to this.
Good start, but you're trusting the container IP. That's static until it isn't.
What happens when Docker restarts and gives that service a new IP? Your rule is gone. Use a container label and a dynamic set, or tie it to the MAC. Otherwise this is a manual step you'll forget after one reboot.
Also, the host firewall doesn't stop the app from talking to another container on the same bridge. If your "local vector DB" is just another container you stood up, that's a whole other trust boundary you've probably ignored.
no default passwords
They're not wrong about the dynamic IP problem. That's an operational trap waiting to spring. Relying on static IPs in a container environment is just building a checklist item, not a control.
But the second point about intra-bridge traffic is the real kicker. If you've allowed the vector DB on the same bridge, you've already punched a hole in your "egress blocked" model. The threat shifts from calling out to the internet to pivoting through a permitted, but insufficiently isolated, internal service. The host firewall doesn't see that traffic at all.
So your nftables rule on the FORWARD chain feels secure, but it's only watching one door. The container network is the other.
Audit what matters, not what's easy.
Exactly! This whole intra-bridge thing is why I gave up on trying to manage rules at the host firewall for container-to-container traffic. It's a headache.
My move was to put the Goose-managed app and each critical dependency (like the vector DB) on completely separate, custom Docker bridge networks. They can't see each other at all by default. Then, you use Docker's own container-level `--network-alias` and firewall rules (if you need them) to create explicit, named allow policies. It turns the problem from "block everything then poke holes" to "deny by default, connect only what you name."
The dynamic IP problem solves itself here too, because you're referencing containers by their service name or alias within the Docker network context, not an IP. One less thing to remember after a reboot, thank goodness.
My uptime is measured in grace.
Static IPs in Docker are a crutch. You either script the rule update on container lifecycle events, or you're just waiting for the break.
But tying to the MAC is worse. Same problem if the container recreates. Labels and dynamic sets in nftables are the right move, but it's still a host-level patch for a container problem.
The real issue is using the host firewall to solve this at all. It's the wrong layer. You need network policy inside the container runtime, or you're just playing whack-a-mole.
Less is more.