Skip to content

Add OPA-gated subprocess execution APIs (Deno.Command and child_process.exec)#101

Open
r33drichards wants to merge 2 commits intomainfrom
claude/add-opa-subprocess-policies-P3Iic
Open

Add OPA-gated subprocess execution APIs (Deno.Command and child_process.exec)#101
r33drichards wants to merge 2 commits intomainfrom
claude/add-opa-subprocess-policies-P3Iic

Conversation

@r33drichards
Copy link
Copy Markdown
Owner

Summary

This PR adds policy-gated subprocess execution capabilities to the JavaScript runtime, supporting both Deno's Command API and Node.js-compatible child_process.exec(). All subprocess invocations are evaluated against an OPA policy chain before execution.

Key Changes

  • New subprocess module (server/src/engine/subprocess.rs):

    • Implements two async ops: op_subprocess_output (for Deno.Command.output()) and op_subprocess_exec (for child_process.exec())
    • Provides JavaScript wrappers that expose both APIs to user code
    • Supports command execution with arguments, working directory, and environment variable configuration
    • Returns structured output with exit code, stdout, stderr, and success status
    • Encodes binary output as base64 for safe transmission across the JS/Rust boundary
  • OPA policy integration:

    • Subprocess operations are gated by OPA policies before execution
    • Policy input includes operation type, command, arguments, working directory, and environment variables
    • Supports three operation types: command_output, command_spawn, and exec
    • Added subprocess field to PoliciesConfig to configure subprocess policy chains
  • Runtime integration:

    • Added SubprocessConfig to ExecutionConfig for passing policy chains to the runtime
    • Conditionally registers subprocess extension and injects JavaScript wrappers when subprocess policies are configured
    • Integrated subprocess policy chain initialization in main.rs
  • Example policy (policies/subprocess.rego):

    • Provides template for defining allowed commands and shell command patterns
    • Demonstrates filtering by operation type, command name, and working directory
  • Comprehensive testing:

    • Unit tests for policy input serialization, base64 encoding, and options deserialization
    • Integration tests verifying policy allow/deny behavior for subprocess operations

Implementation Details

  • Binary output from subprocess calls is base64-encoded in Rust and decoded back to Uint8Array in JavaScript for the Deno API
  • The child_process.exec() implementation supports configurable encoding (defaults to UTF-8 strings, can be set to "buffer" for binary)
  • Shell execution for exec() is platform-aware (uses /bin/sh -c on Unix, cmd /C on Windows)
  • Policy evaluation is async and occurs before any subprocess is spawned

https://claude.ai/code/session_011eLgvj7BYjWQzr42j3WHK5

Adds policy-gated subprocess execution to the JS runtime, exposing two
APIs behind OPA policy evaluation:

- Deno.Command: `new Deno.Command("cmd", {args}).output()` runs a
  command to completion, returning {code, stdout, stderr} with
  Uint8Array output (matching the Deno subprocess API).

- child_process.exec: `await child_process.exec("shell command", opts)`
  runs a shell command via /bin/sh -c, returning {code, stdout, stderr}
  as strings (matching the Node.js child_process.exec API).

Every invocation is checked against a PolicyChain (local Rego or remote
OPA) before execution. The policy input includes operation type, command,
args, cwd, and env. Configuration uses the same --policies-json schema
with a new "subprocess" key.

https://claude.ai/code/session_011eLgvj7BYjWQzr42j3WHK5
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 2, 2026

MCP-V8 Load Test Benchmark Report

Comparison of single-node vs 3-node cluster at various request rates.

Results

Topology Target Rate Actual Iter/s HTTP Req/s Exec Avg (ms) Exec p95 (ms) Exec p99 (ms) Success % Dropped Max VUs
cluster-stateful 100/s 46.5 1905.6 2001.78 5119.54 5139.68 79% 2998 100
cluster-stateful 200/s 52.1 3740.5 3601.11 5141.81 5157.39 39.2% 8609 200
cluster-stateless 1000/s 366 1521.2 2596.89 10000.66 12607.7 98% 37310 1000
cluster-stateless 100/s 99.9 300.8 52.51 53.28 62.63 100% 0 10
cluster-stateless 200/s 199.8 602.2 53.26 54.28 102.75 100% 1 21
cluster-stateless 500/s 474 1775.8 262.91 966.33 3197.77 100% 814 500
single-stateful 100/s 19 1866.9 4996.37 5165.47 5177.75 6% 4763 100
single-stateful 200/s 37.6 3797.5 5152.44 5179.43 5191.76 0% 9601 200
single-stateless 1000/s 143.8 361.9 6547.84 11965.2 15550.17 73.4% 50765 1000
single-stateless 100/s 99.9 309.1 57.68 103.89 106.11 100% 2 12
single-stateless 200/s 198.3 650.2 91.64 211.14 391.91 100% 62 80
single-stateless 500/s 143.6 386.4 3340.45 7223.04 10302.02 99.8% 21128 500

P95 Latency

Topology Rate P95 (ms)
cluster-stateful 100/s 5119.54 ███████████████████████████
cluster-stateful 200/s 5141.81 ███████████████████████████
cluster-stateless 100/s 53.28 █████████████
cluster-stateless 200/s 54.28 █████████████
cluster-stateless 500/s 966.33 ██████████████████████
cluster-stateless 1000/s 10000.66 █████████████████████████████
single-stateful 100/s 5165.47 ███████████████████████████
single-stateful 200/s 5179.43 ███████████████████████████
single-stateless 100/s 103.89 ███████████████
single-stateless 200/s 211.14 █████████████████
single-stateless 500/s 7223.04 ████████████████████████████
single-stateless 1000/s 11965.2 ██████████████████████████████

Notes

  • Target Rate: The configured constant-arrival-rate (requests/second k6 attempts)
  • Actual Iter/s: Achieved iterations per second (each iteration = 1 POST /api/exec)
  • HTTP Req/s: Total HTTP requests per second (1 per iteration)
  • Dropped: Iterations k6 couldn't schedule because VUs were exhausted (indicates server saturation)
  • Topology: single = 1 MCP-V8 node; cluster = 3 MCP-V8 nodes with Raft

Tests both Deno.Command and child_process.exec APIs with a local
Rego policy that allows only echo and cat commands while denying
everything else. Covers allowed/denied commands, exit codes,
cwd options, stdout/stderr capture, and API availability checks.

https://claude.ai/code/session_01FmHwVDVVu1pPfpedeA6WDL
r33drichards added a commit that referenced this pull request Mar 23, 2026
Resolves conflicts in server/src/engine/mod.rs, server/src/main.rs,
server/src/engine/opa.rs, and flake.nix.

Both mcp_headers (from main) and subprocess_config (from #101) are
preserved and work together in ExecutionConfig and the RunJsRequest builder.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants