@orygn/opa-mcp 0.0.0 → 0.1.1

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 (184) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/LICENSE +21 -0
  3. package/README.md +622 -2
  4. package/dist/config.d.ts +58 -0
  5. package/dist/config.d.ts.map +1 -0
  6. package/dist/config.js +72 -0
  7. package/dist/config.js.map +1 -0
  8. package/dist/lib/errors.d.ts +18 -0
  9. package/dist/lib/errors.d.ts.map +1 -0
  10. package/dist/lib/errors.js +25 -0
  11. package/dist/lib/errors.js.map +1 -0
  12. package/dist/lib/logger.d.ts +10 -0
  13. package/dist/lib/logger.d.ts.map +1 -0
  14. package/dist/lib/logger.js +57 -0
  15. package/dist/lib/logger.js.map +1 -0
  16. package/dist/lib/opa-cli.d.ts +226 -0
  17. package/dist/lib/opa-cli.d.ts.map +1 -0
  18. package/dist/lib/opa-cli.js +283 -0
  19. package/dist/lib/opa-cli.js.map +1 -0
  20. package/dist/lib/opa-client.d.ts +47 -0
  21. package/dist/lib/opa-client.d.ts.map +1 -0
  22. package/dist/lib/opa-client.js +101 -0
  23. package/dist/lib/opa-client.js.map +1 -0
  24. package/dist/lib/output.d.ts +17 -0
  25. package/dist/lib/output.d.ts.map +1 -0
  26. package/dist/lib/output.js +21 -0
  27. package/dist/lib/output.js.map +1 -0
  28. package/dist/lib/regal-cli.d.ts +79 -0
  29. package/dist/lib/regal-cli.d.ts.map +1 -0
  30. package/dist/lib/regal-cli.js +122 -0
  31. package/dist/lib/regal-cli.js.map +1 -0
  32. package/dist/lib/security.d.ts +16 -0
  33. package/dist/lib/security.d.ts.map +1 -0
  34. package/dist/lib/security.js +64 -0
  35. package/dist/lib/security.js.map +1 -0
  36. package/dist/lib/subprocess.d.ts +26 -0
  37. package/dist/lib/subprocess.d.ts.map +1 -0
  38. package/dist/lib/subprocess.js +72 -0
  39. package/dist/lib/subprocess.js.map +1 -0
  40. package/dist/lib/tool-helpers.d.ts +46 -0
  41. package/dist/lib/tool-helpers.d.ts.map +1 -0
  42. package/dist/lib/tool-helpers.js +73 -0
  43. package/dist/lib/tool-helpers.js.map +1 -0
  44. package/dist/prompts/index.d.ts +4 -0
  45. package/dist/prompts/index.d.ts.map +1 -0
  46. package/dist/prompts/index.js +147 -0
  47. package/dist/prompts/index.js.map +1 -0
  48. package/dist/resources/index.d.ts +12 -0
  49. package/dist/resources/index.d.ts.map +1 -0
  50. package/dist/resources/index.js +107 -0
  51. package/dist/resources/index.js.map +1 -0
  52. package/dist/resources/patterns.d.ts +9 -0
  53. package/dist/resources/patterns.d.ts.map +1 -0
  54. package/dist/resources/patterns.js +375 -0
  55. package/dist/resources/patterns.js.map +1 -0
  56. package/dist/resources/style-guide.d.ts +6 -0
  57. package/dist/resources/style-guide.d.ts.map +1 -0
  58. package/dist/resources/style-guide.js +187 -0
  59. package/dist/resources/style-guide.js.map +1 -0
  60. package/dist/server.d.ts +34 -0
  61. package/dist/server.d.ts.map +1 -0
  62. package/dist/server.js +125 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/tools/authoring/capabilities.d.ts +11 -0
  65. package/dist/tools/authoring/capabilities.d.ts.map +1 -0
  66. package/dist/tools/authoring/capabilities.js +57 -0
  67. package/dist/tools/authoring/capabilities.js.map +1 -0
  68. package/dist/tools/authoring/check.d.ts +18 -0
  69. package/dist/tools/authoring/check.d.ts.map +1 -0
  70. package/dist/tools/authoring/check.js +71 -0
  71. package/dist/tools/authoring/check.js.map +1 -0
  72. package/dist/tools/authoring/deps.d.ts +8 -0
  73. package/dist/tools/authoring/deps.d.ts.map +1 -0
  74. package/dist/tools/authoring/deps.js +51 -0
  75. package/dist/tools/authoring/deps.js.map +1 -0
  76. package/dist/tools/authoring/format.d.ts +8 -0
  77. package/dist/tools/authoring/format.d.ts.map +1 -0
  78. package/dist/tools/authoring/format.js +44 -0
  79. package/dist/tools/authoring/format.js.map +1 -0
  80. package/dist/tools/authoring/index.d.ts +11 -0
  81. package/dist/tools/authoring/index.d.ts.map +1 -0
  82. package/dist/tools/authoring/index.js +17 -0
  83. package/dist/tools/authoring/index.js.map +1 -0
  84. package/dist/tools/authoring/inspect.d.ts +10 -0
  85. package/dist/tools/authoring/inspect.d.ts.map +1 -0
  86. package/dist/tools/authoring/inspect.js +47 -0
  87. package/dist/tools/authoring/inspect.js.map +1 -0
  88. package/dist/tools/authoring/lint.d.ts +18 -0
  89. package/dist/tools/authoring/lint.d.ts.map +1 -0
  90. package/dist/tools/authoring/lint.js +111 -0
  91. package/dist/tools/authoring/lint.js.map +1 -0
  92. package/dist/tools/authoring/parse.d.ts +7 -0
  93. package/dist/tools/authoring/parse.d.ts.map +1 -0
  94. package/dist/tools/authoring/parse.js +43 -0
  95. package/dist/tools/authoring/parse.js.map +1 -0
  96. package/dist/tools/bundles/build.d.ts +8 -0
  97. package/dist/tools/bundles/build.d.ts.map +1 -0
  98. package/dist/tools/bundles/build.js +87 -0
  99. package/dist/tools/bundles/build.js.map +1 -0
  100. package/dist/tools/bundles/index.d.ts +10 -0
  101. package/dist/tools/bundles/index.d.ts.map +1 -0
  102. package/dist/tools/bundles/index.js +7 -0
  103. package/dist/tools/bundles/index.js.map +1 -0
  104. package/dist/tools/bundles/sign.d.ts +8 -0
  105. package/dist/tools/bundles/sign.d.ts.map +1 -0
  106. package/dist/tools/bundles/sign.js +47 -0
  107. package/dist/tools/bundles/sign.js.map +1 -0
  108. package/dist/tools/evaluation/_shared.d.ts +55 -0
  109. package/dist/tools/evaluation/_shared.d.ts.map +1 -0
  110. package/dist/tools/evaluation/_shared.js +102 -0
  111. package/dist/tools/evaluation/_shared.js.map +1 -0
  112. package/dist/tools/evaluation/bench.d.ts +9 -0
  113. package/dist/tools/evaluation/bench.d.ts.map +1 -0
  114. package/dist/tools/evaluation/bench.js +76 -0
  115. package/dist/tools/evaluation/bench.js.map +1 -0
  116. package/dist/tools/evaluation/compile.d.ts +11 -0
  117. package/dist/tools/evaluation/compile.d.ts.map +1 -0
  118. package/dist/tools/evaluation/compile.js +19 -0
  119. package/dist/tools/evaluation/compile.js.map +1 -0
  120. package/dist/tools/evaluation/eval.d.ts +10 -0
  121. package/dist/tools/evaluation/eval.d.ts.map +1 -0
  122. package/dist/tools/evaluation/eval.js +35 -0
  123. package/dist/tools/evaluation/eval.js.map +1 -0
  124. package/dist/tools/evaluation/index.d.ts +11 -0
  125. package/dist/tools/evaluation/index.d.ts.map +1 -0
  126. package/dist/tools/evaluation/index.js +11 -0
  127. package/dist/tools/evaluation/index.js.map +1 -0
  128. package/dist/tools/evaluation/test.d.ts +28 -0
  129. package/dist/tools/evaluation/test.d.ts.map +1 -0
  130. package/dist/tools/evaluation/test.js +74 -0
  131. package/dist/tools/evaluation/test.js.map +1 -0
  132. package/dist/tools/helpers/describe-policy.d.ts +26 -0
  133. package/dist/tools/helpers/describe-policy.d.ts.map +1 -0
  134. package/dist/tools/helpers/describe-policy.js +76 -0
  135. package/dist/tools/helpers/describe-policy.js.map +1 -0
  136. package/dist/tools/helpers/explain-decision.d.ts +41 -0
  137. package/dist/tools/helpers/explain-decision.d.ts.map +1 -0
  138. package/dist/tools/helpers/explain-decision.js +75 -0
  139. package/dist/tools/helpers/explain-decision.js.map +1 -0
  140. package/dist/tools/helpers/generate-test-skeleton.d.ts +8 -0
  141. package/dist/tools/helpers/generate-test-skeleton.d.ts.map +1 -0
  142. package/dist/tools/helpers/generate-test-skeleton.js +93 -0
  143. package/dist/tools/helpers/generate-test-skeleton.js.map +1 -0
  144. package/dist/tools/helpers/index.d.ts +12 -0
  145. package/dist/tools/helpers/index.d.ts.map +1 -0
  146. package/dist/tools/helpers/index.js +11 -0
  147. package/dist/tools/helpers/index.js.map +1 -0
  148. package/dist/tools/helpers/suggest-fix.d.ts +15 -0
  149. package/dist/tools/helpers/suggest-fix.d.ts.map +1 -0
  150. package/dist/tools/helpers/suggest-fix.js +119 -0
  151. package/dist/tools/helpers/suggest-fix.js.map +1 -0
  152. package/dist/tools/index.d.ts +31 -0
  153. package/dist/tools/index.d.ts.map +1 -0
  154. package/dist/tools/index.js +13 -0
  155. package/dist/tools/index.js.map +1 -0
  156. package/dist/tools/server-management/_shared.d.ts +9 -0
  157. package/dist/tools/server-management/_shared.d.ts.map +1 -0
  158. package/dist/tools/server-management/_shared.js +41 -0
  159. package/dist/tools/server-management/_shared.js.map +1 -0
  160. package/dist/tools/server-management/data.d.ts +4 -0
  161. package/dist/tools/server-management/data.d.ts.map +1 -0
  162. package/dist/tools/server-management/data.js +90 -0
  163. package/dist/tools/server-management/data.js.map +1 -0
  164. package/dist/tools/server-management/decisions.d.ts +4 -0
  165. package/dist/tools/server-management/decisions.d.ts.map +1 -0
  166. package/dist/tools/server-management/decisions.js +87 -0
  167. package/dist/tools/server-management/decisions.js.map +1 -0
  168. package/dist/tools/server-management/index.d.ts +11 -0
  169. package/dist/tools/server-management/index.d.ts.map +1 -0
  170. package/dist/tools/server-management/index.js +11 -0
  171. package/dist/tools/server-management/index.js.map +1 -0
  172. package/dist/tools/server-management/policies.d.ts +4 -0
  173. package/dist/tools/server-management/policies.d.ts.map +1 -0
  174. package/dist/tools/server-management/policies.js +94 -0
  175. package/dist/tools/server-management/policies.js.map +1 -0
  176. package/dist/tools/server-management/status.d.ts +4 -0
  177. package/dist/tools/server-management/status.d.ts.map +1 -0
  178. package/dist/tools/server-management/status.js +82 -0
  179. package/dist/tools/server-management/status.js.map +1 -0
  180. package/dist/types.d.ts +24 -0
  181. package/dist/types.d.ts.map +1 -0
  182. package/dist/types.js +5 -0
  183. package/dist/types.js.map +1 -0
  184. package/package.json +86 -4
