@orygn/opa-mcp 0.4.0 → 0.5.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.
Files changed (146) hide show
  1. package/CHANGELOG.md +339 -3
  2. package/README.md +71 -72
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +8 -3
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +57 -15
  9. package/dist/config.js.map +1 -1
  10. package/dist/lib/bundle-signatures.d.ts +23 -0
  11. package/dist/lib/bundle-signatures.d.ts.map +1 -0
  12. package/dist/lib/bundle-signatures.js +152 -0
  13. package/dist/lib/bundle-signatures.js.map +1 -0
  14. package/dist/lib/child-env.d.ts +9 -2
  15. package/dist/lib/child-env.d.ts.map +1 -1
  16. package/dist/lib/child-env.js +38 -6
  17. package/dist/lib/child-env.js.map +1 -1
  18. package/dist/lib/conftest-cli.d.ts +25 -10
  19. package/dist/lib/conftest-cli.d.ts.map +1 -1
  20. package/dist/lib/conftest-cli.js +152 -33
  21. package/dist/lib/conftest-cli.js.map +1 -1
  22. package/dist/lib/install-id.d.ts.map +1 -1
  23. package/dist/lib/install-id.js +18 -3
  24. package/dist/lib/install-id.js.map +1 -1
  25. package/dist/lib/json-stream.d.ts +26 -0
  26. package/dist/lib/json-stream.d.ts.map +1 -0
  27. package/dist/lib/json-stream.js +69 -0
  28. package/dist/lib/json-stream.js.map +1 -0
  29. package/dist/lib/opa-cli.d.ts +64 -16
  30. package/dist/lib/opa-cli.d.ts.map +1 -1
  31. package/dist/lib/opa-cli.js +98 -28
  32. package/dist/lib/opa-cli.js.map +1 -1
  33. package/dist/lib/opa-paths.d.ts +24 -0
  34. package/dist/lib/opa-paths.d.ts.map +1 -0
  35. package/dist/lib/opa-paths.js +97 -0
  36. package/dist/lib/opa-paths.js.map +1 -0
  37. package/dist/lib/regal-cli.d.ts +16 -1
  38. package/dist/lib/regal-cli.d.ts.map +1 -1
  39. package/dist/lib/regal-cli.js +40 -6
  40. package/dist/lib/regal-cli.js.map +1 -1
  41. package/dist/lib/rego-ast-walker.d.ts.map +1 -1
  42. package/dist/lib/rego-ast-walker.js +141 -17
  43. package/dist/lib/rego-ast-walker.js.map +1 -1
  44. package/dist/lib/rego-counterexample.d.ts +1 -1
  45. package/dist/lib/rego-counterexample.d.ts.map +1 -1
  46. package/dist/lib/rego-counterexample.js +23 -5
  47. package/dist/lib/rego-counterexample.js.map +1 -1
  48. package/dist/lib/rego-ir.d.ts +32 -2
  49. package/dist/lib/rego-ir.d.ts.map +1 -1
  50. package/dist/lib/rego-smt-encoder.d.ts +53 -5
  51. package/dist/lib/rego-smt-encoder.d.ts.map +1 -1
  52. package/dist/lib/rego-smt-encoder.js +192 -26
  53. package/dist/lib/rego-smt-encoder.js.map +1 -1
  54. package/dist/lib/rego-type-inferencer.d.ts +1 -1
  55. package/dist/lib/rego-type-inferencer.d.ts.map +1 -1
  56. package/dist/lib/rego-type-inferencer.js +3 -3
  57. package/dist/lib/rego-type-inferencer.js.map +1 -1
  58. package/dist/lib/rego-verify-engine.d.ts.map +1 -1
  59. package/dist/lib/rego-verify-engine.js +109 -77
  60. package/dist/lib/rego-verify-engine.js.map +1 -1
  61. package/dist/lib/rego-z3.d.ts +10 -0
  62. package/dist/lib/rego-z3.d.ts.map +1 -1
  63. package/dist/lib/rego-z3.js +38 -0
  64. package/dist/lib/rego-z3.js.map +1 -1
  65. package/dist/lib/security.d.ts +8 -0
  66. package/dist/lib/security.d.ts.map +1 -1
  67. package/dist/lib/security.js +130 -54
  68. package/dist/lib/security.js.map +1 -1
  69. package/dist/resources/patterns.d.ts +1 -1
  70. package/dist/resources/patterns.d.ts.map +1 -1
  71. package/dist/resources/patterns.js +40 -12
  72. package/dist/resources/patterns.js.map +1 -1
  73. package/dist/server.d.ts.map +1 -1
  74. package/dist/server.js +10 -2
  75. package/dist/server.js.map +1 -1
  76. package/dist/tools/bundles/build.d.ts.map +1 -1
  77. package/dist/tools/bundles/build.js +12 -4
  78. package/dist/tools/bundles/build.js.map +1 -1
  79. package/dist/tools/bundles/sign.d.ts +9 -2
  80. package/dist/tools/bundles/sign.d.ts.map +1 -1
  81. package/dist/tools/bundles/sign.js +145 -19
  82. package/dist/tools/bundles/sign.js.map +1 -1
  83. package/dist/tools/bundles/verify.d.ts +1 -1
  84. package/dist/tools/bundles/verify.d.ts.map +1 -1
  85. package/dist/tools/bundles/verify.js +58 -19
  86. package/dist/tools/bundles/verify.js.map +1 -1
  87. package/dist/tools/conftest/pull.d.ts.map +1 -1
  88. package/dist/tools/conftest/pull.js +24 -10
  89. package/dist/tools/conftest/pull.js.map +1 -1
  90. package/dist/tools/conftest/push.d.ts.map +1 -1
  91. package/dist/tools/conftest/push.js +17 -7
  92. package/dist/tools/conftest/push.js.map +1 -1
  93. package/dist/tools/conftest/test.d.ts +10 -6
  94. package/dist/tools/conftest/test.d.ts.map +1 -1
  95. package/dist/tools/conftest/test.js +47 -24
  96. package/dist/tools/conftest/test.js.map +1 -1
  97. package/dist/tools/conftest/verify.d.ts +10 -6
  98. package/dist/tools/conftest/verify.d.ts.map +1 -1
  99. package/dist/tools/conftest/verify.js +30 -18
  100. package/dist/tools/conftest/verify.js.map +1 -1
  101. package/dist/tools/evaluation/bench.d.ts +16 -0
  102. package/dist/tools/evaluation/bench.d.ts.map +1 -1
  103. package/dist/tools/evaluation/bench.js +30 -5
  104. package/dist/tools/evaluation/bench.js.map +1 -1
  105. package/dist/tools/evaluation/exec.d.ts +16 -0
  106. package/dist/tools/evaluation/exec.d.ts.map +1 -1
  107. package/dist/tools/evaluation/exec.js +35 -2
  108. package/dist/tools/evaluation/exec.js.map +1 -1
  109. package/dist/tools/evaluation/test-multiroot.d.ts +4 -0
  110. package/dist/tools/evaluation/test-multiroot.d.ts.map +1 -1
  111. package/dist/tools/evaluation/test-multiroot.js +12 -2
  112. package/dist/tools/evaluation/test-multiroot.js.map +1 -1
  113. package/dist/tools/evaluation/test.d.ts +45 -0
  114. package/dist/tools/evaluation/test.d.ts.map +1 -1
  115. package/dist/tools/evaluation/test.js +146 -27
  116. package/dist/tools/evaluation/test.js.map +1 -1
  117. package/dist/tools/helpers/coverage-gaps.d.ts +11 -3
  118. package/dist/tools/helpers/coverage-gaps.d.ts.map +1 -1
  119. package/dist/tools/helpers/coverage-gaps.js +4 -3
  120. package/dist/tools/helpers/coverage-gaps.js.map +1 -1
  121. package/dist/tools/helpers/explain-undefined.d.ts +9 -3
  122. package/dist/tools/helpers/explain-undefined.d.ts.map +1 -1
  123. package/dist/tools/helpers/explain-undefined.js +83 -51
  124. package/dist/tools/helpers/explain-undefined.js.map +1 -1
  125. package/dist/tools/helpers/verify.d.ts.map +1 -1
  126. package/dist/tools/helpers/verify.js +5 -1
  127. package/dist/tools/helpers/verify.js.map +1 -1
  128. package/dist/tools/server-management/_shared.d.ts +15 -4
  129. package/dist/tools/server-management/_shared.d.ts.map +1 -1
  130. package/dist/tools/server-management/_shared.js +73 -12
  131. package/dist/tools/server-management/_shared.js.map +1 -1
  132. package/dist/tools/server-management/data.d.ts.map +1 -1
  133. package/dist/tools/server-management/data.js +88 -23
  134. package/dist/tools/server-management/data.js.map +1 -1
  135. package/dist/tools/server-management/decisions.d.ts.map +1 -1
  136. package/dist/tools/server-management/decisions.js +16 -3
  137. package/dist/tools/server-management/decisions.js.map +1 -1
  138. package/dist/tools/server-management/policies.d.ts.map +1 -1
  139. package/dist/tools/server-management/policies.js +44 -7
  140. package/dist/tools/server-management/policies.js.map +1 -1
  141. package/dist/tools/server-management/status.d.ts.map +1 -1
  142. package/dist/tools/server-management/status.js +80 -13
  143. package/dist/tools/server-management/status.js.map +1 -1
  144. package/dist/types.d.ts +9 -0
  145. package/dist/types.d.ts.map +1 -1
  146. package/package.json +2 -1
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Helpers for OPA bundle signatures.
3
+ *
4
+ * `opa sign` writes `{ "signatures": ["<JWT>"] }` with exactly one JWT. Its
5
+ * header carries the algorithm and its payload lists every signed file as
6
+ * `{ name, hash, algorithm }`, plus whatever extra claims (`keyid`, `scope`)
7
+ * were supplied through a claims file. Reading that summary back is how the
8
+ * sign tool describes what it just produced, and it is deliberately not a
9
+ * verification: nothing here checks the signature, only OPA does that.
10
+ */
11
+ import { readFile } from 'node:fs/promises';
12
+ function base64UrlDecode(segment) {
13
+ const b64 = segment.replace(/-/g, '+').replace(/_/g, '/');
14
+ const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
15
+ return Buffer.from(padded, 'base64').toString('utf8');
16
+ }
17
+ /**
18
+ * Read the summary of a `.signatures.json` file. Throws on any shape OPA
19
+ * would not have written, so a caller never reports a file it cannot read.
20
+ */
21
+ export async function readSignaturesSummary(path) {
22
+ const raw = await readFile(path, 'utf8');
23
+ const parsed = JSON.parse(raw);
24
+ const sigs = parsed.signatures;
25
+ if (!Array.isArray(sigs) || sigs.length !== 1 || typeof sigs[0] !== 'string') {
26
+ throw new Error('expected exactly one JWT in "signatures"');
27
+ }
28
+ const parts = sigs[0].split('.');
29
+ if (parts.length !== 3)
30
+ throw new Error('signature is not a JWT');
31
+ const header = JSON.parse(base64UrlDecode(parts[0]));
32
+ const payload = JSON.parse(base64UrlDecode(parts[1]));
33
+ if (typeof header.alg !== 'string')
34
+ throw new Error('JWT header has no alg');
35
+ if (!Array.isArray(payload.files))
36
+ throw new Error('JWT payload has no files array');
37
+ const summary = {
38
+ algorithm: header.alg,
39
+ filesSigned: payload.files.length,
40
+ };
41
+ if (typeof payload.keyid === 'string')
42
+ summary.keyId = payload.keyid;
43
+ if (typeof payload.scope === 'string')
44
+ summary.scope = payload.scope;
45
+ return summary;
46
+ }
47
+ /**
48
+ * `opa sign` and `opa build` both print these when the key file is not a PEM
49
+ * key or the algorithm is not one OPA knows. They are the caller's input, not
50
+ * the bundle, so the tools report them as INVALID_INPUT.
51
+ */
52
+ const KEY_OR_ALGORITHM_ERROR = /failed to parse PEM block|unknown signature algorithm/i;
53
+ export function isKeyOrAlgorithmError(output) {
54
+ return KEY_OR_ALGORITHM_ERROR.test(output);
55
+ }
56
+ /**
57
+ * Each pattern is text OPA 1.19 prints, on stdout, for that failure.
58
+ *
59
+ * The order follows what OPA does while loading a signed bundle: it reads the
60
+ * key and algorithm, checks the JWT against the key, compares the scope claim,
61
+ * then reads every file. A Rego file is hashed as written, so its digest is
62
+ * compared before it is parsed. A data file or `.manifest` is hashed by parsed
63
+ * value, so one that no longer parses fails BEFORE its digest is compared;
64
+ * that is why an unparseable data file is `file_unparseable` (possibly
65
+ * modified) and not a load error of signed content. Rego parse errors come
66
+ * last, after the signature and every digest passed. Where messages overlap,
67
+ * the more specific rule is listed first.
68
+ */
69
+ const FAILURE_RULES = [
70
+ {
71
+ reason: 'key_invalid',
72
+ pattern: KEY_OR_ALGORITHM_ERROR,
73
+ summary: 'the verification key or algorithm could not be used',
74
+ hint: 'verificationKey must be a PEM public key, and signingAlg one OPA supports. For an HMAC-signed bundle pass signingAlg (HS256, HS384 or HS512) so the file is read as the secret rather than as PEM.',
75
+ },
76
+ {
77
+ reason: 'not_a_bundle',
78
+ pattern: /archive read failed|bundle read failed/i,
79
+ summary: 'the path is not a bundle directory or a .tar.gz archive',
80
+ },
81
+ {
82
+ reason: 'signature_invalid',
83
+ pattern: /failed to verify JWT signature|verification error/i,
84
+ summary: 'the signature does not verify with this key',
85
+ hint: 'Check the key and pass signingAlg matching how the bundle was signed. OPA verifies against the single key given regardless of the signature keyid claim, so this is the key or the algorithm, not the key id.',
86
+ },
87
+ {
88
+ reason: 'scope_mismatch',
89
+ pattern: /scope mismatch/i,
90
+ summary: 'the scope given does not match the scope claim in the signature',
91
+ hint: 'Pass scope with exactly the value the bundle was signed with, and no scope if it was signed without one.',
92
+ },
93
+ {
94
+ reason: 'file_modified',
95
+ pattern: /digest mismatch/i,
96
+ summary: 'a signed file was modified after signing',
97
+ },
98
+ {
99
+ reason: 'file_missing',
100
+ pattern: /specified in bundle signatures but not found/i,
101
+ summary: 'a file the signature covers is missing from the bundle',
102
+ },
103
+ {
104
+ reason: 'file_added',
105
+ pattern: /not included in bundle signature/i,
106
+ summary: 'the bundle contains a file the signature does not cover',
107
+ hint: 'A directory signature names files under the directory name given to opa, so this is also what a directory reports when verified under a different name than it was signed with. Moving it elsewhere under the same name is fine; renaming it is not. For an artifact that survives renaming, build a signed archive with opa_bundle_build.',
108
+ },
109
+ {
110
+ reason: 'unsigned',
111
+ pattern: /missing \.signatures\.json/i,
112
+ summary: 'the bundle has no .signatures.json',
113
+ },
114
+ {
115
+ reason: 'signatures_malformed',
116
+ pattern: /signatures decode|missing JWT|base64 decode JWT|split compact JWT|unexpected end of JSON input|\.signatures\.json/i,
117
+ summary: '.signatures.json is not a valid signature file',
118
+ },
119
+ {
120
+ reason: 'file_unparseable',
121
+ pattern: /yaml:/i,
122
+ summary: 'a data file or .manifest could not be parsed, so its digest was never compared',
123
+ hint: 'OPA hashes data files by parsed value and stops at the first one it cannot parse, before comparing digests. The file may have been modified after signing. Restore it, or fix it and sign again.',
124
+ },
125
+ {
126
+ reason: 'bundle_load_error',
127
+ pattern: /rego_\w+_error|errors? occurred|manifest roots/i,
128
+ summary: 'the bundle could not be loaded, so verification did not complete',
129
+ hint: 'OPA parses Rego after the signature and every digest passed, so the policy is signed and unmodified but does not load under Rego v1. A policy written for Rego v0 verifies with v0Compatible.',
130
+ },
131
+ {
132
+ // A JWT payload that decodes but is not JSON: OPA prints only the JSON
133
+ // error, with none of the prefixes matched above.
134
+ reason: 'signatures_malformed',
135
+ pattern: /invalid character/i,
136
+ summary: '.signatures.json is not a valid signature file',
137
+ },
138
+ ];
139
+ /** Classify OPA's stdout+stderr from a failed verification. */
140
+ export function classifyVerificationFailure(output) {
141
+ for (const rule of FAILURE_RULES) {
142
+ if (rule.pattern.test(output)) {
143
+ const { pattern: _pattern, ...failure } = rule;
144
+ return failure;
145
+ }
146
+ }
147
+ return {
148
+ reason: 'unknown',
149
+ summary: 'OPA rejected the bundle for a reason this tool does not recognise',
150
+ };
151
+ }
152
+ //# sourceMappingURL=bundle-signatures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-signatures.js","sourceRoot":"","sources":["../../src/lib/bundle-signatures.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,13 @@
10
10
  * `OPA_TOKEN`, the `GITHUB_TOKEN` this project's own README asks users to put in
