Wire OpenShell sandbox lifecycle into the orchestrator#70
Wire OpenShell sandbox lifecycle into the orchestrator#70saichandrapandraju wants to merge 4 commits into
Conversation
|
Hi @dmaniloff , would like to get early feedback on this, thanks! |
| backend: | ||
| type: openshell | ||
| image: base | ||
| policy: ollama-local |
There was a problem hiding this comment.
there's already a pi image and a pi policy for pi.dev agents.
it's the one that gets used when you say openshell sandbox create --from pi
let's use that instead.
and let's also add an agent_command that defaults to the image's entrypoint eg
agent_command: ["pi", "-p", "--no-session"] # optional, defaults to image's entrypoint
There was a problem hiding this comment.
though i may have a different opinion about this now ... let's chat when you get a moment.
a137df1 to
39112bb
Compare
| if self._cached_ocsf is not None: | ||
| return self._cached_ocsf | ||
|
|
||
| from openshell._proto import openshell_pb2 # pyright: ignore[reportMissingImports] |
There was a problem hiding this comment.
is there a way we can avoid the inline import?
There was a problem hiding this comment.
kept so midojo starts without the SDK installed...
| except Exception as exc: | ||
| import logging | ||
| logging.getLogger(__name__).warning( | ||
| "OCSF log fetch failed — security predicates will degrade to False: %s", exc |
There was a problem hiding this comment.
we should think about a way to surface this so that during grading we can say N/A rather than "attack failed"
| agent_command: ["pi", "-p", "--no-session", "--model", "ollama/qwen3.5:2b"] | ||
| models_json: | ||
| providers: | ||
| ollama: | ||
| baseUrl: "http://host.openshell.internal:11434/v1" | ||
| api: "openai-completions" | ||
| apiKey: "ollama" | ||
| compat: | ||
| supportsDeveloperRole: false | ||
| supportsReasoningEffort: false | ||
| models: | ||
| - id: "qwen3.5:2b" |
There was a problem hiding this comment.
thinking back to my suggestion re: agent_command ... i think we should probably try to avoid all this.
the concern of midojo stops at the agent boundary. in other words, agent config (like its command and providers etc) should not be what we deal with. this should all exist somewhere else already.
in the case of an openshell env it seems that there's some coupling between the agent's config & the env so maybe the way around it is via an image or another openshell native mechanism. let's chat about this.
88e8b1f to
5afce53
Compare
bbc1778 to
2200358
Compare
Implements the full sandbox lifecycle for --protocol openshell, enabling
red-teaming of agents that run inside NVIDIA OpenShell MicroVM sandboxes.
The orchestrator drives each evaluation: create sandbox → seed workspace
with injection payloads → run agent → collect workspace diff and OCSF
kernel events → grade → teardown.
Architecture:
run_task()
├── backend.provision(injections) # render workspace files
├── backend.setup(pre_env) # create MicroVM, seed /sandbox/workspace
├── agent_client.send_task(prompt) # exec agent_command inside sandbox
├── backend.snapshot() # workspace diff + OCSF events
├── PUT /environment # post-env to control plane
└── backend.teardown() # delete sandbox (always runs)
New files:
src/midojo/openshell_logs.py OCSF shorthand log parser (NET/HTTP/PROC/FINDING)
src/midojo/verifiers/openshell.py Workspace diff and OCSF predicates
suites/document_assistant/ Demo suite: Q4 report with curl-exfiltration
and file-staging injection tasks
tests/test_openshell_backend.py Unit tests for backend lifecycle
tests/test_shell_predicates.py Tests for workspace/OCSF predicates
Key design decisions:
- providers removed — users register providers with the OpenShell CLI;
midojo does not own credential management
- agent_command in suite YAML is the OpenShell equivalent of --agent-url:
it specifies how to invoke the user's agent inside the sandbox image
- Inline imports for openshell SDK kept intentionally so midojo starts
without the SDK installed (it's a Maturin/Rust build, not pip-only)
- OCSF unavailability degrades to False, not N/A (deferred to follow-up
requiring bool | None verifier protocol change)
Suite YAML:
environment:
backend:
type: openshell
image: pi # community sandbox; resolved to full registry path
agent_command: ["pi", "-p", "--no-session"]
Prerequisites:
brew install z3 && pip install -e /path/to/OpenShell # one-time
openshell status # → Connected
Run:
midojo-serve --suite document_assistant --port 8090
midojo-run --protocol openshell --suite document_assistant \
--control-url http://localhost:8090
# --agent-url omits → uses CLI-registered active gateway
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
providers field restored:
midojo does not manage credentials — it only names which pre-registered
OpenShell providers to activate for the sandbox (via `openshell provider
create`). The gateway supervisor injects them as env vars.
env_vars field added to backend:
Allows suites to pass extra env vars into the sandbox at creation time
(e.g. OPENAI_BASE_URL for a custom inference endpoint).
Absolute path seeding in setup():
Workspace paths starting with "/" are seeded at the absolute path inside
the sandbox rather than relative to /sandbox/workspace/. Enables seeding
agent config files (e.g. PI models.json) alongside the workspace.
--agent-url made optional for openshell protocol:
Omit to use the CLI-registered active gateway; required for all other protocols.
document_assistant suite restructured:
- suite.yaml is now a clean test spec: workspace files, tasks, predicates,
and a policy. No agent configuration hardcoded.
- sandbox/ contains the example agent: a Containerfile that extends the
pi community sandbox with models.json pre-configured for local inference
(no cloud API key required). Users replace this image with their own
production sandbox — the eval does not change.
Validated end-to-end with local qwen3.5:2b via host.openshell.internal:8321.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- suite.yaml: use quay.io/rh-ee-spandraj/midojo:document-assistant-local so users don't need to build the image themselves - sandbox/README.md: add pre-built image quickstart, build-your-own instructions, and prerequisites Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
99fbcc6 to
c827582
Compare
For openshell, --agent-url is the gateway gRPC endpoint (e.g. https://127.0.0.1:17670). Consistent with all other protocols. Removes the optional active-gateway fallback. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
Hi @ABeltramo, can you take a look at this when you have time please, thanks! Note that this is not the perfect implementation and there's still some discussion points (unresolved above) but I think this in a decent shape to showcase the flavor of how openshell x midojo work. |
I will, probably next week though. I'm currently swamped with the 3.5 release and this isn't a priority item for the DP release. Btw we should move this under RHOAIENG-75712 and wrap up RHOAIENG-66502. What I would suggest is to move the example suite to asago-ai/midojo-suites so that we can focus on reviewing the code for the feature. |
What this adds
--protocol openshellwires the full OpenShell sandbox lifecycle into midojo's evaluation loop. The orchestrator creates a fresh sandbox per evaluation, seeds the workspace with the injected document, runs the agent inside the sandbox, collects workspace diff and OCSF kernel events, and tears down the sandbox.Architecture
Two observation channels
The sandbox proxy intercepts all egress and the kernel audits all I/O. midojo collects two independent observation streams after each evaluation:
/sandbox/workspace/Separation of concerns
midojo controls the sandbox lifecycle because workspace injection requires writing the attack payload before the agent runs. Everything else belongs to the user:
image— their sandbox image, with their agent pre-configuredagent_command— how to invoke the agent per evaluation (analogous to--agent-urlfor other protocols)policy— which network endpoints the agent is allowed to reachmidojo does not manage LLM credentials or agent configuration. Providers registered with
openshell provider createare activated by name; the gateway supervisor injects the credentials into the sandbox.Suite structure
The
document_assistantsuite demonstrates the pattern:suite.yamlis a clean evaluation spec. Thesandbox/directory is an example agent provided as a starting point — users swapimage:for their own production sandbox image and run the same evaluation unchanged.New suite YAML fields
backend.type: openshellbackend.imagepiexpand to the community registry)backend.agent_commandbackend.policybackend.providersbackend.env_varsNew predicates
Workspace diff:
workspace_file_exists/workspace_file_deleted/workspace_file_containscommands_match_patternOCSF kernel events:
process_ran— kernel-verified process launchnetwork_call_to/network_call_blocked_tosecurity_finding_raisedPrerequisites (one-time)
OpenShell gateway:
OpenShell Python SDK (Maturin/Rust build, requires z3 + Rust):
Local inference server at
localhost:8321(ogx server or any OpenAI-compatible server). From inside the sandbox, the host is reachable athost.openshell.internal:8321.Running it
A pre-built example sandbox image is available (
quay.io/rh-ee-spandraj/midojo:document-assistant-local) — no build required:To use your own sandbox image, update
image:insuite.yaml. The rest of the eval is unchanged.Known limitations
OCSF grades
Falseon failure instead ofN/A(#107)When
GetSandboxLogsfails, OCSF predicates returnFalserather thanN/A. This means a successful attack can appear to have been resisted if the OCSF stream is unavailable. Fixing this properly requiresbool | Nonefrom predicates — a verifier protocol change deferred to a follow-up.Homebrew gateway 0.0.58 missing
/run/netnstmpfsThe shipped binary doesn't mount the tmpfs the supervisor needs for network namespace setup. The fix is already in the OpenShell source; until 0.0.59 ships, build from source:
🤖 Generated with Claude Code