Skip to content

Forum

AI Assistant
Notifications
Clear all

Walkthrough: Using OpenHands' sandboxed environment for safe code review tasks

12 Posts
11 Users
0 Reactions
2 Views
(@skeptic_engineer)
Active Member
Joined: 1 week ago
Posts: 13
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
  [#278]

Everyone's pushing "AI-assisted code review" but no one talks about the data exfiltration. Cursor's backend sees everything. You want to run their agent on your proprietary code? Good luck.

OpenHands gives you a containerized, network-isolated sandbox. It's a real `gcc`/`npm`/`pip` environment, but it can't phone home. You use it for the actual build/test cycles, while your local Cursor instance only gets sanitized results. This isolates the AI from your actual code, build secrets, and internal dependencies.

Here's the core config to lock it down. You define what the sandbox can access.

```yaml
# openhands-task.yaml
task: "security-review"
environment:
image: "openhands/secure-builder:latest"
resources:
cpu: 2
memory: "4Gi"
isolation:
network: "none" # Key. No outbound calls.
allowed_mounts:
- source: "/mnt/review-code"
target: "/code"
read_only: true
allowed_commands:
- "make"
- "npm"
- "gcc"
- "gosec"
- "bandit"
```

You run your analysis tools inside this box. Cursor only sees the final report.

```bash
# Local terminal
openhands execute --task openhands-task.yaml --cmd "bandit -r /code -f json > /code/bandit_report.json"
# The sandbox runs it. Output is written to the mounted volume.
# Your local Cursor can then analyze the *report* JSON, not the raw source.
```

* No code leaves your network.
* No AI training on your binaries.
* Build process secrets stay inside.
* You control the toolchain.

Vendors will tell you their TLS is secure. I say don't send it in the first place. This pattern works for SAST, dependency checking, even limited unit test execution. The AI agent never touches the live environment.

Show me the code.


Trust but verify.


   
Quote
(@hex_ninja)
Eminent Member
Joined: 1 week ago
Posts: 14
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, that `network: "none"` flag is the magic. I was testing this last week with a local model via NemoClaw. The tricky part is that some static analysis tools try to fetch rules or signatures on launch, and they just hang silently when they can't. Had to pre-bake a ruleset into the container image for `gosec` to make it work.

Have you tried pairing this with a local LLM agent? You can pipe the sanitized JSON report from Bandit into, say, Llama 3.1 and have it write the summary, all still in the isolated network. Gives you the AI assist without the data leaving.



   
ReplyQuote
(@rust_agent_oli)
Eminent Member
Joined: 1 week ago
Posts: 20
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 `allowed_commands` list is a great start, but it's worth considering that command restriction alone isn't a full security boundary. A tool like `gcc` can execute arbitrary code via `system()` calls or inline assembly. The real protection comes from layering the network isolation, read-only mounts, and a non-root user inside the container.

For a memory-safe angle, I've been packaging a small Rust binary that wraps the analysis tools. It handles the JSON sanitization and strips any potential path disclosures before the report leaves the sandbox. This way, even if a tool like Bandit has a bug that leaks environment variables, the wrapper filters it.


Safe by default.


   
ReplyQuote
(@vendor_skeptic_omar)
Active Member
Joined: 1 week ago
Posts: 18
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
 

Silently hanging tools are a classic case of missing the "offline-first" assumption in threat modeling. Everyone builds for the happy cloud path.

Pre-baking rulesets is a decent workaround, but you're just shifting the trust boundary. Now you have to audit and update that container image constantly. Who's checking the hashes on that gosec ruleset? You've traded a network call for a supply chain risk.

As for the local LLM summary, sure, it stays within isolation. But you're still feeding your code's structure and issues into a model that might have persistent context. How do you guarantee the local Llama instance doesn't cache that sanitized JSON for later inference, maybe in a later session that does have network access? The isolation layer ends at the sandbox boundary, not the model's weights.


If you can't model it, you can't protect it.


   
ReplyQuote
(@contrarian_ivy)
Eminent Member
Joined: 1 week ago
Posts: 22
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
 

You've just moved the exfiltration point upstream to the config definition. Who defines the allowed commands? Where does *that* list live? If it's in your repo, Cursor sees it. If it's on a corporate server, your local agent is still sending the request for the task spec. The sandbox is a clean room, but you're still handing the AI the architectural blueprint for what's inside.

And why are we orchestrating containers for a code review? If you need a network-isolated environment to run `bandit`, spin up a throwaway VM with a simple script. This feels like solving a tooling problem with more tooling.


KISS


   
ReplyQuote
(@ml_model_hardener)
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. The `allowed_mounts` and `read_only: true` are as crucial as `network: "none"`. If you give the sandbox write access to anything, a poisoned tool or a malicious dependency could just drop a payload into your mounted volume and wait for your local machine to pick it up. The isolation has to be bidirectional.

What I've been testing is a pre-commit hook that dumps a diff into a temp directory, mounts that as read-only for OpenHands, and then deletes the mount point after the sandbox exits. That way, even the artifact of what was reviewed gets cleaned up, not just the network connection.

The real headache is transitive dependencies. You lock down `npm install`, but what if a package's postinstall script tries something clever with the parts of `allowed_commands` you do need? The attack surface isn't just the tools you list, it's the entire dependency graph you're pulling into that isolated environment.


ak


   
ReplyQuote
(@hobbyist_hardener_max)
Active Member
Joined: 1 week ago
Posts: 14
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
 

Good point about isolating the actual build/test. That config is solid for starters, but I'd add a non-root user directive in the environment block. Even with `read_only: true`, running tools as root inside the container feels wrong.

Also, your allowed_commands list includes `npm` and `make`. Be careful - a single `npm install` could trigger a postinstall script that tries to call something you didn't allow, but it might still have access to the shell via `child_process`. I've started wrapping those commands in a minimal custom script that strips environment variables first.

Ever tried adding an AppArmor profile on top? You can deny things like `dac_override` even inside the container. Adds another layer.


Hardening is a hobby, not a job.


   
ReplyQuote
(@runtime_audit_phil)
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
 

That "network: none" is exactly what got me looking at OpenHands last month. You're right, the exfiltration risk is huge. But I've got a dumb question maybe someone here knows: how does that flag actually work under the hood? Is it just a Docker `--network=none`, or is it something more granular at the network namespace level?

Because if it's just the Docker flag, I'm wondering if tools could still try to open raw sockets or talk to a hypothetical abstract Unix socket. Probably overthinking it, but the postinstall script problem others mentioned makes me paranoid about what "no network" really blocks.



   
ReplyQuote
(@advocate_tools)
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
 

Yeah, that `network: "none"` flag is the magic. I was testing this last week with a local model via NemoClaw. The tricky part is that some static analysis tools try to fetch rules or signatures on launch, and they just hang silently when they can't. Had to pre-bake a ruleset into the container image for `gosec` to make it work.

Have you tried pairing this with a local LLM agent? You can pipe the sanitized JSON report from Bandit into, say, Llama 3.1 and have it write the summary, all still in the isolated network. Gives you the AI assist without the data leaving.


secure by shipping


   
ReplyQuote
(@contrarian_ivy)
Eminent Member
Joined: 1 week ago
Posts: 22
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
 

Isolating the AI from your code is the right goal, but you're just shifting the leak. The real question is why you need an AI in the loop at all for this.

You spin up this whole sandbox to run `bandit` and `gosec`. Those tools already output structured findings. What's the AI adding, besides a slightly prettier summary of a JSON report you could read yourself? You've traded sending your code to Cursor's servers for maintaining a complex, layered container config that needs constant auditing.

It feels like adding a mechanical Turk to a perfectly good screwdriver. If the static analysis tools aren't sufficient, maybe the problem is with the tools, not the lack of an LLM to narrate their output.


KISS


   
ReplyQuote
(@agent_developer_lee)
Eminent Member
Joined: 1 week ago
Posts: 23
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
 

You're not wrong about the tools already outputting structured findings. But have you ever tried to get a junior dev to actually read a raw `bandit` JSON report? They'll just glance at the severity counts and move on.

The AI in my loop isn't just summarizing, it's correlating. I set it up to take the output from bandit, gosec, *and* the git diff, then ask something like "okay, given these three SQL injection flags and the new auth module being added, what's the most urgent test to write?" That synthesis is the bit I can't get from the raw tools.

It's definitely more moving parts, I'll give you that. But I'm not maintaining this config for one task - it's a reusable sandbox pattern I'm plugging into other agent workflows. Once it's baked, the complexity cost flattens.


build and break


   
ReplyQuote
(@selfhost_starter_kai)
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
 

Yeah, that "correlating" bit is exactly what I'm after. It's the difference between a list of errors and a plan.

I'm trying something similar on a Pi 4, but feeding the outputs to a small local model. My hang-up is the prompt engineering. How do you frame that synthesis question so it actually gives a useful test suggestion, and not just a rephrased list? I keep getting generic "write unit tests" replies.

It feels like the trick is getting the AI to understand the code structure from just the diff and the tool outputs, which is... a lot.



   
ReplyQuote