11
11
  * their client config, and whatever else the operator's shell happens to hold.
12
12
  *
13
- * So the child gets an explicit allow-list instead. Nothing on it is a secret.
13
+ * So the child gets an explicit allow-list instead. It holds no cloud or
14
+ * repository token, which is what motivated the list, but it is not free of
15
+ * credentials: a proxy URL can embed one, and `HTTP_PROXY` and its siblings are
16
+ * on the list because dropping them breaks every user behind a corporate proxy.
17
+ * An operator who would rather lose proxy support than expose those credentials
18
+ * to evaluated policy can name them in `OPA_MCP_BLOCK_ENV`.
19
+ *
14
20
  * Measured against the bundled OPA 1.19.0, `version`, `eval`, `check`, `test`
15
21
  * and `build` all succeed with a completely empty environment, so the entries
16
22
  * below exist for correctness in real-world setups (proxies, custom CA bundles,
@@ -25,7 +31,8 @@
25
31
  *
26
32
  * @param extra Variables the calling command needs. Readable by evaluated
27
33
  * policy like everything else here, so pass secrets only when the
28
- * command actually requires them.
34
+ * command actually requires them. `OPA_MCP_BLOCK_ENV` removes a
35
+ * name even when it appears here.
29
36
  * @param source Environment to read from. Injectable for testing.
30
37
  */
31
38
  export declare function buildChildEnv(extra?: Record<string, string>, source?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
@@ -1 +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"}
1
+ {"version":3,"file":"child-env.d.ts","sourceRoot":"","sources":["../../src/lib/child-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AA0HH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,GAAE,MAAM,CAAC,UAAwB,GACtC,MAAM,CAAC,UAAU,CAyBnB;AAED,yDAAyD;AACzD,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAAY,CAAC"}
@@ -10,7 +10,13 @@
10
10
  * `OPA_TOKEN`, the `GITHUB_TOKEN` this project's own README asks users to put in
11
11
  * their client config, and whatever else the operator's shell happens to hold.
12
12
  *
13
- * So the child gets an explicit allow-list instead. Nothing on it is a secret.
13
+ * So the child gets an explicit allow-list instead. It holds no cloud or
14
+ * repository token, which is what motivated the list, but it is not free of
15
+ * credentials: a proxy URL can embed one, and `HTTP_PROXY` and its siblings are
16
+ * on the list because dropping them breaks every user behind a corporate proxy.
17
+ * An operator who would rather lose proxy support than expose those credentials
18
+ * to evaluated policy can name them in `OPA_MCP_BLOCK_ENV`.
19
+ *
14
20
  * Measured against the bundled OPA 1.19.0, `version`, `eval`, `check`, `test`
15
21
  * and `build` all succeed with a completely empty environment, so the entries
16
22
  * below exist for correctness in real-world setups (proxies, custom CA bundles,
@@ -77,9 +83,8 @@ const TOOL_CONFIG = [
77
83
  'REGISTRY_AUTH_FILE',
78
84
  ];
79
85
  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'];
86
+ /** Split a comma or semicolon separated list of variable names. */
87
+ function nameList(raw) {
83
88
  if (!raw)
84
89
  return [];
85
90
  return raw
@@ -87,6 +92,21 @@ function passthroughNames(source) {
87
92
  .map((s) => s.trim())
88
93
  .filter((s) => s.length > 0);
89
94
  }
95
+ /** Names an operator opted into via OPA_MCP_PASSTHROUGH_ENV. */
96
+ function passthroughNames(source) {
97
+ return nameList(source['OPA_MCP_PASSTHROUGH_ENV']);
98
+ }
99
+ /**
100
+ * Names an operator withheld via OPA_MCP_BLOCK_ENV.
101
+ *
102
+ * Applied last, so it overrides the allow-list, the passthrough list and the
103
+ * caller's own `extra`. The point is to be able to say no to something this
104
+ * module would otherwise hand down, and a rule that could be overridden by the
105
+ * thing it is meant to restrain would not be worth having.
106
+ */
107
+ function blockedNames(source) {
108
+ return nameList(source['OPA_MCP_BLOCK_ENV']);
109
+ }
90
110
  /**
91
111
  * Windows treats variable names case-insensitively, and the casing in
92
112
  * `process.env` follows whatever the parent used, so an exact-key lookup would
@@ -123,7 +143,8 @@ const WINDOWS_IDENTITY_TO_BLANK = ['USERNAME', 'USERDOMAIN', 'LOGONSERVER'];
123
143
  *
124
144
  * @param extra Variables the calling command needs. Readable by evaluated
125
145
  * policy like everything else here, so pass secrets only when the
126
- * command actually requires them.
146
+ * command actually requires them. `OPA_MCP_BLOCK_ENV` removes a
147
+ * name even when it appears here.
127
148
  * @param source Environment to read from. Injectable for testing.
128
149
  */
129
150
  export function buildChildEnv(extra, source = process.env) {
@@ -137,7 +158,18 @@ export function buildChildEnv(extra, source = process.env) {
137
158
  if (value !== undefined)
138
159
  env[name] = value;
139
160
  }
140
- return extra ? { ...env, ...extra } : env;
161
+ const result = extra ? { ...env, ...extra } : env;
162
+ for (const name of blockedNames(source)) {
163
+ delete result[name];
164
+ if (process.platform === 'win32') {
165
+ const wanted = name.toLowerCase();
166
+ for (const key of Object.keys(result)) {
167
+ if (key.toLowerCase() === wanted)
168
+ delete result[key];
169
+ }
170
+ }
171
+ }
172
+ return result;
141
173
  }
142
174
  /** Exposed for tests and for documenting the surface. */
143
175
  export const ALLOWED_CHILD_ENV_VARS = ALLOWED;
@@ -1 +1 @@
1
- {"version":3,"file":"child-env.js","sourceRoot":"","sources":["../../src/lib/child-env.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"child-env.js","sourceRoot":"","sources":["../../src/lib/child-env.ts"],"names":[],"mappings":"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"}
@@ -20,8 +20,20 @@ export interface ConftestFileResult {
20
20
  failures: ConftestMessage[];
21
21
  warnings: ConftestMessage[];
22
22
  skipped: ConftestMessage[];
23
- exceptions: string[];
23
+ exceptions: ConftestMessage[];
24
24
  }
25
+ /**
26
+ * Parse `--output=json` from `conftest test` or `conftest verify` into
27
+ * results whose array fields are always present.
28
+ *
29
+ * conftest marks every array `omitempty`, so a file that passes cleanly
30
+ * arrives as `{filename, namespace, successes}` with no `failures` key at
31
+ * all, and `verify` with no test rules prints the literal `null`. Reading
32
+ * `.failures.length` off that threw on every clean run. Returns null when
33
+ * stdout is not a JSON array (or `null`), which is what a command-level
34
+ * failure looks like.
35
+ */
36
+ export declare function parseConftestResults(stdout: string): ConftestFileResult[] | null;
25
37
  /** Input for `conftest test`. */
26
38
  export interface ConftestTestInput {
27
39
  /** Absolute paths to configuration files to evaluate. */
@@ -30,10 +42,10 @@ export interface ConftestTestInput {
30
42
  inlineConfig?: string;
31
43
  /**
32
44
  * Parser used for inline config content. Determines the temp file
33
- * extension (yaml → .yaml, json → .json, dockerfile → Dockerfile, etc.).
45
+ * name (yaml -> config.yaml, dockerfile -> Dockerfile, and so on).
34
46
  * Defaults to `yaml` when not specified.
35
47
  */
36
- inlineConfigParser?: string;
48
+ inlineConfigParser?: ConftestParser;
37
49
  /**
38
50
  * Absolute path to a directory or file containing Rego policies.
39
51
  * Defaults to `./policy` (conftest's convention). Mutually exclusive
@@ -60,7 +72,7 @@ export interface ConftestTestInput {
60
72
  * flag, overriding extension-based detection. Use it to parse files whose
61
73
  * extension does not match their format (e.g. a `.tfstate` file as `json`).
62
74
  */
63
- parser?: string;
75
+ parser?: ConftestParser;
64
76
  }
65
77
  /** Input for `conftest verify`. */
66
78
  export interface ConftestVerifyInput {
@@ -86,12 +98,15 @@ export interface ConftestPushInput {
86
98
  policy?: string;
87
99
  }
88
100
  /**
89
- * Wrapper around the local `conftest` binary.
90
- *
91
- * Methods do not throw on conftest-side errors -- the exit code on the
92
- * returned `SpawnResult` is the signal. Inline source is written to temp
93
- * files/directories because conftest does not read from stdin.
101
+ * The parsers conftest 0.69 accepts for `--parser`, and the only values a
102
+ * caller may pass. The name also chooses the temp file name for inline
103
+ * config, so it must never be free text: a value carrying path separators
104
+ * used to be joined straight into the temp path, and `../` sequences in it
105
+ * walked out of the temp directory to wherever the caller pointed.
94
106
  */
107
+ export declare const CONFTEST_PARSERS: readonly ["cue", "dockerfile", "dotenv", "edn", "hcl1", "hcl2", "hocon", "ignore", "ini", "json", "jsonnet", "nginx", "properties", "spdx", "textproto", "toml", "vcl", "xml", "yaml"];
108
+ export type ConftestParser = (typeof CONFTEST_PARSERS)[number];
109
+ export declare function isConftestParser(value: string): value is ConftestParser;
95
110
  export declare class ConftestCli {
96
111
  private readonly config;
97
112
  constructor(config: Config);
@@ -129,7 +144,7 @@ export declare class ConftestCli {
129
144
  * Run `conftest` with the given argv. Tools should prefer the typed
130
145
  * methods above; this is the escape hatch for unusual invocations.
131
146
  */
132
- run(args: string[], signal?: AbortSignal): Promise<SpawnResult>;
147
+ run(args: string[], signal?: AbortSignal, cwd?: string): Promise<SpawnResult>;
133
148
  /**
134
149
  * Create temp assets for inline inputs, run `fn`, then clean up.
135
150
  * Returns whatever `fn` returns.
@@ -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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"}
@@ -16,28 +16,120 @@
16
16
  */
17
17
  import { mkdtemp, rm, writeFile } from 'node:fs/promises';
18
18
  import { tmpdir } from 'node:os';
19
- import { join } from 'node:path';
19
+ import { basename, dirname, join } from 'node:path';
20
20
  import { runBinary } from './subprocess.js';
21
- // ─── Parser → file extension map ─────────────────────────────────────────────
22
- const PARSER_TO_EXT = {
23
- yaml: '.yaml',
24
- json: '.json',
25
- toml: '.toml',
26
- hcl1: '.hcl',
27
- hcl2: '.hcl',
28
- ini: '.ini',
29
- xml: '.xml',
30
- dotenv: '.env',
31
- cue: '.cue',
32
- jsonnet: '.jsonnet',
33
- properties: '.properties',
34
- edn: '.edn',
35
- hocon: '.conf',
36
- // Dockerfile is a special case -- conftest detects by filename
37
- dockerfile: '',
21
+ /**
22
+ * Parse `--output=json` from `conftest test` or `conftest verify` into
23
+ * results whose array fields are always present.
24
+ *
25
+ * conftest marks every array `omitempty`, so a file that passes cleanly
26
+ * arrives as `{filename, namespace, successes}` with no `failures` key at
27
+ * all, and `verify` with no test rules prints the literal `null`. Reading
28
+ * `.failures.length` off that threw on every clean run. Returns null when
29
+ * stdout is not a JSON array (or `null`), which is what a command-level
30
+ * failure looks like.
31
+ */
32
+ export function parseConftestResults(stdout) {
33
+ const text = stdout.trim();
34
+ if (text === '')
35
+ return null;
36
+ let parsed;
37
+ try {
38
+ parsed = JSON.parse(text);
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ if (parsed === null)
44
+ return [];
45
+ if (!Array.isArray(parsed))
46
+ return null;
47
+ const messages = (v) => Array.isArray(v)
48
+ ? v.map((m) => m !== null && typeof m === 'object' ? m : { msg: String(m) })
49
+ : [];
50
+ return parsed.map((item) => {
51
+ const r = (item !== null && typeof item === 'object' ? item : {});
52
+ return {
53
+ ...r,
54
+ filename: typeof r['filename'] === 'string' ? r['filename'] : '',
55
+ namespace: typeof r['namespace'] === 'string' ? r['namespace'] : '',
56
+ successes: typeof r['successes'] === 'number' ? r['successes'] : 0,
57
+ failures: messages(r['failures']),
58
+ warnings: messages(r['warnings']),
59
+ skipped: messages(r['skipped']),
60
+ exceptions: messages(r['exceptions']),
61
+ };
62
+ });
63
+ }
64
+ // ─── Parser names ─────────────────────────────────────────────────────────────
65
+ /**
66
+ * The parsers conftest 0.69 accepts for `--parser`, and the only values a
67
+ * caller may pass. The name also chooses the temp file name for inline
68
+ * config, so it must never be free text: a value carrying path separators
69
+ * used to be joined straight into the temp path, and `../` sequences in it
70
+ * walked out of the temp directory to wherever the caller pointed.
71
+ */
72
+ export const CONFTEST_PARSERS = [
73
+ 'cue',
74
+ 'dockerfile',
75
+ 'dotenv',
76
+ 'edn',
77
+ 'hcl1',
78
+ 'hcl2',
79
+ 'hocon',
80
+ 'ignore',
81
+ 'ini',
82
+ 'json',
83
+ 'jsonnet',
84
+ 'nginx',
85
+ 'properties',
86
+ 'spdx',
87
+ 'textproto',
88
+ 'toml',
89
+ 'vcl',
90
+ 'xml',
91
+ 'yaml',
92
+ ];
93
+ export function isConftestParser(value) {
94
+ return CONFTEST_PARSERS.includes(value);
95
+ }
96
+ /**
97
+ * Temp file names for inline config. The parser is passed to conftest
98
+ * explicitly, so the name only has to be plausible for a reader of the
99
+ * output; conftest also recognises a Dockerfile by name.
100
+ */
101
+ const INLINE_CONFIG_FILENAME = {
102
+ cue: 'config.cue',
103
+ dockerfile: 'Dockerfile',
104
+ dotenv: 'config.env',
105
+ edn: 'config.edn',
106
+ hcl1: 'config.hcl',
107
+ hcl2: 'config.hcl',
108
+ hocon: 'config.conf',
109
+ ignore: '.gitignore',
110
+ ini: 'config.ini',
111
+ json: 'config.json',
112
+ jsonnet: 'config.jsonnet',
113
+ nginx: 'nginx.conf',
114
+ properties: 'config.properties',
115
+ spdx: 'config.spdx',
116
+ textproto: 'config.textproto',
117
+ toml: 'config.toml',
118
+ vcl: 'config.vcl',
119
+ xml: 'config.xml',
120
+ yaml: 'config.yaml',
38
121
  };
39
- function parserToExt(parser) {
40
- return PARSER_TO_EXT[parser.toLowerCase()] ?? `.${parser.toLowerCase()}`;
122
+ /**
123
+ * Temp file name for inline config. Throws on anything outside the closed
124
+ * set; the tool layer rejects such input first, so this is the backstop for
125
+ * any other caller.
126
+ */
127
+ function inlineConfigFilename(parser) {
128
+ const name = parser ?? 'yaml';
129
+ if (!isConftestParser(name)) {
130
+ throw new Error(`unsupported conftest parser: ${name}`);
131
+ }
132
+ return INLINE_CONFIG_FILENAME[name];
41
133
  }
42
134
  // ─── ConftestCli ──────────────────────────────────────────────────────────────
43
135
  /**
@@ -47,6 +139,28 @@ function parserToExt(parser) {
47
139
  * returned `SpawnResult` is the signal. Inline source is written to temp
48
140
  * files/directories because conftest does not read from stdin.
49
141
  */
142
+ /**
143
+ * Split a policy directory into the directory to run from and the name to pass.
144
+ *
145
+ * `conftest pull` and `conftest push` resolve `--policy` against the working
146
+ * directory rather than treating an absolute path as absolute. On Windows pull
147
+ * fails outright, reporting a path of the form `.\\C:\\...`, and push drops the
148
+ * volume and reads from the same path on whichever drive it was started on. The
149
+ * tools resolve the caller's path against the allow-list before handing it over,
150
+ * so it is always absolute and neither command ever addressed the directory
151
+ * asked for. Running from the parent and passing the final component is
152
+ * unambiguous on every platform.
153
+ */
154
+ function relativeToParent(policy) {
155
+ if (!policy)
156
+ return undefined;
157
+ const parent = dirname(policy);
158
+ const name = basename(policy);
159
+ // A root such as `C:\\` or `/` has nothing to descend into.
160
+ if (name.length === 0 || parent === policy)
161
+ return undefined;
162
+ return { parent, name };
163
+ }
50
164
  export class ConftestCli {
51
165
  config;
52
166
  constructor(config) {
@@ -100,8 +214,12 @@ export class ConftestCli {
100
214
  args.push('--combine');
101
215
  if (input.failOnWarn)
102
216
  args.push('--fail-on-warn');
103
- if (input.parser)
104
- args.push('--parser', input.parser);
217
+ // conftest's --parser overrides extension detection, so inline
218
+ // config names its parser explicitly rather than trusting the temp
219
+ // file's extension. A global `parser` wins when both are given.
220
+ const parser = input.parser ?? (configPath ? (input.inlineConfigParser ?? 'yaml') : undefined);
221
+ if (parser)
222
+ args.push('--parser', parser);
105
223
  // Config files (positional args, must come last)
106
224
  const effectiveFiles = configPath ? [configPath] : (input.files ?? []);
107
225
  args.push(...effectiveFiles);
@@ -129,9 +247,10 @@ export class ConftestCli {
129
247
  */
130
248
  async pull(input, signal) {
131
249
  const args = ['pull', input.url];
132
- if (input.policy)
133
- args.push('--policy', input.policy);
134
- return this.run(args, signal);
250
+ const target = relativeToParent(input.policy);
251
+ if (target)
252
+ args.push('--policy', target.name);
253
+ return this.run(args, signal, target?.parent);
135
254
  }
136
255
  /**
137
256
  * Push the local policy bundle to a remote OCI registry. Uses
@@ -139,15 +258,16 @@ export class ConftestCli {
139
258
  */
140
259
  async push(input, signal) {
141
260
  const args = ['push', input.repository];
142
- if (input.policy)
143
- args.push('--policy', input.policy);
144
- return this.run(args, signal);
261
+ const target = relativeToParent(input.policy);
262
+ if (target)
263
+ args.push('--policy', target.name);
264
+ return this.run(args, signal, target?.parent);
145
265
  }
146
266
  /**
147
267
  * Run `conftest` with the given argv. Tools should prefer the typed
148
268
  * methods above; this is the escape hatch for unusual invocations.
149
269
  */
150
- async run(args, signal) {
270
+ async run(args, signal, cwd) {
151
271
  const opts = {
152
272
  args,
153
273
  timeoutMs: this.config.subprocessTimeoutMs,
@@ -155,6 +275,8 @@ export class ConftestCli {
155
275
  };
156
276
  if (signal !== undefined)
157
277
  opts.signal = signal;
278
+ if (cwd !== undefined)
279
+ opts.cwd = cwd;
158
280
  return runBinary(this.config.conftestBinary, opts);
159
281
  }
160
282
  // ─── Internal: temp file / directory management ──────────────────────────
@@ -167,10 +289,7 @@ export class ConftestCli {
167
289
  const paths = {};
168
290
  try {
169
291
  if (opts.inlineConfig !== undefined) {
170
- const ext = opts.inlineConfigParser === 'dockerfile'
171
- ? ''
172
- : parserToExt(opts.inlineConfigParser ?? 'yaml');
173
- const basename = opts.inlineConfigParser === 'dockerfile' ? 'Dockerfile' : `config${ext}`;
292
+ const basename = inlineConfigFilename(opts.inlineConfigParser);
174
293
  // mkdtemp creates the directory atomically (O_CREAT|O_EXCL) -- safe temp file pattern.
175
294
  const tmpDir = await mkdtemp(join(tmpdir(), 'orygn-conftest-'));
176
295
  temps.push(tmpDir);