@orygn/opa-mcp 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -17,6 +17,51 @@ not part of the public surface and may change in minor releases.
17
17
 
18
18
  ## [Unreleased]
19
19
 
20
+ ## [0.4.0] - 2026-09-03
21
+
22
+ ### Security
23
+
24
+ - Policies evaluated through this server no longer receive the server's environment.
25
+ Rego exposes the environment of the `opa` process through `opa.runtime().env`, and
26
+ child processes inherited `process.env`, so any evaluated policy could read
27
+ `OPA_TOKEN`, the `GITHUB_TOKEN` the README asks users to set for
28
+ `rego_playground_share`, and everything else the operator's client passed in.
29
+ `rego_eval` accepts inline source, so `OPA_MCP_ALLOWED_PATHS` never applied and no
30
+ filesystem access was needed; a policy arriving through a README, an issue, or a
31
+ diff was enough. `conftest_test` was affected the same way, and is the likelier
32
+ route to third-party policy. Children now get an explicit allow-list containing no
33
+ secret. `OPA_MCP_PASSTHROUGH_ENV` opts individual variables back in, and anything
34
+ named there is readable by evaluated policy by design.
35
+
36
+ On Windows, libuv copies a fixed set of variables to every child regardless of what
37
+ is requested. `USERNAME`, `USERDOMAIN` and `LOGONSERVER` cannot be removed, so they
38
+ are blanked rather than left to disclose the operating-system user and domain.
39
+
40
+ ### Fixed
41
+
42
+ - A subprocess that produces very large output no longer takes the server down.
43
+ stdout and stderr were captured without a size limit, and decoding a capture past
44
+ V8's maximum string length throws inside an async `close` handler, where no tool's
45
+ `try`/`catch` can reach it and the process exits. A policy iterating
46
+ `numbers.range(1, 1000)` under `--explain full` produced 518 MiB in under seven
47
+ seconds, so the 30-second timeout never applied; `opa` buffers its result and writes
48
+ it in one burst at exit, which means the runs that complete comfortably are the
49
+ dangerous ones. Capture is now capped per stream, the child is stopped on overflow,
50
+ and the tool returns the new `OUTPUT_TOO_LARGE` error code. Configurable with
51
+ `OPA_MCP_MAX_SUBPROCESS_BYTES`, default 32 MiB.
52
+
53
+ - `rego_test_multiroot` reports `OUTPUT_TOO_LARGE` and `TIMEOUT` through the same mapper as
54
+ every other tool. It carried its own copy of the failure ladder, and a run killed for
55
+ producing too much output reported `OPA_BINARY_NOT_FOUND`, sending the caller after an
56
+ install problem that did not exist. This is the same defect that was fixed for timeouts in
57
+ 0.3.0, in the one place that did not share the fix.
58
+
59
+ ### Added
60
+
61
+ - `OPA_MCP_MAX_SUBPROCESS_BYTES` and `OPA_MCP_PASSTHROUGH_ENV` environment variables.
62
+ - `OUTPUT_TOO_LARGE` error code.
63
+
64
+
20
65
  ## [0.3.0] - 2026-08-05
21
66
 
22
67
  ### Changed
@@ -983,7 +1028,19 @@ wrappers end-to-end. CI matrix: Ubuntu, macOS, and Windows on Node
983
1028
  20 and 22, plus CodeQL security scanning and weekly Dependabot updates
984
1029
  for npm, GitHub Actions, and Docker base images.
985
1030
 
986
- [Unreleased]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.12...HEAD
1031
+ [Unreleased]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.4.0...HEAD
1032
+ [0.4.0]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.3.0...v0.4.0
1033
+ [0.3.0]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.2.1...v0.3.0
1034
+ [0.2.1]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.2.0...v0.2.1
1035
+ [0.2.0]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.20...v0.2.0
1036
+ [0.1.20]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.19...v0.1.20
1037
+ [0.1.19]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.18...v0.1.19
1038
+ [0.1.18]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.17...v0.1.18
1039
+ [0.1.17]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.16...v0.1.17
1040
+ [0.1.16]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.15...v0.1.16
1041
+ [0.1.15]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.14...v0.1.15
1042
+ [0.1.14]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.13...v0.1.14
1043
+ [0.1.13]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.12...v0.1.13
987
1044
  [0.1.12]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.11...v0.1.12
988
1045
  [0.1.11]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.10...v0.1.11
989
1046
  [0.1.10]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.9...v0.1.10
@@ -994,6 +1051,6 @@ for npm, GitHub Actions, and Docker base images.
994
1051
  [0.1.5]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.4...v0.1.5
995
1052
  [0.1.4]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.3...v0.1.4
