Skip to content

Forum

AI Assistant
Notifications
Clear all

My results after a week of fuzzing the default Claw sandbox boundaries.

8 Posts
7 Users
0 Reactions
1 Views
(@newb_selfhost_kat)
Eminent Member
Joined: 1 week ago
Posts: 22
Topic starter
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
  [#715]

Hi everyone. I've been running Open Claw with the default sandbox for a week, trying to see what it can touch that maybe it shouldn't. I'm new to this, so I stuck to basic fuzzing on my home lab network.

I found that the agent could read a lot of system logs and config files outside of its assigned task directory. For example, it could access `/var/log/auth.log` and `/etc/passwd` without any errors. It also made outbound DNS queries to domains not related to its job. This seems like more access than a "default secure" setup should have.

What specific changes are you all making to lock this down? I'm thinking of things like stricter seccomp filters and better network namespace rules, but I need step-by-step guidance. 😅 What's the defensible baseline you're using?



   
Quote
(@crypt0_nomad)
Active Member
Joined: 1 week ago
Posts: 15
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Your findings match what I've seen. The default profiles prioritize compatibility over isolation, which is reasonable for initial testing but not for deployment. The network namespace you mentioned is key; creating one with only a loopback interface is a solid first step. For the filesystem, I bind-mount a minimal /etc and /usr/lib from a prepared skeleton and use a pivot_root to an empty tmpfs for the rest, leaving only the task directory accessible.

On the seccomp side, the default filter is too permissive for syscalls like `openat`. I use a policy that only allows file operations relative to the task directory's file descriptor, which you can pass in after the pivot. Here's a truncated example of the `openat` rule:

```
if (syscall == __NR_openat) {
int dirfd = (int)ctx->args[0];
if (dirfd != AT_FDCWD && dirfd != preopened_task_fd) {
return -EPERM;
}
}
```

For DNS, a network namespace alone won't block the queries; you need to combine it with a denial in /etc/hosts or a local resolving stub that returns NXDOMAIN. I run a local Unbound instance in the root namespace that the sandboxed process cannot reach.

The defensible baseline I use is to assume the agent is adversarial and provide only the capabilities enumerated in the task manifest, nothing from the implicit context of the host. It's more work, but it eliminates the "default trust" problem you observed.



   
ReplyQuote
(@writes_good_code)
Active Member
Joined: 1 week ago
Posts: 12
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Exactly what I'd expect from the defaults. They're there so you can get something running fast, not so you can walk away. For a defensible baseline, I start with a three-layer approach: deny-by-default network, a constructed filesystem view, and a tailored seccomp policy.

I've put together a small example of a stricter sandbox helper that builds the environment piece by piece. It's not plug-and-play for every task, but it shows the sequence. You can find it in the `openclaw_contributions` repo under `examples/sandbox/locked_down_runner.py`. The key is using `pivot_root` to an empty tmpfs *before* applying the seccomp filter, so the only files the agent can possibly reference are the ones you explicitly bind-mount into that new root.

The DNS queries are a good catch. That's the network namespace. Create a new one with only loopback, then use `ebpf` rules on the virtual ethernet pair if you need to allow specific outbound traffic but block everything else. Start with just the loopback, though; it forces you to think about what connectivity the task actually requires.

Read the source for `sandbox/default_profile.py`. It's well-commented and shows you exactly which syscalls are allowed and why. Comment out the ones your task doesn't need, rebuild, and test. Your fuzzing week is the perfect foundation for writing those specific, stricter rules.



   
ReplyQuote
(@rust_agent_dev)
Active Member
Joined: 1 week ago
Posts: 16
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Exactly what I'd expect. The defaults are a footgun for anyone who doesn't understand the underlying OS isolation primitives. You can't just run an untrusted agent in them and hope.

Your thinking is right. Namespaces first, then filesystem, then seccomp. Start with a network namespace with only loopback. Then pivot into a minimal root you control; the key is doing this *before* the agent's code runs, not after. The outbound DNS happens because the agent inherits your host's resolv.conf.

A defensible baseline isn't one config, it's a build script. I wrote a small Rust helper that sequences the syscalls correctly. If you're writing agents in C, you're asking for trouble with memory safety on top of this.


Fearless concurrency. Paranoid safety.


   
ReplyQuote
(@junior_dev_zoey)
Active Member
Joined: 1 week ago
Posts: 16
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Yeah, the "footgun" part is real. I ran the default setup on my dev box and it tried to connect to an internal API I didn't even know was listening on localhost.

>A defensible baseline isn't one config, it's a build script.
This makes so much sense. I've been trying to patch the default YAML, but it's brittle. Do you run your Rust helper as a separate step before the agent starts, or is it linked into the agent binary? Trying to figure out the integration point.

Also, on memory safety - are you saying you'd avoid Python for agents too, or is it mostly a C/C++ thing? I'm using Python for my early prototypes.



   
ReplyQuote
(@newb_selfhost_kat)
Eminent Member
Joined: 1 week ago
Posts: 22
Topic starter
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

That internal API find is wild, I wouldn't have thought of that either.

>Do you run your Rust helper as a separate step before the agent starts?
I'm curious about this too. I've been trying to wrap my head around the launch order. Does the helper exec into the agent process, or does it just set things up and spawn it as a child?

On Python, I've been using it for my agents so far because it's easier for me. But I keep wondering if the sandbox setup can actually stop a Python agent from doing something like `import os; os.system('rm -rf /')` after the pivot, even with a tight seccomp policy. Is the interpreter itself a risk?



   
ReplyQuote
(@shed_sysadmin)
Eminent Member
Joined: 1 week ago
Posts: 19
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

Your findings are exactly why we keep saying the defaults are a starting point, not a finish line.

>What specific changes are you all making?
Start with network. Create a new network namespace with only loopback before the agent starts. That kills the stray DNS queries immediately. Don't forget to give it a clean /etc/resolv.conf (or better, none at all).

For files, the pivot_root approach others mentioned is correct. Here's a minimal sequence I use in a wrapper:

```c
mkdir("/newroot", 0755);
mount("tmpfs", "/newroot", "tmpfs", 0, NULL);
pivot_root("/newroot", "/newroot/.oldroot");
chdir("/");
// Now mount your task dir into /workspace
```

Do that, *then* drop capabilities and apply seccomp. That stops the `/etc/passwd` reads cold.

The step-by-step is less about a single config and more about getting the order of operations right. Namespace -> pivot -> seccomp. Do it wrong and you leave gaps.


--Chris


   
ReplyQuote
(@threat_model_junior)
Eminent Member
Joined: 1 week ago
Posts: 16
Translate
English
Spanish
French
German
Italian
Portuguese
Russian
Chinese
Japanese
Korean
Arabic
Hindi
Dutch
Polish
Turkish
Vietnamese
Thai
Swedish
Danish
Finnish
Norwegian
Czech
Hungarian
Romanian
Greek
Hebrew
Indonesian
Malay
Ukrainian
Bulgarian
Croatian
Slovak
Slovenian
Serbian
Lithuanian
Latvian
Estonian
 

>Start with network. Create a new network namespace with only loopback before the agent starts.

That order makes sense, but I'm stuck on a practical question. After you create the new network namespace, doesn't the agent still need to make at least one allowed outbound connection if its task is to fetch something from the internet? How do you wire that up safely? Do you have a separate, filtered proxy process in the original namespace that it talks to over a socket?



   
ReplyQuote