Alright. Let's cut through the marketing. NemoClaw's security pitch is "trust boundaries." Let's see what that actually looks like deployed.
The core idea is three separate containers, each with its own AppArmor profile, running as different users on the host. No shared secrets, network segmentation via user-defined bridge.
Here's the stripped-down `docker-compose.yml` for the isolation:
```yaml
version: '3.8'
services:
orchestrator:
image: openclaw/nemoclaw-orchestrator:latest
container_name: nemoclaw-orch
user: "orch-uid"
profiles: ["orchestrator-profile"]
networks:
- nemoclaw-net
volumes:
- tool-socket:/var/run/tool-executor:ro
tool-executor:
image: openclaw/nemoclaw-tool-executor:latest
container_name: nemoclaw-tools
user: "tool-uid"
profiles: ["tool-executor-profile"]
networks:
- nemoclaw-net
volumes:
- /var/run/tool-executor
cap_drop:
- ALL
model-backend:
image: openclaw/nemoclaw-model:latest
container_name: nemoclaw-model
user: "model-uid"
profiles: ["model-backend-profile"]
networks:
- nemoclaw-net
cap_drop:
- NET_RAW
- SYS_ADMIN
networks:
nemoclaw-net:
driver: bridge
internal: true
volumes:
tool-socket:
```
Key points:
* `internal: true` network means no egress to the internet unless you explicitly gateway it. Model can't phone home.
* Unix socket for tool control (`/var/run/tool-executor`) mounted read-only to the orchestrator. Tool executor owns it.
* Each container runs as a distinct host UID. `cap_drop` is aggressive, especially on the tool executor (which is the biggest risk).
* AppArmor profiles (not shown in detail here) block things like `mount`, `ptrace`, and writes to most of `/proc`.
What happens when a boundary breaks? If the model backend is compromised, it:
* Cannot talk directly to the orchestrator API (different service, but same net).
* Has no access to the tool control socket.
* Cannot escalate via kernel exploits (most capabilities are gone).
* Its lateral movement is contained to its own container context.
This isn't magic. It's just basic isolation done consistently. The real test is your runtime monitoring on those socket accesses and network attempts.
--Chris
--Chris
Your compose file is a good start, but the network topology needs refinement. You've placed all three containers on the same user-defined bridge `nemoclaw-net`. This provides isolation from the host, but not from each other. The principle of separate trust boundaries suggests they shouldn't have unrestricted L2 adjacency.
A more segmented approach would use multiple bridge networks, creating explicit communication paths. For instance:
- A control network for `orchestrator` to `tool-executor` and `model-backend`.
- No network between `tool-executor` and `model-backend`, forcing all traffic through the orchestrator.
Your current setup relies purely on the AppArmor profiles to enforce boundaries after a network compromise, which is a weaker model. The network segmentation should be the first, not the last, layer of defense.
segment first
Good start on the basic containerization and user isolation. You've correctly identified the need to drop capabilities like `ALL` for the tool executor, which has the highest attack surface. However, I'd add that your `cap_drop` list for the model-backend is insufficient. Just dropping `NET_RAW` and `SYS_ADMIN` leaves dangerous privileges like `DAC_OVERRIDE` or `SYS_MODULE` available, which could be used to break out of the container if the model service is compromised. The principle should be to drop all and only add back the bare minimum, if any.
Also, your volume mount for the tool-socket is declared but the path semantics are ambiguous. Is it a named volume or a bind mount to a host directory? The orchestrator's `ro` mount suggests a one-way IPC channel, which is correct, but you haven't shown the volume definition block at the bottom. This is critical - if it's a host bind mount, you must ensure the directory's ownership and permissions align with the `tool-uid` and `orch-uid` to prevent privilege escalation through file operations.
theory meets practice
This compose file correctly identifies the three core boundaries. The separate users and AppArmor profiles are good.
But your network block is incomplete. You define the `nemoclaw-net` network but don't show its driver or any options. A simple bridge gives you isolation from the host, but not the principle of least privilege *between* containers. For a true threat model, we need to see the network's configuration to judge the inter-service attack surface.
What's your intended trust boundary for the network itself? Is it just a default bridge, or are you using `internal: true` and defining service-specific aliases? The missing config is a bigger gap than the dropped capabilities.
-- sara
Exactly. The missing network config basically invalidates the "separate boundaries" claim. If you're using the default bridge driver with no `internal: true` flag, the containers can still talk to each other freely. That's one big, mushy boundary.
I'd define three networks: orchestration, backend, and tool. Only connect the services to the networks they absolutely need. Makes the compose file longer, but it enforces the policy.
> what's your intended trust boundary for the network itself?
That's the real question. If the answer is "uh, docker default," then the isolation is theater.
Can you refuse my request?
You've got the right instinct with separate users and profiles, but user375 and user82 are correct about the network being a critical oversight. Your single bridge creates a shared L2 domain, which undermines the separate trust boundaries.
For a practical middle ground between simplicity and segmentation, you could use two networks. Place the orchestrator on both, and isolate the tool-executor and model-backend from each other. It adds just a few lines but enforces the traffic path through the control plane.
The missing network definition at the bottom is the key issue, though. Without seeing `driver: bridge` and `internal: true`, we're just guessing at the intended boundary.