996
1053
  [0.1.3]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.2...v0.1.3
997
- [0.1.2]: https://github.com/OrygnsCode/opa-mcp-server/releases/tag/v0.1.2
998
- [0.1.1]: https://github.com/OrygnsCode/opa-mcp-server/releases/tag/v0.1.1
1054
+ [0.1.2]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.1...v0.1.2
1055
+ [0.1.1]: https://github.com/OrygnsCode/opa-mcp-server/compare/v0.1.0...v0.1.1
999
1056
  [0.1.0]: https://github.com/OrygnsCode/opa-mcp-server/releases/tag/v0.1.0
package/README.md CHANGED
@@ -22,10 +22,20 @@ environment.
22
22
  +--------------------+ 52 tools +-----------------+ +---------------------+
23
23
  ```
24
24
 
25
- > **Status:** v0.3.0. Tool surface, error codes, and
25
+ > **Status:** v0.4.0. Tool surface, error codes, and
26
26
  > environment variables follow [SemVer](https://semver.org/) from
27
27
  > v0.1.0 forward.
28
28
 
29
+ > **Upgrading to 0.4.0:** subprocesses no longer inherit the server's
30
+ > environment. A policy that read a variable through `opa.runtime().env`
31
+ > will no longer see it; name the variable in `OPA_MCP_PASSTHROUGH_ENV` if
32
+ > it is genuinely needed. See the security section for why.
33
+
34
+ > **Upgrading to 0.3.0:** the bundled OPA is now 1.19, so Rego v0 policies
35
+ > no longer parse (`if` is required before a rule body, `contains` before a
36
+ > partial set). Run `rego_migrate_v1` to convert them. If you supply your own
37
+ > binary via `OPA_BINARY` or `PATH`, nothing changes.
38
+
29
39
  ---
30
40
 
31
41
  ## Table of contents
@@ -225,8 +235,9 @@ which opa && which regal # macOS / Linux
225
235
  Get-Command opa, regal | Select-Object Source # Windows
226
236
  ```
227
237
 