@@ -0,0 +1,107 @@
1
+ import { OpaCli } from '../lib/opa-cli.js';
2
+ import { tryParseJson } from '../lib/tool-helpers.js';
3
+ import { STYLE_GUIDE } from './style-guide.js';
4
+ import { PATTERNS } from './patterns.js';
5
+ function categorizeBuiltins(caps) {
6
+ const grouped = {};
7
+ for (const builtin of caps.builtins ?? []) {
8
+ if (!builtin.name)
9
+ continue;
10
+ const categoryList = builtin.categories ?? [];
11
+ if (categoryList.length === 0) {
12
+ grouped['uncategorized'] ??= [];
13
+ grouped['uncategorized'].push(builtin.name);
14
+ continue;
15
+ }
16
+ for (const category of categoryList) {
17
+ grouped[category] ??= [];
18
+ grouped[category].push(builtin.name);
19
+ }
20
+ }
21
+ for (const list of Object.values(grouped))
22
+ list.sort();
23
+ return grouped;
24
+ }
25
+ const SECURITY_SENSITIVE = new Set([
26
+ 'http.send',
27
+ 'opa.runtime',
28
+ 'crypto.x509.parse_certificates',
29
+ 'crypto.x509.parse_certificate_request',
30
+ 'crypto.x509.parse_keypair',
31
+ 'crypto.x509.parse_rsa_private_key',
32
+ 'crypto.parse_private_keys',
33
+ ]);
34
+ async function buildBuiltinsResource(opa) {
35
+ const result = await opa.capabilities({ current: true });
36
+ if (result.exitCode !== 0) {
37
+ return JSON.stringify({
38
+ error: 'opa capabilities --current failed',
39
+ stderr: result.stderr.trim(),
40
+ }, null, 2);
41
+ }
42
+ const caps = tryParseJson(result.stdout);
43
+ if (!caps) {
44
+ return JSON.stringify({ error: 'opa capabilities produced no parseable JSON' }, null, 2);
45
+ }
46
+ const grouped = categorizeBuiltins(caps);
47
+ const sensitive = (caps.builtins ?? [])
48
+ .map((b) => b.name)
49
+ .filter((n) => typeof n === 'string' && SECURITY_SENSITIVE.has(n))
50
+ .sort();
51
+ return JSON.stringify({
52
+ version_note: 'Derived at read time from `opa capabilities --current`. Reflects the OPA build linked into this MCP server, not necessarily the OPA the user has installed locally.',
53
+ builtin_count: caps.builtins?.length ?? 0,
54
+ categories: grouped,
55
+ future_keywords: caps.future_keywords ?? [],
56
+ features: caps.features ?? [],
57
+ wasm_abi_versions: caps.wasm_abi_versions ?? [],
58
+ security_sensitive_builtins: sensitive,
59
+ security_note: 'The functions in `security_sensitive_builtins` introduce side effects (HTTP fetches, crypto operations, runtime introspection). Use them sparingly in policy hot paths and confirm operator approval.',
60
+ }, null, 2);
61
+ }
62
+ export function registerResources(server, config) {
63
+ const opa = new OpaCli(config);
64
+ server.registerResource('opa-builtins', 'opa://builtins', {
65
+ title: 'OPA built-in functions',
66
+ description: 'The OPA built-in function catalog, categorized by namespace, with security-sensitive functions flagged. Derived at read time from `opa capabilities --current` so the list stays in sync with the actual OPA binary.',
67
+ mimeType: 'application/json',
68
+ }, async (uri) => {
69
+ const content = await buildBuiltinsResource(opa);
70
+ return {
71
+ contents: [
72
+ {
73
+ uri: uri.href,
74
+ mimeType: 'application/json',
75
+ text: content,
76
+ },
77
+ ],
78
+ };
79
+ });
80
+ server.registerResource('opa-style-guide', 'opa://style-guide', {
81
+ title: 'Rego style guide',
82
+ description: 'Condensed Rego style guide adapted from the Styra reference: rego.v1, package layout, naming, default-deny, comprehensions vs every, schema annotations.',
83
+ mimeType: 'text/markdown',
84
+ }, (uri) => Promise.resolve({
85
+ contents: [
86
+ {
87
+ uri: uri.href,
88
+ mimeType: 'text/markdown',
89
+ text: STYLE_GUIDE,
90
+ },
91
+ ],
92
+ }));
93
+ server.registerResource('opa-patterns', 'opa://patterns', {
94
+ title: 'Rego pattern library',
95
+ description: 'Curated Rego patterns: RBAC, ABAC, Kubernetes admission, IaC gates, API authorization, rate limiting. Each pattern includes when to use it, a full working example, a test, and common pitfalls.',
96
+ mimeType: 'text/markdown',
97
+ }, (uri) => Promise.resolve({
98
+ contents: [
99
+ {
100
+ uri: uri.href,
101
+ mimeType: 'text/markdown',
102
+ text: PATTERNS,
103
+ },
104
+ ],
105
+ }));
106
+ }
107
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resources/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Curated Rego pattern library. Each pattern includes:
3
+ * - When to use it
4
+ * - A working example
5
+ * - A test
6
+ * - Common pitfalls
7
+ */
8
+ export declare const PATTERNS = "# Rego pattern library\n\nCommon Rego patterns with working examples, tests, and pitfalls. Each\npattern is self-contained \u2014 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 \u2014 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? \u2014 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 \"anonymous request\" in (rbac.deny_reasons with input as {\"action\": \"read\"})\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 \u2014 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 \u2014\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 \u2014 no auth required.\npublic_endpoints := {\n {\"method\": \"GET\", \"path\": [\"health\"]},\n {\"method\": \"GET\", \"path\": [\"version\"]},\n}\n\nallow if some _ in public_endpoints; matches_endpoint(_)\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\ncount := count(current_window)\n\nallow if count < limit\n\ndeny_reason := sprintf(\n \"rate limit exceeded: %d requests in last %d seconds (limit %d)\",\n [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* \u2014 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";
9
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/resources/patterns.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,glTA8WpB,CAAC"}
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Curated Rego pattern library. Each pattern includes:
3
+ * - When to use it
4
+ * - A working example
5
+ * - A test
6
+ * - Common pitfalls
7
+ */
8
+ export const PATTERNS = `# Rego pattern library
9
+
10
+ Common Rego patterns with working examples, tests, and pitfalls. Each
11
+ pattern is self-contained — copy, adapt, and ship.
12
+
13
+ ---
14
+
15
+ ## 1. Role-based access control (RBAC)
16
+
17
+ **When to use:** the simplest authorization model. Every user has one
18
+ or more roles; each role grants a set of actions on a set of
19
+ resources. Sufficient for ~80% of internal applications.
20
+
21
+ \`\`\`rego
22
+ package rbac
23
+
24
+ import rego.v1
25
+
26
+ default allow := false
27
+
28
+ # Permissions table — extend as roles evolve.
29
+ permissions := {
30
+ "admin": {"read", "write", "delete", "manage_users"},
31
+ "editor": {"read", "write"},
32
+ "viewer": {"read"},
33
+ }
34
+
35
+ allow if {
36
+ some role in input.user.roles
37
+ input.action in permissions[role]
38
+ }
39
+
40
+ # Why was it denied? — useful for audit logs.
41
+ deny_reasons contains reason if {
42
+ not allow
43
+ input.user
44
+ reason := sprintf(
45
+ "user %q has roles %v, none grant %q",
46
+ [input.user.id, input.user.roles, input.action],
47
+ )
48
+ }
49
+
50
+ deny_reasons contains "anonymous request" if {
51
+ not allow
52
+ not input.user
53
+ }
54
+ \`\`\`
55
+
56
+ **Test:**
57
+
58
+ \`\`\`rego
59
+ package rbac_test
60
+
61
+ import rego.v1
62
+ import data.rbac
63
+
64
+ test_admin_can_delete if {
65
+ rbac.allow with input as {
66
+ "user": {"id": "alice", "roles": ["admin"]},
67
+ "action": "delete",
68
+ }
69
+ }
70
+
71
+ test_viewer_cannot_delete if {
72
+ not rbac.allow with input as {
73
+ "user": {"id": "bob", "roles": ["viewer"]},
74
+ "action": "delete",
75
+ }
76
+ }
77
+
78
+ test_anonymous_denied if {
79
+ not rbac.allow with input as {"action": "read"}
80
+ "anonymous request" in (rbac.deny_reasons with input as {"action": "read"})
81
+ }
82
+ \`\`\`
83
+
84
+ **Pitfalls:**
85
+ - The \`permissions\` table grows unbounded. Move to data files
86
+ (\`data.permissions\`) once you have more than ~20 roles.
87
+ - Roles overlap with groups in real auth systems; map at the boundary
88
+ rather than carrying both.
89
+
90
+ ---
91
+
92
+ ## 2. Attribute-based access control (ABAC)
93
+
94
+ **When to use:** when "who" alone isn't enough — decisions also depend
95
+ on resource attributes (ownership, tenant, sensitivity) and context
96
+ (time of day, source IP).
97
+
98
+ \`\`\`rego
99
+ package abac
100
+
101
+ import rego.v1
102
+
103
+ default allow := false
104
+
105
+ # A user can read any resource they own.
106
+ allow if {
107
+ input.action == "read"
108
+ input.resource.owner_id == input.user.id
109
+ }
110
+
111
+ # A user can read shared resources at their organization.
112
+ allow if {
113
+ input.action == "read"
114
+ input.resource.shared
115
+ input.resource.org_id == input.user.org_id
116
+ }
117
+
118
+ # Admins can do anything within their organization.
119
+ allow if {
120
+ "admin" in input.user.roles
121
+ input.resource.org_id == input.user.org_id
122
+ }
123
+
124
+ # Don't show "secret" resources to anyone outside the owner's
125
+ # organization, even admins.
126
+ allow := false if {
127
+ input.resource.classification == "secret"
128
+ input.resource.org_id != input.user.org_id
129
+ }
130
+ \`\`\`
131
+
132
+ **Test:**
133
+
134
+ \`\`\`rego
135
+ package abac_test
136
+
137
+ import rego.v1
138
+ import data.abac
139
+
140
+ test_owner_reads_own if {
141
+ abac.allow with input as {
142
+ "action": "read",
143
+ "user": {"id": "u1", "org_id": "o1"},
144
+ "resource": {"owner_id": "u1", "org_id": "o1"},
145
+ }
146
+ }
147
+
148
+ test_admin_blocked_from_secret_in_other_org if {
149
+ not abac.allow with input as {
150
+ "action": "read",
151
+ "user": {"id": "u1", "org_id": "o1", "roles": ["admin"]},
152
+ "resource": {
153
+ "owner_id": "u2",
154
+ "org_id": "o2",
155
+ "classification": "secret",
156
+ },
157
+ }
158
+ }
159
+ \`\`\`
160
+
161
+ **Pitfalls:**
162
+ - Multiple \`allow\` rules combine with logical OR. Use explicit
163
+ \`allow := false if ...\` to *override* an allow.
164
+ - Don't compute attributes inside the policy; compute them at the
165
+ boundary and pass via \`input\`.
166
+
167
+ ---
168
+
169
+ ## 3. Kubernetes admission control
170
+
171
+ **When to use:** validate or mutate Kubernetes resources at admission
172
+ time. Run as a Gatekeeper, OPA-as-a-webhook, or Kyverno-equivalent
173
+ policy layer.
174
+
175
+ \`\`\`rego
176
+ package k8s.admission
177
+
178
+ import rego.v1
179
+
180
+ # Reject pods without resource limits.
181
+ deny contains msg if {
182
+ input.request.kind.kind == "Pod"
183
+ container := input.request.object.spec.containers[_]
184
+ not container.resources.limits.memory
185
+ msg := sprintf(
186
+ "Pod %q container %q is missing resources.limits.memory",
187
+ [input.request.object.metadata.name, container.name],
188
+ )
189
+ }
190
+
191
+ # Reject privileged containers in production.
192
+ deny contains msg if {
193
+ input.request.kind.kind == "Pod"
194
+ input.request.namespace != "kube-system"
195
+ container := input.request.object.spec.containers[_]
196
+ container.securityContext.privileged == true
197
+ msg := sprintf(
198
+ "privileged containers are not allowed: %q in %q",
199
+ [container.name, input.request.object.metadata.name],
200
+ )
201
+ }
202
+ \`\`\`
203
+
204
+ **Pitfalls:**
205
+ - Use \`input.request.object\` for the resource being admitted; the
206
+ envelope shape comes from Kubernetes, not your control.
207
+ - For mutations, return a JSON Patch via the \`patch\` field. Test
208
+ patches with the actual admission webhook in dry-run mode before
209
+ enforcing.
210
+ - Iteration order is undefined; never rely on \`containers[0]\` to mean
211
+ anything specific.
212
+
213
+ ---
214
+
215
+ ## 4. Infrastructure-as-Code gates (Terraform)
216
+
217
+ **When to use:** validate Terraform plans before apply. Catch overly
218
+ permissive IAM, public S3 buckets, missing encryption.
219
+
220
+ \`\`\`rego
221
+ package terraform
222
+
223
+ import rego.v1
224
+
225
+ # Reject S3 buckets without server-side encryption.
226
+ deny contains msg if {
227
+ resource := input.resource_changes[_]
228
+ resource.type == "aws_s3_bucket"
229
+ after := resource.change.after
230
+ not after.server_side_encryption_configuration
231
+ msg := sprintf(
232
+ "S3 bucket %q has no server-side encryption configured",
233
+ [resource.address],
234
+ )
235
+ }
236
+
237
+ # Reject IAM policies with action "*" on resource "*".
238
+ deny contains msg if {
239
+ resource := input.resource_changes[_]
240
+ resource.type == "aws_iam_policy"
241
+ policy := json.unmarshal(resource.change.after.policy)
242
+ statement := policy.Statement[_]
243
+ statement.Effect == "Allow"
244
+ "*" in statement.Action
245
+ statement.Resource == "*"
246
+ msg := sprintf("IAM policy %q grants Allow * on *", [resource.address])
247
+ }
248
+ \`\`\`
249
+
250
+ **Pitfalls:**
251
+ - Terraform plan JSON is verbose and version-specific. Pin the
252
+ \`terraform plan -json\` schema you target.
253
+ - \`resource_changes[_].change.after\` may be \`null\` for destroys —
254
+ guard against it.
255
+ - For wide-radius changes, use \`opa exec --decision\` against a plan
256
+ file in CI, not the live API.
257
+
258
+ ---
259
+
260
+ ## 5. API authorization (HTTP request gating)
261
+
262
+ **When to use:** at the API gateway / reverse proxy layer, validate
263
+ each request against the caller's identity and the requested
264
+ endpoint.
265
+
266
+ \`\`\`rego
267
+ package api.authz
268
+
269
+ import rego.v1
270
+
271
+ default allow := false
272
+
273
+ # Public endpoints — no auth required.
274
+ public_endpoints := {
275
+ {"method": "GET", "path": ["health"]},
276
+ {"method": "GET", "path": ["version"]},
277
+ }
278
+
279
+ allow if some _ in public_endpoints; matches_endpoint(_)
280
+
281
+ # Authenticated reads on resources the user has access to.
282
+ allow if {
283
+ input.method == "GET"
284
+ input.user
285
+ user_can_read(input.user, input.path)
286
+ }
287
+
288
+ # Authenticated writes only with specific scopes.
289
+ allow if {
290
+ input.method in {"POST", "PUT", "PATCH", "DELETE"}
291
+ input.user
292
+ "write" in input.user.scopes
293
+ user_can_write(input.user, input.path)
294
+ }
295
+
296
+ matches_endpoint(spec) if {
297
+ spec.method == input.method
298
+ spec.path == input.path
299
+ }
300
+
301
+ user_can_read(user, path) if {
302
+ path[0] == "users"
303
+ user.id == path[1]
304
+ }
305
+
306
+ user_can_write(user, path) if {
307
+ path[0] == "users"
308
+ user.id == path[1]
309
+ }
310
+ \`\`\`
311
+
312
+ **Pitfalls:**
313
+ - \`input.path\` is typically an array (\`["users", "alice"]\`), not a
314
+ string. Build it consistently at the gateway.
315
+ - Path-prefix matching is easy; full pattern matching is not. For
316
+ parameterized routes, decode at the gateway and pass structured
317
+ fields.
318
+
319
+ ---
320
+
321
+ ## 6. Rate limiting (with sliding window data)
322
+
323
+ **When to use:** allow N requests per principal per window. Lightweight
324
+ limit enforcement; for high throughput, push to a dedicated rate
325
+ limiter.
326
+
327
+ \`\`\`rego
328
+ package rate
329
+
330
+ import rego.v1
331
+
332
+ # Configuration: 100 requests per principal per 60-second window.
333
+ limit := 100
334
+ window_seconds := 60
335
+
336
+ # input.now is a unix timestamp in nanoseconds.
337
+ # data.requests[principal] is an array of nanosecond timestamps.
338
+
339
+ current_window := requests if {
340
+ requests := [t | some t in data.requests[input.principal]; t > input.now - window_seconds * 1000000000]
341
+ }
342
+
343
+ count := count(current_window)
344
+
345
+ allow if count < limit
346
+
347
+ deny_reason := sprintf(
348
+ "rate limit exceeded: %d requests in last %d seconds (limit %d)",
349
+ [count, window_seconds, limit],
350
+ ) if not allow
351
+ \`\`\`
352
+
353
+ **Pitfalls:**
354
+ - \`data.requests\` grows unbounded unless the writer prunes outside the
355
+ window. Schedule prune on every write.
356
+ - This pattern is *advisory* — under load, two concurrent decisions
357
+ can both see \`count == limit - 1\` and both allow. For strict
358
+ limits, use a Lua/Redis token bucket at the gateway and have OPA
359
+ validate the token, not count requests.
360
+
361
+ ---
362
+
363
+ ## Where these patterns came from
364
+
365
+ Each is distilled from production policy code. The full Rego files,
366
+ tests, and policy data fixtures live in this server's GitHub
367
+ repository under \`tests/fixtures/policies/\`.
368
+
369
+ For more patterns, see:
370
+
371
+ - OPA Playground: https://play.openpolicyagent.org/
372
+ - Awesome OPA: https://github.com/anderseknert/awesome-opa
373
+ - Styra DAS pattern library: https://docs.styra.com/das/policies
374
+ `;
375
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/resources/patterns.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8WvB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Curated Rego style guide. Adapted from the Styra reference plus the
3
+ * OPA team's own conventions, condensed for LLM consumption.
4
+ */
5
+ export declare const STYLE_GUIDE = "# Rego style guide\n\nA condensed reference for writing idiomatic, maintainable Rego. Adapted\nfrom the Styra style guide and the OPA project's conventions.\n\n## Always import `rego.v1`\n\nEvery `.rego` file should start with the v1 import. It enables the\nmodern syntax (`if`, `contains`, `every`) and disables the older\nimplicit-membership operator that's a frequent source of bugs.\n\n```rego\npackage authz\n\nimport rego.v1\n```\n\nOPA 1.0 (released January 2025) treats this as the default, but\nincluding it explicitly makes intent clear and keeps the file portable\nto older OPA installations.\n\n## Package layout mirrors directory layout\n\nMatch `package foo.bar` with the file path `foo/bar/main.rego` (or\n`foo/bar/<anything>.rego`). Regal flags mismatches as\n`directory-package-mismatch`.\n\nFor a multi-file package, group by responsibility:\n\n```\nauthz/\n main.rego # principal decision (allow / deny)\n rbac.rego # role-based access checks\n abac.rego # attribute-based access checks\n helpers.rego # shared helpers, no decisions\n main_test.rego # tests for principal decisions\n rbac_test.rego # tests for rbac\n```\n\nTests live in `*_test.rego` siblings of the source they test, in the\nsame package or a `<package>_test` package.\n\n## Naming\n\n- Rule names: `snake_case`.\n- Boolean rules: read like predicates \u2014 `allow`, `is_admin`,\n `should_log`. Avoid `is_not_blocked` (double negative).\n- Set/object rules: read like nouns \u2014 `grants`, `roles`, `reasons`.\n- Helper rules: `_` prefix is *not* a convention; just give them names\n that explain what they return.\n\nNames that conflict with reserved words (`type`, `time`, `input`) won't\nparse. Names matching builtins (`http`, `json`) shadow them.\n\n## Default deny\n\nEvery principal decision should have a default fallback so the policy\nreturns a value even when no rule matches.\n\n```rego\ndefault allow := false\n\nallow if {\n input.user.role == \"admin\"\n}\n```\n\nThe same applies to set-valued reasons:\n\n```rego\ndeny contains reason if {\n not allow\n reason := \"not authorized\"\n}\n```\n\n## Comprehensions vs `every`\n\nWhen you need a value derived from a collection, use a comprehension:\n\n```rego\nadmin_users := {u | some u in input.users; u.role == \"admin\"}\n```\n\nWhen you need to assert that a property holds for every element, use\n`every` (introduced in OPA 0.41):\n\n```rego\nallow if {\n every claim in input.token.claims {\n claim.verified == true\n }\n}\n```\n\n`every` is clearer than the older `not <comprehension>` idiom and\nshort-circuits on the first failure.\n\n## Annotations\n\nPublic rules deserve a metadata block so consumers know what they do.\n\n```rego\n# METADATA\n# title: Authorization decision for HTTP requests\n# description: |\n# Returns true when the requesting principal has a role with a\n# permission entry for the requested action on the requested\n# resource. Anonymous requests always deny.\n# entrypoint: true\nallow if {\n some role in input.principal.roles\n permission_grants[role][input.action]\n}\n```\n\nAnnotations show up in `opa inspect`, the registry, and editor\nhovers. They are also extracted by `rego_describe_policy`.\n\n## Schema annotations\n\nFor policies that are sensitive to input shape, attach a JSON Schema:\n\n```rego\n# METADATA\n# schemas:\n# - input: schema.input\nallow if {\n input.user.id != \"\"\n}\n```\n\nCombined with `opa check --schema`, this turns input-shape mismatches\ninto compile errors instead of runtime undefineds.\n\n## Anti-patterns\n\n- **`http.send` in the decision path.** Each call adds round-trip\n latency. If you must, scope it to a small, cacheable read.\n- **Deep `with` chains.** `x with input as ... with data as ...` more\n than two layers deep is a smell \u2014 the test is doing too much. Split.\n- **`print` and `trace`.** Useful in development; remove before\n shipping. Regal flags both as `print-or-trace-call`.\n- **Mixing `if` and the legacy implicit form in the same file.** Pick\n one. With `rego.v1` imported you cannot use the implicit form.\n\n## Tests\n\n`opa test` runs every rule whose name starts with `test_`. A test\neither evaluates to true (pass) or fails to evaluate (fail). The\ncommon shape:\n\n```rego\npackage authz_test\n\nimport rego.v1\nimport data.authz\n\ntest_admin_can_delete if {\n authz.allow with input as {\n \"user\": {\"role\": \"admin\"},\n \"action\": \"delete\",\n }\n}\n\ntest_viewer_cannot_delete if {\n not authz.allow with input as {\n \"user\": {\"role\": \"viewer\"},\n \"action\": \"delete\",\n }\n}\n```\n\nRun with `opa test -v ./...` for verbose output. Add `--coverage` to\nverify which lines were exercised.\n\n## References\n\n- Official Rego style guide: https://docs.styra.com/regal/rego-style-guide\n- OPA documentation: https://www.openpolicyagent.org/docs/latest/\n- Regal linter rules: https://docs.styra.com/regal/rules\n";
6
+ //# sourceMappingURL=style-guide.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"style-guide.d.ts","sourceRoot":"","sources":["../../src/resources/style-guide.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,WAAW,+6JAqLvB,CAAC"}