Skip to content

Add external module import policy enforcement via OPA#78

Merged
r33drichards merged 6 commits into
mainfrom
claude/audit-packages-disable-external-RMSXg
Mar 1, 2026
Merged

Add external module import policy enforcement via OPA#78
r33drichards merged 6 commits into
mainfrom
claude/audit-packages-disable-external-RMSXg

Conversation

@r33drichards

Copy link
Copy Markdown
Owner

Summary

This PR implements a comprehensive module import policy system that allows operators to control which external packages (npm, JSR, and URL imports) are permitted in JavaScript code execution. By default, external module imports are blocked for security. When enabled with --allow-external-modules, imports can be optionally audited against an OPA policy before being fetched.

Key Changes

  • Module Loader Configuration: Added ModuleLoaderConfig struct to control external module access with three settings:

    • allow_external: Enable/disable all external module imports (default: false)
    • opa_client: Optional OPA client for policy auditing
    • opa_module_policy: OPA policy path for module validation
  • Default-Deny Security Model: External module imports (npm:, jsr:, https://) are now rejected by default with a clear error message directing users to use --allow-external-modules

  • OPA Module Auditing: When both --allow-external-modules and --opa-module-policy are set, each module import is audited against the OPA policy before fetching. The policy receives:

    • Original specifier (e.g., "npm:lodash-es@4.17.21")
    • Specifier type ("npm", "jsr", "url", or "relative")
    • Resolved URL that will be fetched
    • Parsed URL components (scheme, host, path)
  • CLI Arguments: Added two new command-line flags:

    • --allow-external-modules: Enable external module imports
    • --opa-module-policy PATH: OPA policy path (requires both --opa-url and --allow-external-modules)
  • OPA Policy Example: Added policies/modules.rego demonstrating a practical policy that:

    • Whitelists specific npm packages (lodash-es, uuid, date-fns, zod)
    • Whitelists specific JSR packages (@luca/cases, @std/path)
    • Whitelists specific URL hosts (esm.sh, cdn.jsdelivr.net, unpkg.com)
  • Comprehensive Testing:

    • Unit tests for module loader resolution with external modules disabled/enabled
    • Engine-level tests verifying npm/jsr/URL imports are blocked by default
    • End-to-end tests with real OPA server validating policy enforcement
    • Tests confirming plain JavaScript continues to work regardless of module settings

Implementation Details

  • Module resolution happens at the NetworkModuleLoader level, rejecting external specifiers before any network requests
  • OPA auditing is performed asynchronously during module loading, with policy denial errors clearly distinguished from network errors
  • The system gracefully handles cases where OPA is unavailable or the policy path doesn't exist
  • Relative imports (e.g., "./utils.js") are always allowed regardless of external module settings
  • All existing tests updated to explicitly configure module loader behavior where needed

https://claude.ai/code/session_015zV3QdtyMgbizp4ZrSzqjq

@github-actions

github-actions Bot commented Mar 1, 2026

Copy link
Copy Markdown

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 99.2 377.5 92.03 304.18 710.42 100% 42 51
cluster-stateful 200/s 124.1 3794.2 1476.71 4330.76 5413.73 100% 4052 200
cluster-stateless 1000/s 355.8 870.6 2678.36 10000.85 13310.53 95% 37963 1000
cluster-stateless 100/s 99.9 299.8 52.43 54.26 56.38 100% 0 10
cluster-stateless 200/s 199.8 600.9 54.56 58.84 74.93 100% 0 20
cluster-stateless 500/s 450.2 1240.5 558.95 2824.02 4888.24 100% 2630 500
single-stateful 100/s 20.1 1851.2 4695.14 5215.65 5232.21 53.7% 4692 100
single-stateful 200/s 37.8 3719.8 5044.12 5248.84 5292.51 5.5% 9538 200
single-stateless 1000/s 352.6 897.3 2222.9 10000.59 10785.08 21.5% 38846 1000
single-stateless 100/s 99.9 299.8 53.33 56.22 59.44 100% 0 10
single-stateless 200/s 193.8 563.3 128.77 498.81 1449.71 100% 235 200
single-stateless 500/s 138.8 421 3454.12 7688.16 10975.65 99.4% 21422 500

P95 Latency

Topology Rate P95 (ms)
cluster-stateful 100/s 304.18 ███████████████████
cluster-stateful 200/s 4330.76 ███████████████████████████
cluster-stateless 100/s 54.26 █████████████
cluster-stateless 200/s 58.84 █████████████
cluster-stateless 500/s 2824.02 ██████████████████████████
cluster-stateless 1000/s 10000.85 ██████████████████████████████
single-stateful 100/s 5215.65 ████████████████████████████
single-stateful 200/s 5248.84 ████████████████████████████
single-stateless 100/s 56.22 █████████████
single-stateless 200/s 498.81 ████████████████████
single-stateless 500/s 7688.16 █████████████████████████████
single-stateless 1000/s 10000.59 ██████████████████████████████

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

claude added 3 commits March 1, 2026 00:09
…auditing

External module imports (npm:, jsr:, URL) are now blocked by default.
Two new CLI flags control this behavior:
- --allow-external-modules: enables external imports
- --opa-module-policy: when set with --opa-url, audits each module import
  against an OPA policy before fetching

Includes OPA policy (policies/modules.rego) with allowlists for npm packages,
JSR packages, and URL hosts. Adds unit tests, engine integration tests, and
e2e tests that spin up a real OPA server to verify policy enforcement.

https://claude.ai/code/session_015zV3QdtyMgbizp4ZrSzqjq
…tests

Remove server/tests/module_policy_e2e.rs and replace it with a Docker
Compose-based integration test that spins up real containers (OPA + mcp-js)
and makes actual HTTP requests, matching the pattern of the existing
docker-compose-integration.sh tests.

New files:
- docker-compose.module-policy.yml: two mcp-js services (default + OPA policy)
- tests/module-policy-integration.sh: 8 tests covering blocked imports, OPA
  allow/deny, and plain JS execution
- .github/workflows/module-policy-integration.yml: CI workflow

https://claude.ai/code/session_01ReRwVdpytBRvCLn8pWHNEV
Remove push-to-main, daily schedule, and workflow_dispatch triggers.

https://claude.ai/code/session_01ReRwVdpytBRvCLn8pWHNEV
@r33drichards r33drichards force-pushed the claude/audit-packages-disable-external-RMSXg branch from a0bc330 to 7693fd8 Compare March 1, 2026 08:09
r33drichards and others added 3 commits March 1, 2026 00:17
- Add missing module_loader_config parameter to fuzz_execute_stateless
- Update docker-compose-integration.sh for async execution model (submit + poll)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
test_stdio_run_js_execution was checking raw run_js response for "2" but
the async API returns an execution_id UUID. Use run_js_and_wait to poll
for the actual result.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@r33drichards r33drichards merged commit 98d00c5 into main Mar 1, 2026
17 checks passed
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