228
- This does not affect the **Docker** or **MCPB** install paths; those
229
- ship `opa` and `regal` inside the bundle and bypass `PATH` entirely.
238
+ This does not affect the **Docker** install path, which ships `opa` and
239
+ `regal` in the image and bypasses `PATH` entirely. The **MCPB** bundle
240
+ carries neither and resolves `opa` the same way the npm install does.
230
241
  See [Troubleshooting](#troubleshooting) for full detail.
231
242
 
232
243
  ## Configuration
@@ -235,20 +246,22 @@ The server reads its configuration from environment variables. Every
235
246
  variable is optional; defaults are sensible for a local OPA on
236
247
  `http://localhost:8181`.
237
248
 
238
- | Variable | Default | Purpose |
239
- | ---------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
240
- | `OPA_URL` | `http://localhost:8181` | Base URL of an OPA REST endpoint, used by `opa_*` tools. |
241
- | `OPA_TOKEN` | _(unset)_ | Bearer token for OPA, if your instance requires auth. Treated as a secret. Never echoed in logs or tool responses. |
242
- | `OPA_BINARY` | `opa` (on `PATH`) | Path to the `opa` CLI, used by `rego_*` tools. |
243
- | `REGAL_BINARY` | `regal` (on `PATH`) | Path to the `regal` linter. Only required by `rego_lint`. |
244
- | `CONFTEST_BINARY` | `conftest` (on `PATH`) | Path to the `conftest` binary. Only required by `conftest_*` tools. Returns `CONFTEST_NOT_FOUND` if absent. |
245
- | `OPA_MCP_ALLOWED_PATHS` | _(unset)_ | Comma- or semicolon-separated list of directories the server is allowed to read policies from. **When unset, file-based tools refuse to read from disk.** |
246
- | `OPA_MCP_LOG_FILE` | `<tmpdir>/orygn-opa-mcp.log` | Path the server appends logs to. The server never writes to stdout; that channel is reserved for the MCP protocol. |
247
- | `OPA_MCP_LOG_LEVEL` | `info` | One of `debug`, `info`, `warn`, `error`. |
248
- | `OPA_MCP_MAX_RESPONSE_BYTES` | `100000` | Hard cap on a single tool response. Larger payloads are truncated with a `__truncated: true` marker. |
249
- | `OPA_MCP_TIMEOUT_MS` | `30000` | Hard timeout for any spawned subprocess (`opa`, `regal`). After this, the child gets `SIGTERM` and then `SIGKILL`. |
250
- | `OPA_MCP_HTTP_TIMEOUT_MS` | `15000` | Timeout for HTTP requests to the OPA REST API. |
251
- | `OPA_MCP_NO_TELEMETRY` | _(unset)_ | Set to `1` to disable the anonymous startup ping. The ping sends the server version, OS platform, and a random install ID. The install ID is stored at `~/.orygn/opa-mcp/install-id` and is generated once on first run. No policy content or file paths are ever sent. |
249
+ | Variable | Default | Purpose |
250
+ | ------------------------------ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
251
+ | `OPA_URL` | `http://localhost:8181` | Base URL of an OPA REST endpoint, used by `opa_*` tools. |
252
+ | `OPA_TOKEN` | _(unset)_ | Bearer token for OPA, if your instance requires auth. Treated as a secret. Never echoed in logs or tool responses. |
253
+ | `OPA_BINARY` | `opa` (on `PATH`) | Path to the `opa` CLI, used by `rego_*` tools. |
254
+ | `REGAL_BINARY` | `regal` (on `PATH`) | Path to the `regal` linter. Required by `rego_lint`, `rego_fix`, and `rego_security_audit`. |
255
+ | `CONFTEST_BINARY` | `conftest` (on `PATH`) | Path to the `conftest` binary. Only required by `conftest_*` tools. Returns `CONFTEST_NOT_FOUND` if absent. |
256
+ | `OPA_MCP_ALLOWED_PATHS` | _(unset)_ | Comma- or semicolon-separated list of directories the server is allowed to read policies from. **When unset, file-based tools refuse to read from disk.** |
257
+ | `OPA_MCP_LOG_FILE` | `<tmpdir>/orygn-opa-mcp.log` | Path the server appends logs to. The server never writes to stdout; that channel is reserved for the MCP protocol. |
258
+ | `OPA_MCP_LOG_LEVEL` | `info` | One of `debug`, `info`, `warn`, `error`. |
259
+ | `OPA_MCP_MAX_RESPONSE_BYTES` | `100000` | Hard cap on a single tool response. Larger payloads are truncated with a `__truncated: true` marker. |
260
+ | `OPA_MCP_TIMEOUT_MS` | `30000` | Hard timeout for any spawned subprocess (`opa`, `regal`). After this, the child gets `SIGTERM` and then `SIGKILL`. |
261
+ | `OPA_MCP_HTTP_TIMEOUT_MS` | `15000` | Timeout for HTTP requests to the OPA REST API. |
262
+ | `OPA_MCP_NO_TELEMETRY` | _(unset)_ | Set to `1` to disable the anonymous startup ping. The ping sends the server version, OS platform, and a random install ID. The install ID is stored at `~/.orygn/opa-mcp/install-id` and is generated once on first run. No policy content or file paths are ever sent. |
263
+ | `OPA_MCP_MAX_SUBPROCESS_BYTES` | `33554432` (32 MiB) | Maximum bytes captured from a subprocess's stdout and stderr, counted separately. On overflow the stream is clamped, the child is stopped, and the tool returns `OUTPUT_TOO_LARGE`. Distinct from `OPA_MCP_MAX_RESPONSE_BYTES`, which trims the reply after the output is already in memory. |
264
+ | `OPA_MCP_PASSTHROUGH_ENV` | _(unset)_ | Comma-separated variable names to pass through to `opa`, `regal` and `conftest`. Everything else is withheld. **Anything named here is readable by any policy the server evaluates**, via `opa.runtime().env`, so use it only for values that are safe in that position. |
252
265
 
253
266
  Paths in `OPA_MCP_ALLOWED_PATHS` and the `*_BINARY` variables must be
254
267
  absolute. Relative paths and missing binaries are rejected with structured
@@ -269,7 +282,8 @@ Stable error codes: `INVALID_INPUT`, `INVALID_REGO`, `INVALID_BUNDLE`,
269
282
  `OPA_AUTH_FAILED`, `POLICY_NOT_FOUND`, `DATA_NOT_FOUND`, `PATH_NOT_ALLOWED`,
270
283
  `PATH_NOT_FOUND`, `DEPENDENCY_CONFLICT`, `NO_TESTS_FOUND`,
271
284
  `COVERAGE_BELOW_THRESHOLD`, `OPA_VERSION_UNSUPPORTED`, `VERIFY_INCONCLUSIVE`,
272
- `Z3_INIT_ERROR`, `GITHUB_TOKEN_MISSING`, `GIST_CREATE_FAILED`, `TIMEOUT`,
285
+ `Z3_INIT_ERROR`, `GITHUB_TOKEN_MISSING`, `GIST_CREATE_FAILED`,
286
+ `OUTPUT_TOO_LARGE`, `TIMEOUT`,
273
287
  `CANCELLED`, `UNKNOWN_ERROR`.
274
288
 
275
289
  ### Category A: Authoring & static analysis
@@ -295,7 +309,7 @@ Operate on Rego source code without needing a running OPA server. Wrap
295
309
  ```jsonc
296
310
  // Input
297
311
  {
298
- "source": "package x\nallow{input.user==\"admin\"}"
312
+ "source": "package x\nallow if input.user==\"admin\""
299
313
  }
300
314
 
301
315
  // Output (ok)
@@ -441,7 +455,7 @@ YAML/JSON/HCL/TOML/INI against Rego policies using
441
455
  // Input
442
456
  {
443
457
  "inlineConfig": "apiVersion: v1\nkind: Pod\nspec:\n containers:\n - name: app\n image: nginx:latest",
444
- "inlinePolicy": "package main\ndeny[msg] { input.spec.containers[_].image == \"nginx:latest\"; msg := \"pin your image tag\" }"
458
+ "inlinePolicy": "package main\ndeny contains msg if { input.spec.containers[_].image == \"nginx:latest\"; msg := \"pin your image tag\" }"
445
459
  }
446
460
 
447
461
  // Output
@@ -567,18 +581,28 @@ Agent: Done. Policy `rbac` is live on staging at $OPA_URL.
567
581
  └───────────────────────────────────────────────────────────────────────────────────────┘
568
582
  ```
569
583
 
570
- Three things worth knowing if you're going to operate this:
584
+ Four things worth knowing if you're going to operate this:
571
585
 
572
586
  1. **stdout is the protocol channel.** The server logs to a file via
573
587
  `lib/logger.ts` and never writes to stdout. If you see stray stdout
574
588
  bytes, the client disconnects; the MCP transport layer is strict.
575
589
  2. **No tool throws.** Every tool catches its own exceptions and returns
576
590
  a structured `{ ok: false, error: ... }` envelope. The agent sees a
577
- stable error vocabulary, not a stack trace.
578
- 3. **Subprocesses are tightly bounded.** `lib/subprocess.ts` runs `opa`
579
- and `regal` with `shell: false`, a hard timeout, and `SIGTERM`-then-
580
- `SIGKILL` escalation. There is no path through the server where an
581
- agent can construct a shell command.
591
+ stable error vocabulary, not a stack trace. Decoding subprocess output
592
+ happens inside an async callback, where a throw would bypass those
593
+ handlers entirely, so that path is bounded by bytes rather than left to
594
+ a `try`/`catch` that could not see it.
595
+ 3. **Subprocesses are bounded in time, size, and environment.**
596
+ `lib/subprocess.ts` runs the binaries with `shell: false`, a hard
597
+ timeout with `SIGTERM`-then-`SIGKILL` escalation, and a per-stream byte
598
+ cap. There is no path through the server where an agent can construct a
599
+ shell command. The timeout alone is not enough: `opa` buffers a result
600
+ in memory and writes it in one burst at exit, so a command that finishes
601
+ well inside the timeout can still deliver hundreds of megabytes.
602
+ 4. **Children do not inherit the server's environment.** `lib/child-env.ts`
603
+ builds an explicit allow-list instead. Rego can read its interpreter's
604
+ environment through `opa.runtime().env`, so anything passed down is
605
+ readable by any policy the server evaluates.
582
606
 
583
607
  ## Security
584
608
 
@@ -588,8 +612,18 @@ be exposed on the network.
588
612
 
589
613
  - File-based tools refuse to read anything outside `OPA_MCP_ALLOWED_PATHS`.
590
614
  When that variable is unset, file tools return `PATH_NOT_ALLOWED`.
591
- - Subprocesses run with `shell: false` and a hard timeout.
592
- - `OPA_TOKEN` is never echoed in tool responses or log entries.
615
+ - Subprocesses run with `shell: false`, a hard timeout, and a byte cap on
616
+ captured output.
617
+ - **Evaluated policy cannot read the server's environment.** Rego exposes the
618
+ environment of the `opa` process through `opa.runtime().env`, so a child that
619
+ inherited `process.env` would hand `OPA_TOKEN`, `GITHUB_TOKEN`, and every
620
+ other variable to any policy it evaluated. Since `rego_eval` accepts inline
621
+ source, no filesystem access is needed to reach that, which puts it one
622
+ prompt injection away from any untrusted Rego an agent reads. Children get an
623
+ explicit allow-list instead (`lib/child-env.ts`), containing no secret.
624
+ `OPA_MCP_PASSTHROUGH_ENV` opts individual variables back in.
625
+ - `OPA_TOKEN` is never echoed in tool responses or log entries, and is not
626
+ passed to any child process.
593
627
  - Releases are published with
594
628
  [npm provenance](https://docs.npmjs.com/generating-provenance-statements);
595
629
  the Docker image is built reproducibly from the committed `Dockerfile`.
@@ -634,8 +668,9 @@ is not enough -- the spawned server gets a reduced `PATH`. The
634
668
  [`examples/`](./examples) configs already include these env vars; just
635
669
  edit the placeholder paths.
636
670
 
637
- This issue does **not** affect the Docker or MCPB install paths. Those
638
- bundle `opa` and `regal` and bypass `PATH` entirely.
671
+ This issue does **not** affect the Docker install path, which bundles
672
+ `opa` and `regal` and bypasses `PATH` entirely. The MCPB bundle resolves
673
+ `opa` from `OPA_BINARY` or `PATH`, so it can hit this.
639
674
 
640
675
  **The server starts, then the client says "disconnected."**
641
676
 
@@ -716,7 +751,7 @@ Breaking changes will be:
716
751
 
717
752
  Pinned versions of the upstream toolchain (`opa` and `regal`) are treated
718
753
  as part of the build, not as a dependency the operator manages. The
719
- Dockerfile, MCPB bundle, and CI all use the same pin; bumps go through
754
+ Dockerfile and CI use the same pin; bumps go through
720
755
  Dependabot or a manual PR.
721
756
 
722
757
  ## License
package/dist/config.d.ts CHANGED
@@ -31,6 +31,16 @@ declare const ConfigSchema: z.ZodObject<{
31
31
  * write to a file path the agent specifies.
32
32
  */
33
33
  maxResponseBytes: z.ZodDefault<z.ZodNumber>;
34
+ /**
35
+ * Maximum bytes captured from a subprocess's stdout and stderr, each counted
36
+ * separately. On overflow the stream is clamped and the child is killed.
37
+ *
38
+ * This is a memory bound, distinct from `maxResponseBytes`, which trims the
39
+ * response after the output has already been read into the heap. A capture
40
+ * past V8's max string length cannot be decoded at all, and the throw would
41
+ * land in an async callback where no tool can catch it.
42
+ */
43
+ maxSubprocessBytes: z.ZodDefault<z.ZodNumber>;
34
44
  }, "strip", z.ZodTypeAny, {
35
45
  opaUrl: string;
36
46
  opaBinary: string;
@@ -40,8 +50,9 @@ declare const ConfigSchema: z.ZodObject<{
40
50
  httpTimeoutMs: number;
41
51
  allowedPaths: string[];
42
52
  logFile: string;
43
- logLevel: "debug" | "info" | "warn" | "error";
53
+ logLevel: "error" | "debug" | "info" | "warn";
44
54
  maxResponseBytes: number;
55
+ maxSubprocessBytes: number;
45
56
  opaToken?: string | undefined;
46
57
  }, {
47
58
  opaUrl?: string | undefined;
@@ -53,8 +64,9 @@ declare const ConfigSchema: z.ZodObject<{
53
64
  httpTimeoutMs?: number | undefined;
54
65
  allowedPaths?: string[] | undefined;
55
66
  logFile?: string | undefined;
56
- logLevel?: "debug" | "info" | "warn" | "error" | undefined;
67
+ logLevel?: "error" | "debug" | "info" | "warn" | undefined;
57
68
  maxResponseBytes?: number | undefined;
69
+ maxSubprocessBytes?: number | undefined;
58
70
  }>;
59
71
  export type Config = z.infer<typeof ConfigSchema>;
60
72
  export declare function loadConfig(): Config;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,YAAY;IAChB,wEAAwE;;IASxE,2EAA2E;;IAG3E,2DAA2D;;IAG3D,+DAA+D;;IAG/D,qEAAqE;;IAGrE,kEAAkE;;IAGlE,mDAAmD;;IAGnD;;;;;OAKG;;IAGH,uEAAuE;;IAGvE,qCAAqC;;IAGrC;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;EAEH,CAAC;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAyBlD,wBAAgB,UAAU,IAAI,MAAM,CAqCnC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,QAAA,MAAM,YAAY;IAChB,wEAAwE;;IASxE,2EAA2E;;IAG3E,2DAA2D;;IAG3D,+DAA+D;;IAG/D,qEAAqE;;IAGrE,kEAAkE;;IAGlE,mDAAmD;;IAGnD;;;;;OAKG;;IAGH,uEAAuE;;IAGvE,qCAAqC;;IAGrC;;;;OAIG;;IAGH;;;;;;;;OAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUH,CAAC;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AA0BlD,wBAAgB,UAAU,IAAI,MAAM,CAsCnC"}
package/dist/config.js CHANGED
@@ -9,6 +9,7 @@ import { tmpdir } from 'node:os';
9
9
  import { join } from 'node:path';
10
10
  import { z } from 'zod';
11
11
  import { resolveOpaBinary } from './lib/resolve-binary.js';
12
+ import { DEFAULT_MAX_OUTPUT_BYTES } from './lib/subprocess.js';
12
13
  const ConfigSchema = z.object({
13
14
  /** Base URL of a running OPA server (used by `opa_*` runtime tools). */
14
15
  opaUrl: z
@@ -47,6 +48,21 @@ const ConfigSchema = z.object({
47
48
  * write to a file path the agent specifies.
48
49
  */
49
50
  maxResponseBytes: z.coerce.number().int().positive().default(100_000),
51
+ /**
52
+ * Maximum bytes captured from a subprocess's stdout and stderr, each counted
53
+ * separately. On overflow the stream is clamped and the child is killed.
54
+ *
55
+ * This is a memory bound, distinct from `maxResponseBytes`, which trims the
56
+ * response after the output has already been read into the heap. A capture
57
+ * past V8's max string length cannot be decoded at all, and the throw would
58
+ * land in an async callback where no tool can catch it.
59
+ */
60
+ maxSubprocessBytes: z.coerce
61
+ .number()
62
+ .int()
63
+ .positive()
64
+ .max(DEFAULT_MAX_OUTPUT_BYTES * 8, 'OPA_MCP_MAX_SUBPROCESS_BYTES is too large to decode safely; keep it well under 512 MiB.')
65
+ .default(DEFAULT_MAX_OUTPUT_BYTES),
50
66
  });
51
67
  function parseAllowedPaths(raw) {
52
68
  if (!raw)
@@ -69,6 +85,7 @@ const ENV_VAR_NAMES = {
69
85
  logFile: 'OPA_MCP_LOG_FILE',
70
86
  logLevel: 'OPA_MCP_LOG_LEVEL',
71
87
  maxResponseBytes: 'OPA_MCP_MAX_RESPONSE_BYTES',
88
+ maxSubprocessBytes: 'OPA_MCP_MAX_SUBPROCESS_BYTES',
72
89
  };
73
90
  export function loadConfig() {
74
91
  const allowedPaths = parseAllowedPaths(process.env['OPA_MCP_ALLOWED_PATHS']);
@@ -84,6 +101,7 @@ export function loadConfig() {
84
101
  logFile: process.env['OPA_MCP_LOG_FILE'],
85
102
  logLevel: process.env['OPA_MCP_LOG_LEVEL'],
86
103
  maxResponseBytes: process.env['OPA_MCP_MAX_RESPONSE_BYTES'],
104
+ maxSubprocessBytes: process.env['OPA_MCP_MAX_SUBPROCESS_BYTES'],
87
105
  });
88
106
  if (!parsed.success) {
89
107
  console.error('opa-mcp: invalid configuration');
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -4,5 +4,5 @@
4
4
  * (server.ts -> tools -> meta -> server.ts).
5
5
  */
6
6
  export declare const SERVER_NAME = "orygn-opa-mcp";
7
- export declare const SERVER_VERSION = "0.3.0";
7
+ export declare const SERVER_VERSION = "0.4.0";
8
8
  //# sourceMappingURL=constants.d.ts.map
package/dist/constants.js CHANGED
@@ -4,5 +4,5 @@
4
4
  * (server.ts -> tools -> meta -> server.ts).
5
5
  */
6
6
  export const SERVER_NAME = 'orygn-opa-mcp';
7
- export const SERVER_VERSION = '0.3.0';
7
+ export const SERVER_VERSION = '0.4.0';
8
8
  //# sourceMappingURL=constants.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Builds the environment handed to `opa`, `regal` and `conftest`.
3
+ *
4
+ * Rego can read its interpreter's environment: `opa.runtime().env` returns the
5
+ * whole thing. So every variable this server passes down is readable by any
6
+ * policy it evaluates, and `rego_eval` takes inline source -- no filesystem
7
+ * access, so `OPA_MCP_ALLOWED_PATHS` never applies. Untrusted Rego reaches an
8
+ * agent through a README, an issue, or a pull request diff, which makes a
9
+ * pass-through of `process.env` a prompt injection away from handing over
10
+ * `OPA_TOKEN`, the `GITHUB_TOKEN` this project's own README asks users to put in
11
+ * their client config, and whatever else the operator's shell happens to hold.
12
+ *
13
+ * So the child gets an explicit allow-list instead. Nothing on it is a secret.
14
+ * Measured against the bundled OPA 1.19.0, `version`, `eval`, `check`, `test`
15
+ * and `build` all succeed with a completely empty environment, so the entries
16
+ * below exist for correctness in real-world setups (proxies, custom CA bundles,
17
+ * tool config discovery), not to make the binaries run.
18
+ *
19
+ * Operators who genuinely need a variable in policy can name it in
20
+ * `OPA_MCP_PASSTHROUGH_ENV`, which is opt-in precisely because it undoes this.
21
+ */
22
+ /**
23
+ * Build the child environment: the allow-list, plus any opted-in passthrough,
24
+ * plus `extra` from the caller (which wins, since it is explicit intent).
25
+ *
26
+ * @param extra Variables the calling command needs. Readable by evaluated
27
+ * policy like everything else here, so pass secrets only when the
28
+ * command actually requires them.
29
+ * @param source Environment to read from. Injectable for testing.
30
+ */
31
+ export declare function buildChildEnv(extra?: Record<string, string>, source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
32
+ /** Exposed for tests and for documenting the surface. */
33
+ export declare const ALLOWED_CHILD_ENV_VARS: readonly string[];
34
+ //# sourceMappingURL=child-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child-env.d.ts","sourceRoot":"","sources":["../../src/lib/child-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA0GH;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,GAAE,MAAM,CAAC,UAAwB,GACtC,MAAM,CAAC,UAAU,CAanB;AAED,yDAAyD;AACzD,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAAY,CAAC"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Builds the environment handed to `opa`, `regal` and `conftest`.
3
+ *
4
+ * Rego can read its interpreter's environment: `opa.runtime().env` returns the
5
+ * whole thing. So every variable this server passes down is readable by any
6
+ * policy it evaluates, and `rego_eval` takes inline source -- no filesystem
7
+ * access, so `OPA_MCP_ALLOWED_PATHS` never applies. Untrusted Rego reaches an
8
+ * agent through a README, an issue, or a pull request diff, which makes a
9
+ * pass-through of `process.env` a prompt injection away from handing over
10
+ * `OPA_TOKEN`, the `GITHUB_TOKEN` this project's own README asks users to put in
11
+ * their client config, and whatever else the operator's shell happens to hold.
12
+ *
13
+ * So the child gets an explicit allow-list instead. Nothing on it is a secret.
14
+ * Measured against the bundled OPA 1.19.0, `version`, `eval`, `check`, `test`
15
+ * and `build` all succeed with a completely empty environment, so the entries
16
+ * below exist for correctness in real-world setups (proxies, custom CA bundles,
17
+ * tool config discovery), not to make the binaries run.
18
+ *
19
+ * Operators who genuinely need a variable in policy can name it in
20
+ * `OPA_MCP_PASSTHROUGH_ENV`, which is opt-in precisely because it undoes this.
21
+ */
22
+ /** Vars every platform benefits from. Locale keeps output formatting stable. */
23
+ const COMMON = ['PATH', 'LANG', 'LC_ALL', 'LC_CTYPE', 'LC_NUMERIC', 'TZ'];
24
+ /** POSIX home and temp. Regal and Conftest look under HOME for config. */
25
+ const POSIX = ['HOME', 'TMPDIR', 'SHELL', 'USER', 'LOGNAME'];
26
+ /**
27
+ * Windows equivalents. A Go binary starts without these, but process creation
28
+ * and temp-file handling are better behaved with them present.
29
+ */
30
+ const WINDOWS = [
31
+ 'PATHEXT',
32
+ 'SystemRoot',
33
+ 'SystemDrive',
34
+ 'COMSPEC',
35
+ 'windir',
36
+ 'TEMP',
37
+ 'TMP',
38
+ 'USERPROFILE',
39
+ 'HOMEDRIVE',
40
+ 'HOMEPATH',
41
+ 'APPDATA',
42
+ 'LOCALAPPDATA',
43
+ 'PROCESSOR_ARCHITECTURE',
44
+ 'NUMBER_OF_PROCESSORS',
45
+ ];
46
+ /**
47
+ * Proxy and TLS trust. Without these, `http.send`, bundle downloads and remote
48
+ * schema fetches fail behind a corporate proxy or a custom CA.
49
+ *
50
+ * A proxy URL can embed credentials, so these are the least inert entries here.
51
+ * They are included because breaking every corporate user is the worse trade,
52
+ * and they are the operator's own infrastructure credentials rather than the
53
+ * cloud and repository tokens that motivated this module.
54
+ */
55
+ const NETWORK = [
56
+ 'HTTP_PROXY',
57
+ 'HTTPS_PROXY',
58
+ 'NO_PROXY',
59
+ 'ALL_PROXY',
60
+ 'http_proxy',
61
+ 'https_proxy',
62
+ 'no_proxy',
63
+ 'all_proxy',
64
+ 'SSL_CERT_FILE',
65
+ 'SSL_CERT_DIR',
66
+ ];
67
+ /**
68
+ * Where the tools look for their own config and credentials. These name file
69
+ * locations; they are not themselves secrets. Conftest reads registry auth from
70
+ * the Docker config file, so `conftest_pull` and `conftest_push` need these.
71
+ */
72
+ const TOOL_CONFIG = [
73
+ 'XDG_CONFIG_HOME',
74
+ 'XDG_CACHE_HOME',
75
+ 'XDG_DATA_HOME',
76
+ 'DOCKER_CONFIG',
77
+ 'REGISTRY_AUTH_FILE',
78
+ ];
79
+ const ALLOWED = [...COMMON, ...POSIX, ...WINDOWS, ...NETWORK, ...TOOL_CONFIG];
80
+ /** Names an operator opted into via OPA_MCP_PASSTHROUGH_ENV. */
81
+ function passthroughNames(source) {
82
+ const raw = source['OPA_MCP_PASSTHROUGH_ENV'];
83
+ if (!raw)
84
+ return [];
85
+ return raw
86
+ .split(/[;,]/)
87
+ .map((s) => s.trim())
88
+ .filter((s) => s.length > 0);
89
+ }
90
+ /**
91
+ * Windows treats variable names case-insensitively, and the casing in
92
+ * `process.env` follows whatever the parent used, so an exact-key lookup would
93
+ * silently miss `Path` where the list says `PATH`.
94
+ */
95
+ function lookup(source, name) {
96
+ const direct = source[name];
97
+ if (direct !== undefined)
98
+ return direct;
99
+ if (process.platform !== 'win32')
100
+ return undefined;
101
+ const wanted = name.toLowerCase();
102
+ for (const key of Object.keys(source)) {
103
+ if (key.toLowerCase() === wanted)
104
+ return source[key];
105
+ }
106
+ return undefined;
107
+ }
108
+ /**
109
+ * Windows-only identity variables that libuv copies from the parent no matter
110
+ * what environment is requested -- spawning with `{}` still delivers them, and
111
+ * measurably so. They are not secrets, but they name the operating-system user,
112
+ * the Windows domain and the domain controller, which is more than an untrusted
113
+ * policy needs to know about the machine evaluating it.
114
+ *
115
+ * They cannot be removed, only overwritten, so they are blanked. None of the
116
+ * three binaries reads them: every representative `opa` command succeeds with a
117
+ * completely empty environment on both platforms.
118
+ */
119
+ const WINDOWS_IDENTITY_TO_BLANK = ['USERNAME', 'USERDOMAIN', 'LOGONSERVER'];
120
+ /**
121
+ * Build the child environment: the allow-list, plus any opted-in passthrough,
122
+ * plus `extra` from the caller (which wins, since it is explicit intent).
123
+ *
124
+ * @param extra Variables the calling command needs. Readable by evaluated
125
+ * policy like everything else here, so pass secrets only when the
126
+ * command actually requires them.
127
+ * @param source Environment to read from. Injectable for testing.
128
+ */
129
+ export function buildChildEnv(extra, source = process.env) {
130
+ const env = {};
131
+ if (process.platform === 'win32') {
132
+ for (const name of WINDOWS_IDENTITY_TO_BLANK)
133
+ env[name] = '';
134
+ }
135
+ for (const name of [...ALLOWED, ...passthroughNames(source)]) {
136
+ const value = lookup(source, name);
137
+ if (value !== undefined)
138
+ env[name] = value;
139
+ }
140
+ return extra ? { ...env, ...extra } : env;
141
+ }
142
+ /** Exposed for tests and for documenting the surface. */
143
+ export const ALLOWED_CHILD_ENV_VARS = ALLOWED;
144
+ //# sourceMappingURL=child-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child-env.js","sourceRoot":"","sources":["../../src/lib/child-env.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"conftest-cli.d.ts","sourceRoot":"","sources":["../../src/lib/conftest-cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"conftest-cli.d.ts","sourceRoot":"","sources":["../../src/lib/conftest-cli.ts"],"names":[],"mappings":"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"}
@@ -151,6 +151,7 @@ export class ConftestCli {
151
151
  const opts = {
152
152
  args,
153
153
  timeoutMs: this.config.subprocessTimeoutMs,
154
+ maxOutputBytes: this.config.maxSubprocessBytes,
154
155
  };
155
156
  if (signal !== undefined)
156
157
  opts.signal = signal;