@orygn/opa-mcp 0.4.0 → 0.6.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 +583 -4
- package/README.md +144 -98
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -3
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +14 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +91 -19
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +1 -6
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +17 -3
- package/dist/constants.js.map +1 -1
- package/dist/lib/bundle-signatures.d.ts +23 -0
- package/dist/lib/bundle-signatures.d.ts.map +1 -0
- package/dist/lib/bundle-signatures.js +152 -0
- package/dist/lib/bundle-signatures.js.map +1 -0
- package/dist/lib/child-env.d.ts +9 -2
- package/dist/lib/child-env.d.ts.map +1 -1
- package/dist/lib/child-env.js +38 -6
- package/dist/lib/child-env.js.map +1 -1
- package/dist/lib/conftest-cli.d.ts +25 -10
- package/dist/lib/conftest-cli.d.ts.map +1 -1
- package/dist/lib/conftest-cli.js +233 -50
- package/dist/lib/conftest-cli.js.map +1 -1
- package/dist/lib/errors.d.ts +4 -5
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +15 -3
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/inline-paths.d.ts +37 -0
- package/dist/lib/inline-paths.d.ts.map +1 -0
- package/dist/lib/inline-paths.js +162 -0
- package/dist/lib/inline-paths.js.map +1 -0
- package/dist/lib/install-id.d.ts.map +1 -1
- package/dist/lib/install-id.js +18 -3
- package/dist/lib/install-id.js.map +1 -1
- package/dist/lib/json-stream.d.ts +26 -0
- package/dist/lib/json-stream.d.ts.map +1 -0
- package/dist/lib/json-stream.js +69 -0
- package/dist/lib/json-stream.js.map +1 -0
- package/dist/lib/opa-cli.d.ts +74 -16
- package/dist/lib/opa-cli.d.ts.map +1 -1
- package/dist/lib/opa-cli.js +107 -30
- package/dist/lib/opa-cli.js.map +1 -1
- package/dist/lib/opa-client.d.ts +27 -1
- package/dist/lib/opa-client.d.ts.map +1 -1
- package/dist/lib/opa-client.js +98 -14
- package/dist/lib/opa-client.js.map +1 -1
- package/dist/lib/opa-paths.d.ts +34 -0
- package/dist/lib/opa-paths.d.ts.map +1 -0
- package/dist/lib/opa-paths.js +101 -0
- package/dist/lib/opa-paths.js.map +1 -0
- package/dist/lib/output.d.ts.map +1 -1
- package/dist/lib/output.js +83 -10
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/regal-cli.d.ts +16 -1
- package/dist/lib/regal-cli.d.ts.map +1 -1
- package/dist/lib/regal-cli.js +40 -6
- package/dist/lib/regal-cli.js.map +1 -1
- package/dist/lib/rego-ast-walker.d.ts +0 -6
- package/dist/lib/rego-ast-walker.d.ts.map +1 -1
- package/dist/lib/rego-ast-walker.js +181 -55
- package/dist/lib/rego-ast-walker.js.map +1 -1
- package/dist/lib/rego-counterexample.d.ts +15 -2
- package/dist/lib/rego-counterexample.d.ts.map +1 -1
- package/dist/lib/rego-counterexample.js +63 -15
- package/dist/lib/rego-counterexample.js.map +1 -1
- package/dist/lib/rego-input-path.d.ts +20 -0
- package/dist/lib/rego-input-path.d.ts.map +1 -0
- package/dist/lib/rego-input-path.js +70 -0
- package/dist/lib/rego-input-path.js.map +1 -0
- package/dist/lib/rego-ir.d.ts +34 -3
- package/dist/lib/rego-ir.d.ts.map +1 -1
- package/dist/lib/rego-smt-encoder.d.ts +88 -5
- package/dist/lib/rego-smt-encoder.d.ts.map +1 -1
- package/dist/lib/rego-smt-encoder.js +270 -31
- package/dist/lib/rego-smt-encoder.js.map +1 -1
- package/dist/lib/rego-type-inferencer.d.ts +27 -2
- package/dist/lib/rego-type-inferencer.d.ts.map +1 -1
- package/dist/lib/rego-type-inferencer.js +71 -3
- package/dist/lib/rego-type-inferencer.js.map +1 -1
- package/dist/lib/rego-verify-engine.d.ts.map +1 -1
- package/dist/lib/rego-verify-engine.js +203 -90
- package/dist/lib/rego-verify-engine.js.map +1 -1
- package/dist/lib/rego-z3.d.ts +66 -0
- package/dist/lib/rego-z3.d.ts.map +1 -1
- package/dist/lib/rego-z3.js +312 -5
- package/dist/lib/rego-z3.js.map +1 -1
- package/dist/lib/security.d.ts +8 -0
- package/dist/lib/security.d.ts.map +1 -1
- package/dist/lib/security.js +130 -54
- package/dist/lib/security.js.map +1 -1
- package/dist/lib/subprocess.d.ts +13 -0
- package/dist/lib/subprocess.d.ts.map +1 -1
- package/dist/lib/subprocess.js +153 -18
- package/dist/lib/subprocess.js.map +1 -1
- package/dist/lib/tool-helpers.d.ts +12 -25
- package/dist/lib/tool-helpers.d.ts.map +1 -1
- package/dist/lib/tool-helpers.js +66 -40
- package/dist/lib/tool-helpers.js.map +1 -1
- package/dist/prompts/index.js +2 -2
- package/dist/resources/index.js +1 -1
- package/dist/resources/index.js.map +1 -1
- package/dist/resources/patterns.d.ts +1 -1
- package/dist/resources/patterns.d.ts.map +1 -1
- package/dist/resources/patterns.js +42 -15
- package/dist/resources/patterns.js.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +50 -7
- package/dist/server.js.map +1 -1
- package/dist/tools/authoring/capabilities.d.ts +4 -0
- package/dist/tools/authoring/capabilities.d.ts.map +1 -1
- package/dist/tools/authoring/capabilities.js +41 -4
- package/dist/tools/authoring/capabilities.js.map +1 -1
- package/dist/tools/authoring/check-schema.d.ts +6 -0
- package/dist/tools/authoring/check-schema.d.ts.map +1 -1
- package/dist/tools/authoring/check-schema.js +56 -3
- package/dist/tools/authoring/check-schema.js.map +1 -1
- package/dist/tools/authoring/format.d.ts.map +1 -1
- package/dist/tools/authoring/format.js +2 -2
- package/dist/tools/authoring/format.js.map +1 -1
- package/dist/tools/authoring/lint.d.ts.map +1 -1
- package/dist/tools/authoring/lint.js +8 -6
- package/dist/tools/authoring/lint.js.map +1 -1
- package/dist/tools/authoring/migrate-v1.d.ts.map +1 -1
- package/dist/tools/authoring/migrate-v1.js +20 -4
- package/dist/tools/authoring/migrate-v1.js.map +1 -1
- package/dist/tools/authoring/parse.d.ts.map +1 -1
- package/dist/tools/authoring/parse.js +3 -3
- package/dist/tools/authoring/parse.js.map +1 -1
- package/dist/tools/bundles/build.d.ts.map +1 -1
- package/dist/tools/bundles/build.js +12 -4
- package/dist/tools/bundles/build.js.map +1 -1
- package/dist/tools/bundles/sign.d.ts +9 -2
- package/dist/tools/bundles/sign.d.ts.map +1 -1
- package/dist/tools/bundles/sign.js +125 -19
- package/dist/tools/bundles/sign.js.map +1 -1
- package/dist/tools/bundles/verify.d.ts +1 -1
- package/dist/tools/bundles/verify.d.ts.map +1 -1
- package/dist/tools/bundles/verify.js +58 -19
- package/dist/tools/bundles/verify.js.map +1 -1
- package/dist/tools/conftest/index.d.ts +5 -0
- package/dist/tools/conftest/index.d.ts.map +1 -1
- package/dist/tools/conftest/index.js.map +1 -1
- package/dist/tools/conftest/pull.d.ts.map +1 -1
- package/dist/tools/conftest/pull.js +24 -10
- package/dist/tools/conftest/pull.js.map +1 -1
- package/dist/tools/conftest/push.d.ts.map +1 -1
- package/dist/tools/conftest/push.js +17 -7
- package/dist/tools/conftest/push.js.map +1 -1
- package/dist/tools/conftest/test.d.ts +10 -6
- package/dist/tools/conftest/test.d.ts.map +1 -1
- package/dist/tools/conftest/test.js +61 -39
- package/dist/tools/conftest/test.js.map +1 -1
- package/dist/tools/conftest/verify.d.ts +10 -6
- package/dist/tools/conftest/verify.d.ts.map +1 -1
- package/dist/tools/conftest/verify.js +40 -32
- package/dist/tools/conftest/verify.js.map +1 -1
- package/dist/tools/evaluation/_shared.js +9 -12
- package/dist/tools/evaluation/_shared.js.map +1 -1
- package/dist/tools/evaluation/bench.d.ts +29 -2
- package/dist/tools/evaluation/bench.d.ts.map +1 -1
- package/dist/tools/evaluation/bench.js +53 -9
- package/dist/tools/evaluation/bench.js.map +1 -1
- package/dist/tools/evaluation/compile.js +4 -4
- package/dist/tools/evaluation/compile.js.map +1 -1
- package/dist/tools/evaluation/eval.d.ts.map +1 -1
- package/dist/tools/evaluation/eval.js +12 -16
- package/dist/tools/evaluation/eval.js.map +1 -1
- package/dist/tools/evaluation/exec.d.ts +16 -0
- package/dist/tools/evaluation/exec.d.ts.map +1 -1
- package/dist/tools/evaluation/exec.js +38 -6
- package/dist/tools/evaluation/exec.js.map +1 -1
- package/dist/tools/evaluation/test-multiroot.d.ts +6 -0
- package/dist/tools/evaluation/test-multiroot.d.ts.map +1 -1
- package/dist/tools/evaluation/test-multiroot.js +35 -8
- package/dist/tools/evaluation/test-multiroot.js.map +1 -1
- package/dist/tools/evaluation/test.d.ts +45 -0
- package/dist/tools/evaluation/test.d.ts.map +1 -1
- package/dist/tools/evaluation/test.js +153 -32
- package/dist/tools/evaluation/test.js.map +1 -1
- package/dist/tools/helpers/coverage-gaps.d.ts +11 -3
- package/dist/tools/helpers/coverage-gaps.d.ts.map +1 -1
- package/dist/tools/helpers/coverage-gaps.js +7 -7
- package/dist/tools/helpers/coverage-gaps.js.map +1 -1
- package/dist/tools/helpers/describe-policy.d.ts.map +1 -1
- package/dist/tools/helpers/describe-policy.js +2 -2
- package/dist/tools/helpers/describe-policy.js.map +1 -1
- package/dist/tools/helpers/explain-decision.d.ts.map +1 -1
- package/dist/tools/helpers/explain-decision.js +3 -4
- package/dist/tools/helpers/explain-decision.js.map +1 -1
- package/dist/tools/helpers/explain-undefined.d.ts +9 -3
- package/dist/tools/helpers/explain-undefined.d.ts.map +1 -1
- package/dist/tools/helpers/explain-undefined.js +214 -73
- package/dist/tools/helpers/explain-undefined.js.map +1 -1
- package/dist/tools/helpers/fix.d.ts +11 -5
- package/dist/tools/helpers/fix.d.ts.map +1 -1
- package/dist/tools/helpers/fix.js +88 -45
- package/dist/tools/helpers/fix.js.map +1 -1
- package/dist/tools/helpers/generate-test-skeleton.d.ts.map +1 -1
- package/dist/tools/helpers/generate-test-skeleton.js +105 -21
- package/dist/tools/helpers/generate-test-skeleton.js.map +1 -1
- package/dist/tools/helpers/playground-share.d.ts +4 -2
- package/dist/tools/helpers/playground-share.d.ts.map +1 -1
- package/dist/tools/helpers/playground-share.js +13 -3
- package/dist/tools/helpers/playground-share.js.map +1 -1
- package/dist/tools/helpers/policy-diff.d.ts.map +1 -1
- package/dist/tools/helpers/policy-diff.js +3 -4
- package/dist/tools/helpers/policy-diff.js.map +1 -1
- package/dist/tools/helpers/security-audit.d.ts.map +1 -1
- package/dist/tools/helpers/security-audit.js +19 -19
- package/dist/tools/helpers/security-audit.js.map +1 -1
- package/dist/tools/helpers/verify.d.ts.map +1 -1
- package/dist/tools/helpers/verify.js +5 -1
- package/dist/tools/helpers/verify.js.map +1 -1
- package/dist/tools/meta/index.d.ts +1 -1
- package/dist/tools/server-management/_shared.d.ts +15 -4
- package/dist/tools/server-management/_shared.d.ts.map +1 -1
- package/dist/tools/server-management/_shared.js +86 -13
- package/dist/tools/server-management/_shared.js.map +1 -1
- package/dist/tools/server-management/data.d.ts.map +1 -1
- package/dist/tools/server-management/data.js +88 -23
- package/dist/tools/server-management/data.js.map +1 -1
- package/dist/tools/server-management/decisions.d.ts.map +1 -1
- package/dist/tools/server-management/decisions.js +16 -3
- package/dist/tools/server-management/decisions.js.map +1 -1
- package/dist/tools/server-management/policies.d.ts.map +1 -1
- package/dist/tools/server-management/policies.js +44 -7
- package/dist/tools/server-management/policies.js.map +1 -1
- package/dist/tools/server-management/status.d.ts.map +1 -1
- package/dist/tools/server-management/status.js +80 -13
- package/dist/tools/server-management/status.js.map +1 -1
- package/dist/types.d.ts +10 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/lib/subprocess.js
CHANGED
|
@@ -25,6 +25,61 @@ import { buildChildEnv } from './child-env.js';
|
|
|
25
25
|
* OPA_MCP_MAX_SUBPROCESS_BYTES.
|
|
26
26
|
*/
|
|
27
27
|
export const DEFAULT_MAX_OUTPUT_BYTES = 32 * 1024 * 1024;
|
|
28
|
+
/**
|
|
29
|
+
* How long to wait for the stdio pipes to close once the child has exited.
|
|
30
|
+
*
|
|
31
|
+
* The child's own output is complete at 'exit'; 'close' follows when every
|
|
32
|
+
* holder of the pipes is gone. A grandchild that inherited them, which is the
|
|
33
|
+
* ordinary shape of a wrapper script around the binary, keeps 'close' from
|
|
34
|
+
* ever firing, and a result that waits for it never settles.
|
|
35
|
+
*/
|
|
36
|
+
const PIPE_DRAIN_GRACE_MS = 1_000;
|
|
37
|
+
/** Grace between SIGTERM and SIGKILL. */
|
|
38
|
+
const KILL_ESCALATION_MS = 2_000;
|
|
39
|
+
/**
|
|
40
|
+
* Ceiling on the drain after 'exit', however often late data re-arms it. The
|
|
41
|
+
* deadline bounds the drain while it is still pending; once it has fired, a
|
|
42
|
+
* writer that survived the group signal could otherwise keep the result open
|
|
43
|
+
* for as long as it kept writing.
|
|
44
|
+
*/
|
|
45
|
+
const PIPE_DRAIN_MAX_MS = 5 * PIPE_DRAIN_GRACE_MS;
|
|
46
|
+
/**
|
|
47
|
+
* Signal the child, and on POSIX its whole process group, so a grandchild
|
|
48
|
+
* started by a wrapper script dies with it. The child is spawned as a group
|
|
49
|
+
* leader for this purpose; when the group is already gone, or the platform
|
|
50
|
+
* has no groups, fall back to the child alone.
|
|
51
|
+
*/
|
|
52
|
+
function signalTree(child, sig) {
|
|
53
|
+
// The group is addressed by the child's pid, which the kernel may hand to
|
|
54
|
+
// an unrelated process once the child has been reaped. Past that point only
|
|
55
|
+
// the handle is safe to use, and Node's kill on a dead handle is a no-op.
|
|
56
|
+
const unreaped = child.exitCode === null && child.signalCode === null;
|
|
57
|
+
if (unreaped && process.platform !== 'win32' && child.pid !== undefined) {
|
|
58
|
+
try {
|
|
59
|
+
process.kill(-child.pid, sig);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// ESRCH or EPERM: no group to signal. The child itself may still be there.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
child.kill(sig);
|
|
67
|
+
}
|
|
68
|
+
/** Children that have been spawned and have not settled yet. */
|
|
69
|
+
const liveChildren = new Set();
|
|
70
|
+
/**
|
|
71
|
+
* Signal every child still running. A child sits in its own process group on
|
|
72
|
+
* POSIX, so a signal that stops the server does not reach it on its own; the
|
|
73
|
+
* server calls this on the way out. Returns how many were signalled.
|
|
74
|
+
*/
|
|
75
|
+
export function terminateChildren(sig = 'SIGTERM') {
|
|
76
|
+
let n = 0;
|
|
77
|
+
for (const child of liveChildren) {
|
|
78
|
+
signalTree(child, sig);
|
|
79
|
+
n++;
|
|
80
|
+
}
|
|
81
|
+
return n;
|
|
82
|
+
}
|
|
28
83
|
/** Accumulates a stream's chunks up to a byte ceiling. */
|
|
29
84
|
class CappedBuffer {
|
|
30
85
|
limit;
|
|
@@ -88,6 +143,9 @@ export async function runBinary(binary, opts) {
|
|
|
88
143
|
env: buildChildEnv(opts.env),
|
|
89
144
|
shell: false,
|
|
90
145
|
windowsHide: true,
|
|
146
|
+
// A process group of its own on POSIX, so a kill reaches a grandchild
|
|
147
|
+
// as well. See signalTree.
|
|
148
|
+
detached: process.platform !== 'win32',
|
|
91
149
|
});
|
|
92
150
|
const limit = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
93
151
|
const stdout = new CappedBuffer(limit);
|
|
@@ -96,27 +154,53 @@ export async function runBinary(binary, opts) {
|
|
|
96
154
|
let aborted = false;
|
|
97
155
|
let settled = false;
|
|
98
156
|
let killTimer;
|
|
157
|
+
let drainTimer;
|
|
158
|
+
/** Set once 'exit' has fired: the child is gone, only its pipes remain. */
|
|
159
|
+
let exited;
|
|
160
|
+
/** Absolute time by which the drain finishes regardless of late data. */
|
|
161
|
+
let drainUntil = 0;
|
|
162
|
+
liveChildren.add(child);
|
|
163
|
+
// Whether the child has actually ended. `child.killed` is not that: Node
|
|
164
|
+
// sets it as soon as a signal was sent, whether or not the child obeyed,
|
|
165
|
+
// so a guard on it never escalated and a child that trapped SIGTERM hung
|
|
166
|
+
// the call for good.
|
|
167
|
+
const alive = () => child.exitCode === null && child.signalCode === null;
|
|
99
168
|
const killChild = () => {
|
|
100
|
-
|
|
169
|
+
if (settled)
|
|
170
|
+
return;
|
|
171
|
+
signalTree(child, 'SIGTERM');
|
|
101
172
|
killTimer = setTimeout(() => {
|
|
102
|
-
if (
|
|
103
|
-
child
|
|
104
|
-
},
|
|
173
|
+
if (alive())
|
|
174
|
+
signalTree(child, 'SIGKILL');
|
|
175
|
+
}, KILL_ESCALATION_MS);
|
|
105
176
|
};
|
|
106
177
|
const timer = setTimeout(() => {
|
|
178
|
+
// The child is already gone and only the drain is pending: settle from
|
|
179
|
+
// what was captured. Calling that a timeout would be wrong.
|
|
180
|
+
if (exited) {
|
|
181
|
+
finishFromCapture();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
107
184
|
timedOut = true;
|
|
108
185
|
killChild();
|
|
109
186
|
}, opts.timeoutMs);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
187
|
+
// Once the child has exited there is nothing to cancel, and a listener
|
|
188
|
+
// left behind after settling would signal a pid the kernel may have
|
|
189
|
+
// handed to someone else. rego_test_multiroot passes one signal through
|
|
190
|
+
// a whole sequence of runs, so the listener has to go when the run does.
|
|
191
|
+
const onAbort = () => {
|
|
192
|
+
if (settled || exited)
|
|
193
|
+
return;
|
|
194
|
+
aborted = true;
|
|
195
|
+
killChild();
|
|
196
|
+
};
|
|
197
|
+
opts.signal?.addEventListener('abort', onAbort, { once: true });
|
|
116
198
|
const clearTimers = () => {
|
|
117
199
|
clearTimeout(timer);
|
|
118
200
|
if (killTimer)
|
|
119
201
|
clearTimeout(killTimer);
|
|
202
|
+
if (drainTimer)
|
|
203
|
+
clearTimeout(drainTimer);
|
|
120
204
|
};
|
|
121
205
|
/**
|
|
122
206
|
* Resolve at most once. 'error' and 'close' can both fire -- killing a child
|
|
@@ -128,16 +212,51 @@ export async function runBinary(binary, opts) {
|
|
|
128
212
|
return;
|
|
129
213
|
settled = true;
|
|
130
214
|
clearTimers();
|
|
215
|
+
liveChildren.delete(child);
|
|
216
|
+
opts.signal?.removeEventListener('abort', onAbort);
|
|
131
217
|
resolvePromise(result);
|
|
132
218
|
};
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
219
|
+
/**
|
|
220
|
+
* Build the result from the capture and let go of the pipes. Used once the
|
|
221
|
+
* child has exited but something it left behind still holds its stdio.
|
|
222
|
+
*/
|
|
223
|
+
const finishFromCapture = () => {
|
|
224
|
+
if (settled || !exited)
|
|
225
|
+
return;
|
|
226
|
+
child.stdout?.destroy();
|
|
227
|
+
child.stderr?.destroy();
|
|
228
|
+
finish(exited.code, exited.signal);
|
|
229
|
+
};
|
|
230
|
+
// (Re)start the wait for 'close'. Bytes can still be sitting in the pipe
|
|
231
|
+
// at 'exit', and on a loaded machine the reads that deliver them may
|
|
232
|
+
// queue behind an expired timer; each chunk that arrives pushes the
|
|
233
|
+
// grace out again, and the deadline above bounds the whole thing.
|
|
234
|
+
const armDrain = () => {
|
|
235
|
+
if (drainTimer)
|
|
236
|
+
clearTimeout(drainTimer);
|
|
237
|
+
const left = drainUntil - Date.now();
|
|
238
|
+
if (left <= 0) {
|
|
239
|
+
finishFromCapture();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
drainTimer = setTimeout(finishFromCapture, Math.min(PIPE_DRAIN_GRACE_MS, left));
|
|
243
|
+
};
|
|
244
|
+
const onData = (buf, chunk) => {
|
|
245
|
+
const overflowed = buf.push(chunk);
|
|
246
|
+
if (exited) {
|
|
247
|
+
// Nothing left to kill; a writer that outlived the child is either
|
|
248
|
+
// done, or has just been cut off by the cap.
|
|
249
|
+
if (overflowed)
|
|
250
|
+
finishFromCapture();
|
|
251
|
+
else
|
|
252
|
+
armDrain();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (overflowed)
|
|
139
256
|
killChild();
|
|
140
|
-
}
|
|
257
|
+
};
|
|
258
|
+
child.stdout?.on('data', (chunk) => onData(stdout, chunk));
|
|
259
|
+
child.stderr?.on('data', (chunk) => onData(stderr, chunk));
|
|
141
260
|
// Killing a child mid-write can surface an EPIPE on the pipe. Without a
|
|
142
261
|
// listener that is an unhandled 'error' event, which would take the whole
|
|
143
262
|
// server down -- the exact failure mode this cap exists to prevent.
|
|
@@ -153,9 +272,10 @@ export async function runBinary(binary, opts) {
|
|
|
153
272
|
aborted,
|
|
154
273
|
durationMs: Date.now() - start,
|
|
155
274
|
outputTruncated: false,
|
|
275
|
+
signal: null,
|
|
156
276
|
});
|
|
157
277
|
});
|
|
158
|
-
|
|
278
|
+
const finish = (code, signal) => {
|
|
159
279
|
// Decoding runs here, in an async callback whose throw would escape every
|
|
160
280
|
// try/catch upstream and reach the process as an uncaughtException. The
|
|
161
281
|
// byte cap is what makes that unreachable; this is the belt to its braces.
|
|
@@ -168,6 +288,7 @@ export async function runBinary(binary, opts) {
|
|
|
168
288
|
aborted,
|
|
169
289
|
durationMs: Date.now() - start,
|
|
170
290
|
outputTruncated: stdout.didOverflow || stderr.didOverflow,
|
|
291
|
+
signal,
|
|
171
292
|
});
|
|
172
293
|
}
|
|
173
294
|
catch (e) {
|
|
@@ -179,9 +300,23 @@ export async function runBinary(binary, opts) {
|
|
|
179
300
|
aborted,
|
|
180
301
|
durationMs: Date.now() - start,
|
|
181
302
|
outputTruncated: true,
|
|
303
|
+
signal,
|
|
182
304
|
});
|
|
183
305
|
}
|
|
306
|
+
};
|
|
307
|
+
// 'close' carries the complete output and is the normal way to finish.
|
|
308
|
+
// 'exit' starts a short grace for it: when the pipes are still held open
|
|
309
|
+
// by something the child left behind, the result is built from what was
|
|
310
|
+
// captured and the server's ends of the pipes are released.
|
|
311
|
+
child.on('exit', (code, signal) => {
|
|
312
|
+
// A spawn failure settles on 'error' and may still be followed by 'exit'.
|
|
313
|
+
if (settled)
|
|
314
|
+
return;
|
|
315
|
+
exited = { code, signal };
|
|
316
|
+
drainUntil = Date.now() + PIPE_DRAIN_MAX_MS;
|
|
317
|
+
armDrain();
|
|
184
318
|
});
|
|
319
|
+
child.on('close', (code, signal) => finish(code, signal));
|
|
185
320
|
if (opts.stdin !== undefined) {
|
|
186
321
|
child.stdin?.write(opts.stdin);
|
|
187
322
|
child.stdin?.end();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subprocess.js","sourceRoot":"","sources":["../../src/lib/subprocess.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"subprocess.js","sourceRoot":"","sources":["../../src/lib/subprocess.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,41 +6,28 @@
|
|
|
6
6
|
* the formatted MCP result. The pieces here factor out the parts every
|
|
7
7
|
* tool repeats so each tool file can stay focused on its own logic.
|
|
8
8
|
*/
|
|
9
|
-
/**
|
|
10
|
-
* Matches the temp-file paths written by OpaCli.withTempSource and
|
|
11
|
-
* RegalCli.withTempSource when handling inline source. Both now use
|
|
12
|
-
* mkdtemp which produces a private directory; the file inside is always
|
|
13
|
-
* named input.rego. Matches both Unix (/) and Windows (\) separators.
|
|
14
|
-
*/
|
|
15
|
-
export declare const INLINE_TEMP_PATH_PATTERN: RegExp;
|
|
16
|
-
/**
|
|
17
|
-
* Replace a file path that refers to one of our temp files with the
|
|
18
|
-
* sentinel string `<inline>`. Returns the original string unchanged when
|
|
19
|
-
* it does not match the pattern, so callers can unconditionally apply it
|
|
20
|
-
* to all location.file values regardless of whether inline source was used.
|
|
21
|
-
*/
|
|
22
|
-
export declare function sanitizeInlinePath(file: string): string;
|
|
23
|
-
/**
|
|
24
|
-
* Recursively rewrite every temp-file path -- in both string values and object
|
|
25
|
-
* keys -- to the `<inline>` sentinel. OPA writes inline source to a temp file,
|
|
26
|
-
* so trace (`explanation`), coverage, and profile output reference that path;
|
|
27
|
-
* this normalizes the whole structure so callers never see an absolute temp
|
|
28
|
-
* path. Only our own temp paths match the pattern, so real user file paths pass
|
|
29
|
-
* through untouched.
|
|
30
|
-
*/
|
|
31
|
-
export declare function sanitizeInlinePathsDeep(value: unknown): unknown;
|
|
32
9
|
import type { Config } from '../config.js';
|
|
33
10
|
import { type McpToolResult } from './output.js';
|
|
34
11
|
import type { SpawnResult } from './subprocess.js';
|
|
35
12
|
import type { ToolEnvelope } from '../types.js';
|
|
13
|
+
export { INLINE_TEMP_PATH_PATTERN, sanitizeInlinePath, sanitizeInlinePathsDeep, sanitizeInlineText, } from './inline-paths.js';
|
|
36
14
|
/**
|
|
37
15
|
* Convert a subprocess outcome into a structured tool error envelope
|
|
38
|
-
* for the cases that are universal across all CLI-backed tools:
|
|
39
|
-
*
|
|
16
|
+
* for the cases that are universal across all CLI-backed tools: cancelled
|
|
17
|
+
* by the client, timed out, output over the capture cap, killed by a
|
|
18
|
+
* signal from outside the server, binary missing. Returns `undefined` when the
|
|
40
19
|
* subprocess exited normally (exitCode is a number) so the caller can
|
|
41
20
|
* inspect the result.
|
|
42
21
|
*/
|
|
43
22
|
export declare function mapSubprocessFailure(result: SpawnResult, binary: 'opa' | 'regal' | 'conftest'): ToolEnvelope<never> | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Parse the last top-level JSON object in `text`, for a command that prints
|
|
25
|
+
* one document per run (`opa test --coverage --count N`). Strings are
|
|
26
|
+
* honoured, so a brace inside a file name does not end a document. Returns
|
|
27
|
+
* `undefined` when there is none. `accept` narrows it to the last object of
|
|
28
|
+
* the shape wanted, so trace text that happens to hold braces is skipped.
|
|
29
|
+
*/
|
|
30
|
+
export declare function lastJsonObject<T = unknown>(text: string, accept?: (parsed: unknown) => boolean): T | undefined;
|
|
44
31
|
/**
|
|
45
32
|
* Try to parse `text` as JSON. Returns `undefined` on failure so the
|
|
46
33
|
* caller can fall back to a textual error envelope rather than throwing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/tool-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH
|
|
1
|
+
{"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/tool-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,aAAa,CAAC;AAE/D,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,KAAK,GAAG,OAAO,GAAG,UAAU,GACnC,YAAY,CAAC,KAAK,CAAC,GAAG,SAAS,CA+DjC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,GAAG,OAAO,EACxC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAoB,GAChD,CAAC,GAAG,SAAS,CAkCf;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAOrE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GACpC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,CAAA;CAAE,CAU9E;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GACnC,OAAO,CAAC,aAAa,CAAC,CAgBxB"}
|
package/dist/lib/tool-helpers.js
CHANGED
|
@@ -6,52 +6,16 @@
|
|
|
6
6
|
* the formatted MCP result. The pieces here factor out the parts every
|
|
7
7
|
* tool repeats so each tool file can stay focused on its own logic.
|
|
8
8
|
*/
|
|
9
|
-
/**
|
|
10
|
-
* Matches the temp-file paths written by OpaCli.withTempSource and
|
|
11
|
-
* RegalCli.withTempSource when handling inline source. Both now use
|
|
12
|
-
* mkdtemp which produces a private directory; the file inside is always
|
|
13
|
-
* named input.rego. Matches both Unix (/) and Windows (\) separators.
|
|
14
|
-
*/
|
|
15
|
-
export const INLINE_TEMP_PATH_PATTERN = /orygn-(?:opa|regal)-mcp-[^/\\]+[/\\]input\.rego$/i;
|
|
16
|
-
/**
|
|
17
|
-
* Replace a file path that refers to one of our temp files with the
|
|
18
|
-
* sentinel string `<inline>`. Returns the original string unchanged when
|
|
19
|
-
* it does not match the pattern, so callers can unconditionally apply it
|
|
20
|
-
* to all location.file values regardless of whether inline source was used.
|
|
21
|
-
*/
|
|
22
|
-
export function sanitizeInlinePath(file) {
|
|
23
|
-
return INLINE_TEMP_PATH_PATTERN.test(file) ? '<inline>' : file;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Recursively rewrite every temp-file path -- in both string values and object
|
|
27
|
-
* keys -- to the `<inline>` sentinel. OPA writes inline source to a temp file,
|
|
28
|
-
* so trace (`explanation`), coverage, and profile output reference that path;
|
|
29
|
-
* this normalizes the whole structure so callers never see an absolute temp
|
|
30
|
-
* path. Only our own temp paths match the pattern, so real user file paths pass
|
|
31
|
-
* through untouched.
|
|
32
|
-
*/
|
|
33
|
-
export function sanitizeInlinePathsDeep(value) {
|
|
34
|
-
if (typeof value === 'string')
|
|
35
|
-
return sanitizeInlinePath(value);
|
|
36
|
-
if (Array.isArray(value))
|
|
37
|
-
return value.map(sanitizeInlinePathsDeep);
|
|
38
|
-
if (value !== null && typeof value === 'object') {
|
|
39
|
-
const out = {};
|
|
40
|
-
for (const [key, val] of Object.entries(value)) {
|
|
41
|
-
out[sanitizeInlinePath(key)] = sanitizeInlinePathsDeep(val);
|
|
42
|
-
}
|
|
43
|
-
return out;
|
|
44
|
-
}
|
|
45
|
-
return value;
|
|
46
|
-
}
|
|
47
9
|
import { err } from './errors.js';
|
|
48
10
|
import { logger } from './logger.js';
|
|
49
11
|
import { formatEnvelope } from './output.js';
|
|
50
12
|
import { validatePath } from './security.js';
|
|
13
|
+
export { INLINE_TEMP_PATH_PATTERN, sanitizeInlinePath, sanitizeInlinePathsDeep, sanitizeInlineText, } from './inline-paths.js';
|
|
51
14
|
/**
|
|
52
15
|
* Convert a subprocess outcome into a structured tool error envelope
|
|
53
|
-
* for the cases that are universal across all CLI-backed tools:
|
|
54
|
-
*
|
|
16
|
+
* for the cases that are universal across all CLI-backed tools: cancelled
|
|
17
|
+
* by the client, timed out, output over the capture cap, killed by a
|
|
18
|
+
* signal from outside the server, binary missing. Returns `undefined` when the
|
|
55
19
|
* subprocess exited normally (exitCode is a number) so the caller can
|
|
56
20
|
* inspect the result.
|
|
57
21
|
*/
|
|
@@ -80,6 +44,16 @@ export function mapSubprocessFailure(result, binary) {
|
|
|
80
44
|
details: { durationMs: result.durationMs },
|
|
81
45
|
});
|
|
82
46
|
}
|
|
47
|
+
// The server's own kills are handled above. A signal that reaches here came
|
|
48
|
+
// from outside it, most often the kernel's out-of-memory killer, and the
|
|
49
|
+
// binary is installed and was running; sending the user to reinstall it
|
|
50
|
+
// would be wrong.
|
|
51
|
+
if (result.exitCode === null && result.signal) {
|
|
52
|
+
return err('SUBPROCESS_KILLED', `${binary} was terminated by ${result.signal} before it finished.`, {
|
|
53
|
+
hint: 'The process was killed from outside the server, for example by the out-of-memory killer or a container limit. Check the host memory limits and system logs.',
|
|
54
|
+
details: { signal: result.signal, durationMs: result.durationMs },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
83
57
|
if (result.exitCode === null) {
|
|
84
58
|
const code = binary === 'opa'
|
|
85
59
|
? 'OPA_BINARY_NOT_FOUND'
|
|
@@ -95,6 +69,58 @@ export function mapSubprocessFailure(result, binary) {
|
|
|
95
69
|
}
|
|
96
70
|
return undefined;
|
|
97
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse the last top-level JSON object in `text`, for a command that prints
|
|
74
|
+
* one document per run (`opa test --coverage --count N`). Strings are
|
|
75
|
+
* honoured, so a brace inside a file name does not end a document. Returns
|
|
76
|
+
* `undefined` when there is none. `accept` narrows it to the last object of
|
|
77
|
+
* the shape wanted, so trace text that happens to hold braces is skipped.
|
|
78
|
+
*/
|
|
79
|
+
export function lastJsonObject(text, accept = () => true) {
|
|
80
|
+
let depth = 0;
|
|
81
|
+
let inString = false;
|
|
82
|
+
let escaped = false;
|
|
83
|
+
let start = -1;
|
|
84
|
+
let last;
|
|
85
|
+
for (let i = 0; i < text.length; i++) {
|
|
86
|
+
const ch = text[i];
|
|
87
|
+
if (inString) {
|
|
88
|
+
if (escaped)
|
|
89
|
+
escaped = false;
|
|
90
|
+
else if (ch === '\\')
|
|
91
|
+
escaped = true;
|
|
92
|
+
else if (ch === '"')
|
|
93
|
+
inString = false;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (ch === '"')
|
|
97
|
+
inString = true;
|
|
98
|
+
else if (ch === '{') {
|
|
99
|
+
if (depth === 0)
|
|
100
|
+
start = i;
|
|
101
|
+
depth++;
|
|
102
|
+
}
|
|
103
|
+
else if (ch === '}') {
|
|
104
|
+
// A stray closing brace must not put every later object out of reach.
|
|
105
|
+
if (depth > 0)
|
|
106
|
+
depth--;
|
|
107
|
+
if (depth === 0 && start !== -1) {
|
|
108
|
+
const candidate = tryParseJson(text.slice(start, i + 1));
|
|
109
|
+
if (candidate !== undefined && accept(candidate))
|
|
110
|
+
last = text.slice(start, i + 1);
|
|
111
|
+
start = -1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (last === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(last);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
98
124
|
/**
|
|
99
125
|
* Try to parse `text` as JSON. Returns `undefined` on failure so the
|
|
100
126
|
* caller can fall back to a textual error envelope rather than throwing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-helpers.js","sourceRoot":"","sources":["../../src/lib/tool-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"tool-helpers.js","sourceRoot":"","sources":["../../src/lib/tool-helpers.ts"],"names":[],"mappings":"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"}
|
package/dist/prompts/index.js
CHANGED
|
@@ -58,7 +58,7 @@ Apply this checklist, calling tools as needed:
|
|
|
58
58
|
concern in a policy hot path.
|
|
59
59
|
6. **Annotations.** Check that exported rules have docstrings. If
|
|
60
60
|
missing, suggest minimal \`# METADATA\` annotations.
|
|
61
|
-
7. **Shape of input.** Use \`
|
|
61
|
+
7. **Shape of input.** Use \`rego_infer_input_schema\` to enumerate input
|
|
62
62
|
refs the policy reads; confirm the documented input contract
|
|
63
63
|
matches.
|
|
64
64
|
|
|
@@ -81,7 +81,7 @@ Workflow:
|
|
|
81
81
|
Identify which rules were evaluated and which fired.
|
|
82
82
|
4. The cause is one of:
|
|
83
83
|
a. **Input mismatch** -- the policy expected a different input shape.
|
|
84
|
-
Use \`
|
|
84
|
+
Use \`rego_infer_input_schema\` to list the refs the policy reads
|
|
85
85
|
and confirm each is present in the input.
|
|
86
86
|
b. **Rule logic** -- a guard fired or didn't fire when it should
|
|
87
87
|
have. Read the trace and explain which rule's body evaluated to
|
package/dist/resources/index.js
CHANGED
|
@@ -49,7 +49,7 @@ async function buildBuiltinsResource(opa) {
|
|
|
49
49
|
.filter((n) => typeof n === 'string' && SECURITY_SENSITIVE.has(n))
|
|
50
50
|
.sort();
|
|
51
51
|
return JSON.stringify({
|
|
52
|
-
version_note: 'Derived at read time from `opa capabilities --current
|
|
52
|
+
version_note: 'Derived at read time from `opa capabilities --current` of the opa binary this server resolved: OPA_BINARY, then PATH, then the bundled copy.',
|
|
53
53
|
builtin_count: caps.builtins?.length ?? 0,
|
|
54
54
|
categories: grouped,
|
|
55
55
|
future_keywords: caps.future_keywords ?? [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAazC,SAAS,kBAAkB,CAAC,IAAuB;IACjD,MAAM,OAAO,GAA6B,EAAE,CAAC;IAC7C,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,IAAI;YAAE,SAAS;QAC5B,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,IAAI,EAAE,CAAC;QAC9C,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,OAAO,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;YAChC,OAAO,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5C,SAAS;QACX,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,YAAY,EAAE,CAAC;YACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACzB,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;QAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACvD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,WAAW;IACX,aAAa;IACb,gCAAgC;IAChC,uCAAuC;IACvC,2BAA2B;IAC3B,mCAAmC;IACnC,2BAA2B;CAC5B,CAAC,CAAC;AAEH,KAAK,UAAU,qBAAqB,CAAC,GAAW;IAC9C,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,SAAS,CACnB;YACE,KAAK,EAAE,mCAAmC;YAC1C,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;SAC7B,EACD,IAAI,EACJ,CAAC,CACF,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,YAAY,CAAoB,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5D,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,6CAA6C,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;SACpC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAClB,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SAC9E,IAAI,EAAE,CAAC;IAEV,OAAO,IAAI,CAAC,SAAS,CACnB;QACE,YAAY,EACV,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* - A test
|
|
6
6
|
* - Common pitfalls
|
|
7
7
|
*/
|
|
8
|
-
export declare const PATTERNS = "# Rego pattern library\n\nCommon Rego patterns with working examples, tests, and pitfalls. Each\npattern is self-contained -- copy, adapt, and ship.\n\n---\n\n## 1. Role-based access control (RBAC)\n\n**When to use:** the simplest authorization model. Every user has one\nor more roles; each role grants a set of actions on a set of\nresources. Sufficient for ~80% of internal applications.\n\n```rego\npackage rbac\n\nimport rego.v1\n\ndefault allow := false\n\n# Permissions table -- extend as roles evolve.\npermissions := {\n \"admin\": {\"read\", \"write\", \"delete\", \"manage_users\"},\n \"editor\": {\"read\", \"write\"},\n \"viewer\": {\"read\"},\n}\n\nallow if {\n some role in input.user.roles\n input.action in permissions[role]\n}\n\n# Why was it denied? -- useful for audit logs.\ndeny_reasons contains reason if {\n not allow\n input.user\n reason := sprintf(\n \"user %q has roles %v, none grant %q\",\n [input.user.id, input.user.roles, input.action],\n )\n}\n\ndeny_reasons contains \"anonymous request\" if {\n not allow\n not input.user\n}\n```\n\n**Test:**\n\n```rego\npackage rbac_test\n\nimport rego.v1\nimport data.rbac\n\ntest_admin_can_delete if {\n rbac.allow with input as {\n \"user\": {\"id\": \"alice\", \"roles\": [\"admin\"]},\n \"action\": \"delete\",\n }\n}\n\ntest_viewer_cannot_delete if {\n not rbac.allow with input as {\n \"user\": {\"id\": \"bob\", \"roles\": [\"viewer\"]},\n \"action\": \"delete\",\n }\n}\n\ntest_anonymous_denied if {\n not rbac.allow with input as {\"action\": \"read\"}\n reasons := rbac.deny_reasons with input as {\"action\": \"read\"}\n \"anonymous request\" in reasons\n}\n```\n\n**Pitfalls:**\n- The `permissions` table grows unbounded. Move to data files\n (`data.permissions`) once you have more than ~20 roles.\n- Roles overlap with groups in real auth systems; map at the boundary\n rather than carrying both.\n\n---\n\n## 2. Attribute-based access control (ABAC)\n\n**When to use:** when \"who\" alone isn't enough -- decisions also depend\non resource attributes (ownership, tenant, sensitivity) and context\n(time of day, source IP).\n\n```rego\npackage abac\n\nimport rego.v1\n\ndefault allow := false\n\n# A user can read any resource they own.\nallow if {\n input.action == \"read\"\n input.resource.owner_id == input.user.id\n}\n\n# A user can read shared resources at their organization.\nallow if {\n input.action == \"read\"\n input.resource.shared\n input.resource.org_id == input.user.org_id\n}\n\n# Admins can do anything within their organization.\nallow if {\n \"admin\" in input.user.roles\n input.resource.org_id == input.user.org_id\n}\n\n# Don't show \"secret\" resources to anyone outside the owner's\n# organization, even admins.\nallow := false if {\n input.resource.classification == \"secret\"\n input.resource.org_id != input.user.org_id\n}\n```\n\n**Test:**\n\n```rego\npackage abac_test\n\nimport rego.v1\nimport data.abac\n\ntest_owner_reads_own if {\n abac.allow with input as {\n \"action\": \"read\",\n \"user\": {\"id\": \"u1\", \"org_id\": \"o1\"},\n \"resource\": {\"owner_id\": \"u1\", \"org_id\": \"o1\"},\n }\n}\n\ntest_admin_blocked_from_secret_in_other_org if {\n not abac.allow with input as {\n \"action\": \"read\",\n \"user\": {\"id\": \"u1\", \"org_id\": \"o1\", \"roles\": [\"admin\"]},\n \"resource\": {\n \"owner_id\": \"u2\",\n \"org_id\": \"o2\",\n \"classification\": \"secret\",\n },\n }\n}\n```\n\n**Pitfalls:**\n- Multiple `allow` rules combine with logical OR. Use explicit\n `allow := false if ...` to *override* an allow.\n- Don't compute attributes inside the policy; compute them at the\n boundary and pass via `input`.\n\n---\n\n## 3. Kubernetes admission control\n\n**When to use:** validate or mutate Kubernetes resources at admission\ntime. Run as a Gatekeeper, OPA-as-a-webhook, or Kyverno-equivalent\npolicy layer.\n\n```rego\npackage k8s.admission\n\nimport rego.v1\n\n# Reject pods without resource limits.\ndeny contains msg if {\n input.request.kind.kind == \"Pod\"\n container := input.request.object.spec.containers[_]\n not container.resources.limits.memory\n msg := sprintf(\n \"Pod %q container %q is missing resources.limits.memory\",\n [input.request.object.metadata.name, container.name],\n )\n}\n\n# Reject privileged containers in production.\ndeny contains msg if {\n input.request.kind.kind == \"Pod\"\n input.request.namespace != \"kube-system\"\n container := input.request.object.spec.containers[_]\n container.securityContext.privileged == true\n msg := sprintf(\n \"privileged containers are not allowed: %q in %q\",\n [container.name, input.request.object.metadata.name],\n )\n}\n```\n\n**Pitfalls:**\n- Use `input.request.object` for the resource being admitted; the\n envelope shape comes from Kubernetes, not your control.\n- For mutations, return a JSON Patch via the `patch` field. Test\n patches with the actual admission webhook in dry-run mode before\n enforcing.\n- Iteration order is undefined; never rely on `containers[0]` to mean\n anything specific.\n\n---\n\n## 4. Infrastructure-as-Code gates (Terraform)\n\n**When to use:** validate Terraform plans before apply. Catch overly\npermissive IAM, public S3 buckets, missing encryption.\n\n```rego\npackage terraform\n\nimport rego.v1\n\n# Reject S3 buckets without server-side encryption.\ndeny contains msg if {\n resource := input.resource_changes[_]\n resource.type == \"aws_s3_bucket\"\n after := resource.change.after\n not after.server_side_encryption_configuration\n msg := sprintf(\n \"S3 bucket %q has no server-side encryption configured\",\n [resource.address],\n )\n}\n\n# Reject IAM policies with action \"*\" on resource \"*\".\ndeny contains msg if {\n resource := input.resource_changes[_]\n resource.type == \"aws_iam_policy\"\n policy := json.unmarshal(resource.change.after.policy)\n statement := policy.Statement[_]\n statement.Effect == \"Allow\"\n \"*\" in statement.Action\n statement.Resource == \"*\"\n msg := sprintf(\"IAM policy %q grants Allow * on *\", [resource.address])\n}\n```\n\n**Pitfalls:**\n- Terraform plan JSON is verbose and version-specific. Pin the\n `terraform plan -json` schema you target.\n- `resource_changes[_].change.after` may be `null` for destroys --\n guard against it.\n- For wide-radius changes, use `opa exec --decision` against a plan\n file in CI, not the live API.\n\n---\n\n## 5. API authorization (HTTP request gating)\n\n**When to use:** at the API gateway / reverse proxy layer, validate\neach request against the caller's identity and the requested\nendpoint.\n\n```rego\npackage api.authz\n\nimport rego.v1\n\ndefault allow := false\n\n# Public endpoints -- no auth required.\npublic_endpoints := {\n {\"method\": \"GET\", \"path\": [\"health\"]},\n {\"method\": \"GET\", \"path\": [\"version\"]},\n}\n\nallow if {\n some endpoint in public_endpoints\n matches_endpoint(endpoint)\n}\n\n# Authenticated reads on resources the user has access to.\nallow if {\n input.method == \"GET\"\n input.user\n user_can_read(input.user, input.path)\n}\n\n# Authenticated writes only with specific scopes.\nallow if {\n input.method in {\"POST\", \"PUT\", \"PATCH\", \"DELETE\"}\n input.user\n \"write\" in input.user.scopes\n user_can_write(input.user, input.path)\n}\n\nmatches_endpoint(spec) if {\n spec.method == input.method\n spec.path == input.path\n}\n\nuser_can_read(user, path) if {\n path[0] == \"users\"\n user.id == path[1]\n}\n\nuser_can_write(user, path) if {\n path[0] == \"users\"\n user.id == path[1]\n}\n```\n\n**Pitfalls:**\n- `input.path` is typically an array (`[\"users\", \"alice\"]`), not a\n string. Build it consistently at the gateway.\n- Path-prefix matching is easy; full pattern matching is not. For\n parameterized routes, decode at the gateway and pass structured\n fields.\n\n---\n\n## 6. Rate limiting (with sliding window data)\n\n**When to use:** allow N requests per principal per window. Lightweight\nlimit enforcement; for high throughput, push to a dedicated rate\nlimiter.\n\n```rego\npackage rate\n\nimport rego.v1\n\n# Configuration: 100 requests per principal per 60-second window.\nlimit := 100\nwindow_seconds := 60\n\n# input.now is a unix timestamp in nanoseconds.\n# data.requests[principal] is an array of nanosecond timestamps.\n\ncurrent_window := requests if {\n requests := [t | some t in data.requests[input.principal]; t > input.now - window_seconds * 1000000000]\n}\n\n# Do not name this rule `count`: it would shadow the built-in of the same\n# name, and the call below would resolve to the rule instead.\nrequest_count := count(current_window)\n\nallow if request_count < limit\n\ndeny_reason := sprintf(\n \"rate limit exceeded: %d requests in last %d seconds (limit %d)\",\n [request_count, window_seconds, limit],\n) if not allow\n```\n\n**Pitfalls:**\n- `data.requests` grows unbounded unless the writer prunes outside the\n window. Schedule prune on every write.\n- This pattern is *advisory* -- under load, two concurrent decisions\n can both see `count == limit - 1` and both allow. For strict\n limits, use a Lua/Redis token bucket at the gateway and have OPA\n validate the token, not count requests.\n\n---\n\n## Where these patterns came from\n\nEach is distilled from production policy code. The full Rego files,\ntests, and policy data fixtures live in this server's GitHub\nrepository under `tests/fixtures/policies/`.\n\nFor more patterns, see:\n\n- OPA Playground: https://play.openpolicyagent.org/\n- Awesome OPA: https://github.com/anderseknert/awesome-opa\n- Styra DAS pattern library: https://docs.styra.com/das/policies\n";
|
|
8
|
+
export declare const PATTERNS = "# Rego pattern library\n\nCommon Rego patterns with working examples, tests, and pitfalls. Each\npattern is self-contained -- copy, adapt, and ship.\n\n---\n\n## 1. Role-based access control (RBAC)\n\n**When to use:** the simplest authorization model. Every user has one\nor more roles; each role grants a set of actions on a set of\nresources. Sufficient for ~80% of internal applications.\n\n```rego\npackage rbac\n\nimport rego.v1\n\ndefault allow := false\n\n# Permissions table -- extend as roles evolve.\npermissions := {\n \"admin\": {\"read\", \"write\", \"delete\", \"manage_users\"},\n \"editor\": {\"read\", \"write\"},\n \"viewer\": {\"read\"},\n}\n\nallow if {\n some role in input.user.roles\n input.action in permissions[role]\n}\n\n# Why was it denied? -- useful for audit logs.\ndeny_reasons contains reason if {\n not allow\n input.user\n reason := sprintf(\n \"user %q has roles %v, none grant %q\",\n [input.user.id, input.user.roles, input.action],\n )\n}\n\ndeny_reasons contains \"anonymous request\" if {\n not allow\n not input.user\n}\n```\n\n**Test:**\n\n```rego\npackage rbac_test\n\nimport rego.v1\nimport data.rbac\n\ntest_admin_can_delete if {\n rbac.allow with input as {\n \"user\": {\"id\": \"alice\", \"roles\": [\"admin\"]},\n \"action\": \"delete\",\n }\n}\n\ntest_viewer_cannot_delete if {\n not rbac.allow with input as {\n \"user\": {\"id\": \"bob\", \"roles\": [\"viewer\"]},\n \"action\": \"delete\",\n }\n}\n\ntest_anonymous_denied if {\n not rbac.allow with input as {\"action\": \"read\"}\n reasons := rbac.deny_reasons with input as {\"action\": \"read\"}\n \"anonymous request\" in reasons\n}\n```\n\n**Pitfalls:**\n- The `permissions` table grows unbounded. Move to data files\n (`data.permissions`) once you have more than ~20 roles.\n- Roles overlap with groups in real auth systems; map at the boundary\n rather than carrying both.\n\n---\n\n## 2. Attribute-based access control (ABAC)\n\n**When to use:** when \"who\" alone isn't enough -- decisions also depend\non resource attributes (ownership, tenant, sensitivity) and context\n(time of day, source IP).\n\n```rego\npackage abac\n\nimport rego.v1\n\ndefault allow := false\n\n# Don't show \"secret\" resources to anyone outside the owner's\n# organization, even admins.\n#\n# Written as a guard the permissive rules consult. A second rule\n# assigning `allow := false` is a conflict in Rego, not an override:\n# both rules produce a value for the same document and evaluation\n# fails with eval_conflict_error. Deny wins by being a precondition,\n# not by being written last.\nhidden_from_other_org if {\n input.resource.classification == \"secret\"\n input.resource.org_id != input.user.org_id\n}\n\n# A user can read any resource they own.\nallow if {\n input.action == \"read\"\n input.resource.owner_id == input.user.id\n not hidden_from_other_org\n}\n\n# A user can read shared resources at their organization.\nallow if {\n input.action == \"read\"\n input.resource.shared\n input.resource.org_id == input.user.org_id\n not hidden_from_other_org\n}\n\n# Admins can do anything within their organization.\nallow if {\n \"admin\" in input.user.roles\n input.resource.org_id == input.user.org_id\n not hidden_from_other_org\n}\n```\n\n**Test:**\n\n```rego\npackage abac_test\n\nimport rego.v1\nimport data.abac\n\ntest_owner_reads_own if {\n abac.allow with input as {\n \"action\": \"read\",\n \"user\": {\"id\": \"u1\", \"org_id\": \"o1\"},\n \"resource\": {\"owner_id\": \"u1\", \"org_id\": \"o1\"},\n }\n}\n\ntest_admin_blocked_from_secret_in_other_org if {\n not abac.allow with input as {\n \"action\": \"read\",\n \"user\": {\"id\": \"u1\", \"org_id\": \"o1\", \"roles\": [\"admin\"]},\n \"resource\": {\n \"owner_id\": \"u2\",\n \"org_id\": \"o2\",\n \"classification\": \"secret\",\n },\n }\n}\n```\n\n**Pitfalls:**\n- Multiple `allow` rules combine with logical OR. A later rule\n cannot override an earlier one by assigning a different value:\n two rules producing different values for the same document is an\n `eval_conflict_error` and evaluation fails. Express a denial as a\n condition the permissive rules must pass, as `hidden_from_other_org`\n does above.\n- Don't compute attributes inside the policy; compute them at the\n boundary and pass via `input`.\n\n---\n\n## 3. Kubernetes admission control\n\n**When to use:** validate or mutate Kubernetes resources at admission\ntime. Run as a Gatekeeper, OPA-as-a-webhook, or Kyverno-equivalent\npolicy layer.\n\n```rego\npackage k8s.admission\n\nimport rego.v1\n\n# Reject pods without resource limits.\ndeny contains msg if {\n input.request.kind.kind == \"Pod\"\n container := input.request.object.spec.containers[_]\n not container.resources.limits.memory\n msg := sprintf(\n \"Pod %q container %q is missing resources.limits.memory\",\n [input.request.object.metadata.name, container.name],\n )\n}\n\n# Reject privileged containers in production.\ndeny contains msg if {\n input.request.kind.kind == \"Pod\"\n input.request.namespace != \"kube-system\"\n container := input.request.object.spec.containers[_]\n container.securityContext.privileged == true\n msg := sprintf(\n \"privileged containers are not allowed: %q in %q\",\n [container.name, input.request.object.metadata.name],\n )\n}\n```\n\n**Pitfalls:**\n- Use `input.request.object` for the resource being admitted; the\n envelope shape comes from Kubernetes, not your control.\n- For mutations, return a JSON Patch via the `patch` field. Test\n patches with the actual admission webhook in dry-run mode before\n enforcing.\n- Iteration order is undefined; never rely on `containers[0]` to mean\n anything specific.\n\n---\n\n## 4. Infrastructure-as-Code gates (Terraform)\n\n**When to use:** validate Terraform plans before apply. Catch overly\npermissive IAM, public S3 buckets, missing encryption.\n\n```rego\npackage terraform\n\nimport rego.v1\n\n# Reject S3 buckets without server-side encryption.\ndeny contains msg if {\n resource := input.resource_changes[_]\n resource.type == \"aws_s3_bucket\"\n after := resource.change.after\n not after.server_side_encryption_configuration\n msg := sprintf(\n \"S3 bucket %q has no server-side encryption configured\",\n [resource.address],\n )\n}\n\n# Reject IAM policies with action \"*\" on resource \"*\".\n#\n# AWS accepts either a bare string or an array for Statement, Action\n# and Resource, so each is widened to a set before the wildcard test.\n# Testing the array form alone lets the most common admin policy of\n# all, {\"Action\": \"*\", \"Resource\": \"*\"}, through untouched.\nto_set(v) := {v} if is_string(v)\n\nto_set(v) := {x | some x in v} if is_array(v)\n\nstatements(policy) := to_set(policy.Statement) if is_array(policy.Statement)\n\nstatements(policy) := {policy.Statement} if is_object(policy.Statement)\n\ndeny contains msg if {\n resource := input.resource_changes[_]\n resource.type == \"aws_iam_policy\"\n policy := json.unmarshal(resource.change.after.policy)\n some statement in statements(policy)\n statement.Effect == \"Allow\"\n \"*\" in to_set(statement.Action)\n \"*\" in to_set(statement.Resource)\n msg := sprintf(\"IAM policy %q grants Allow * on *\", [resource.address])\n}\n```\n\n**Pitfalls:**\n- Terraform plan JSON is verbose and version-specific. Pin the\n `terraform plan -json` schema you target.\n- `resource_changes[_].change.after` may be `null` for destroys --\n guard against it.\n- IAM policy documents are string-or-array in several places. Match\n both, or a policy that grants everything slips through.\n- For wide-radius changes, use `opa exec --decision` against a plan\n file in CI, not the live API.\n\n---\n\n## 5. API authorization (HTTP request gating)\n\n**When to use:** at the API gateway / reverse proxy layer, validate\neach request against the caller's identity and the requested\nendpoint.\n\n```rego\npackage api.authz\n\nimport rego.v1\n\ndefault allow := false\n\n# Public endpoints -- no auth required.\npublic_endpoints := {\n {\"method\": \"GET\", \"path\": [\"health\"]},\n {\"method\": \"GET\", \"path\": [\"version\"]},\n}\n\nallow if {\n some endpoint in public_endpoints\n matches_endpoint(endpoint)\n}\n\n# Authenticated reads on resources the user has access to.\nallow if {\n input.method == \"GET\"\n input.user\n user_can_read(input.user, input.path)\n}\n\n# Authenticated writes only with specific scopes.\nallow if {\n input.method in {\"POST\", \"PUT\", \"PATCH\", \"DELETE\"}\n input.user\n \"write\" in input.user.scopes\n user_can_write(input.user, input.path)\n}\n\nmatches_endpoint(spec) if {\n spec.method == input.method\n spec.path == input.path\n}\n\nuser_can_read(user, path) if {\n path[0] == \"users\"\n user.id == path[1]\n}\n\nuser_can_write(user, path) if {\n path[0] == \"users\"\n user.id == path[1]\n}\n```\n\n**Pitfalls:**\n- `input.path` is typically an array (`[\"users\", \"alice\"]`), not a\n string. Build it consistently at the gateway.\n- Path-prefix matching is easy; full pattern matching is not. For\n parameterized routes, decode at the gateway and pass structured\n fields.\n\n---\n\n## 6. Rate limiting (with sliding window data)\n\n**When to use:** allow N requests per principal per window. Lightweight\nlimit enforcement; for high throughput, push to a dedicated rate\nlimiter.\n\n```rego\npackage rate\n\nimport rego.v1\n\n# Configuration: 100 requests per principal per 60-second window.\nlimit := 100\nwindow_seconds := 60\n\n# input.now is a unix timestamp in nanoseconds.\n# data.requests[principal] is an array of nanosecond timestamps.\n\ncurrent_window := requests if {\n requests := [t | some t in data.requests[input.principal]; t > input.now - window_seconds * 1000000000]\n}\n\n# Do not name this rule `count`: it would shadow the built-in of the same\n# name, and the call below would resolve to the rule instead.\nrequest_count := count(current_window)\n\nallow if request_count < limit\n\ndeny_reason := sprintf(\n \"rate limit exceeded: %d requests in last %d seconds (limit %d)\",\n [request_count, window_seconds, limit],\n) if not allow\n```\n\n**Pitfalls:**\n- `data.requests` grows unbounded unless the writer prunes outside the\n window. Schedule prune on every write.\n- This pattern is *advisory* -- under load, two concurrent decisions\n can both see `count == limit - 1` and both allow. For strict\n limits, use a Lua/Redis token bucket at the gateway and have OPA\n validate the token, not count requests.\n\n---\n\n## Where these patterns came from\n\nEach is distilled from production policy code, and this server's\nintegration tests evaluate every pattern against OPA.\n\nFor more patterns, see:\n\n- OPA Playground: https://play.openpolicyagent.org/\n- Awesome OPA: https://github.com/anderseknert/awesome-opa\n- Styra DAS pattern library: https://docs.styra.com/das/policies\n";
|
|
9
9
|
//# sourceMappingURL=patterns.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/resources/patterns.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/resources/patterns.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,m/VA+YpB,CAAC"}
|