@highflame/policy 2.0.9 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/_schemas/guardrails/context.json +435 -0
  2. package/_schemas/guardrails/schema.cedarschema +225 -0
  3. package/_schemas/guardrails/templates/defaults/agentic_safety.cedar +94 -0
  4. package/_schemas/guardrails/templates/defaults/baseline.cedar +24 -0
  5. package/_schemas/guardrails/templates/defaults/injection.cedar +70 -0
  6. package/_schemas/guardrails/templates/defaults/pii.cedar +48 -0
  7. package/_schemas/guardrails/templates/defaults/secrets.cedar +40 -0
  8. package/_schemas/guardrails/templates/defaults/semantic.cedar +59 -0
  9. package/_schemas/guardrails/templates/defaults/tool_risk.cedar +58 -0
  10. package/_schemas/guardrails/templates/defaults/toxicity.cedar +76 -0
  11. package/_schemas/guardrails/templates/mcp_tool_permissions.cedar +84 -0
  12. package/_schemas/guardrails/templates/profiles/chat_assistant/privacy.cedar +22 -0
  13. package/_schemas/guardrails/templates/profiles/chat_assistant/security.cedar +35 -0
  14. package/_schemas/guardrails/templates/profiles/chat_assistant/trust_safety.cedar +43 -0
  15. package/_schemas/guardrails/templates/profiles/chat_assistant.cedar +85 -0
  16. package/_schemas/guardrails/templates/profiles/code_agent/agentic_security.cedar +109 -0
  17. package/_schemas/guardrails/templates/profiles/code_agent/security.cedar +22 -0
  18. package/_schemas/guardrails/templates/profiles/code_agent.cedar +125 -0
  19. package/_schemas/guardrails/templates/profiles/data_pipeline/agentic_security.cedar +38 -0
  20. package/_schemas/guardrails/templates/profiles/data_pipeline/privacy.cedar +40 -0
  21. package/_schemas/guardrails/templates/profiles/data_pipeline/security.cedar +49 -0
  22. package/_schemas/guardrails/templates/profiles/data_pipeline.cedar +111 -0
  23. package/_schemas/guardrails/templates/templates.json +213 -0
  24. package/_schemas/overwatch/context.json +54 -54
  25. package/_schemas/overwatch/schema.cedarschema +77 -68
  26. package/dist/builder.d.ts +106 -13
  27. package/dist/builder.js +103 -34
  28. package/dist/engine.d.ts +20 -2
  29. package/dist/engine.js +50 -20
  30. package/dist/entities.gen.d.ts +4 -0
  31. package/dist/entities.gen.js +4 -0
  32. package/dist/explain.d.ts +150 -0
  33. package/dist/explain.js +363 -0
  34. package/dist/guardrails-context.gen.d.ts +49 -0
  35. package/dist/guardrails-context.gen.js +50 -0
  36. package/dist/guardrails-defaults.gen.d.ts +61 -0
  37. package/dist/guardrails-defaults.gen.js +1278 -0
  38. package/dist/guardrails-entities.gen.d.ts +11 -0
  39. package/dist/guardrails-entities.gen.js +37 -0
  40. package/dist/index.d.ts +6 -1
  41. package/dist/index.js +6 -1
  42. package/dist/overwatch-defaults.gen.js +122 -2
  43. package/dist/parser.js +136 -4
  44. package/dist/schema.gen.d.ts +1 -1
  45. package/dist/schema.gen.js +6 -0
  46. package/dist/service-schemas.gen.d.ts +15 -11
  47. package/dist/service-schemas.gen.js +509 -84
  48. package/dist/types.d.ts +6 -1
  49. package/dist/types.js +6 -1
  50. package/package.json +5 -1
package/dist/engine.d.ts CHANGED
@@ -38,11 +38,21 @@ export interface EngineOptions {
38
38
  export declare class InputValidationError extends Error {
39
39
  constructor(message: string);
40
40
  }
41
+ /**
42
+ * A policy that contributed to the authorization decision,
43
+ * enriched with its Cedar annotations.
44
+ */
45
+ export interface DeterminingPolicy {
46
+ /** Policy ID (from @id annotation or positional fallback) */
47
+ id: string;
48
+ /** All annotations from this policy as key-value pairs */
49
+ annotations: Record<string, string>;
50
+ }
41
51
  export declare class Decision {
42
52
  readonly effect: "Allow" | "Deny";
43
- readonly determining_policies: string[];
53
+ readonly determining_policies: DeterminingPolicy[];
44
54
  readonly reason?: string;
45
- constructor(effect: "Allow" | "Deny", determining_policies: string[], reason?: string);
55
+ constructor(effect: "Allow" | "Deny", determining_policies: DeterminingPolicy[], reason?: string);
46
56
  isAllowed(): boolean;
47
57
  isDenied(): boolean;
48
58
  }
@@ -63,6 +73,7 @@ export interface EvaluateRequest {
63
73
  */
64
74
  export declare class PolicyEngine {
65
75
  private policySet;
76
+ private policyAnnotations;
66
77
  private schema;
67
78
  private options;
68
79
  private limits;
@@ -74,11 +85,13 @@ export declare class PolicyEngine {
74
85
  /**
75
86
  * Load a single Cedar policy text string.
76
87
  * Uses @id annotations as policy IDs when available.
88
+ * Stores all annotations per policy for enriching evaluation results.
77
89
  */
78
90
  loadPolicy(policy: string): void;
79
91
  /**
80
92
  * Load multiple Cedar policy texts (concatenated with newlines).
81
93
  * Uses @id annotations as policy IDs when available.
94
+ * Stores all annotations per policy for enriching evaluation results.
82
95
  */
83
96
  loadPolicies(policies: string[]): void;
84
97
  /**
@@ -89,6 +102,11 @@ export declare class PolicyEngine {
89
102
  * Load schema from a Cedar schema file.
90
103
  */
91
104
  loadSchemaFromFile(path: string): void;
105
+ /**
106
+ * Returns stored annotations for a given policy ID.
107
+ * Returns undefined if the policy ID is not found.
108
+ */
109
+ getPolicyAnnotations(policyId: string): Record<string, string> | undefined;
92
110
  /**
93
111
  * Evaluate a policy request and return a decision.
94
112
  * @throws InputValidationError if context validation fails
package/dist/engine.js CHANGED
@@ -147,39 +147,51 @@ function parseActionString(action) {
147
147
  }
148
148
  return { type: actionType, id: actionId };
149
149
  }
150
+ /**
151
+ * Regex to extract Cedar annotations from policy text.
152
+ * Matches `@key("value")` with proper escaped-quote handling.
153
+ */
154
+ const ANNOTATION_REGEX = /@(\w+)\("((?:[^"\\]|\\.)*)"\)/g;
155
+ /**
156
+ * Extract all `@key("value")` annotations from a single Cedar policy text string.
157
+ */
158
+ function extractAnnotationsFromText(policyText) {
159
+ const annotations = {};
160
+ let match;
161
+ const regex = new RegExp(ANNOTATION_REGEX.source, ANNOTATION_REGEX.flags);
162
+ while ((match = regex.exec(policyText)) !== null) {
163
+ // Unescape the annotation value (reverse Cedar escaping)
164
+ annotations[match[1]] = match[2].replace(/\\"/g, '"').replace(/\\\\/g, '\\');
165
+ }
166
+ return annotations;
167
+ }
150
168
  /**
151
169
  * Extract @id annotations from Cedar policy text and return a
152
- * Record<PolicyId, Policy> for cedar-wasm. This ensures that
153
- * determining_policies in evaluation results use the @id values
154
- * instead of positional IDs (policy0, policy1...).
155
- *
156
- * Falls back to the raw string when no @id annotations are found.
170
+ * Record<PolicyId, Policy> for cedar-wasm, along with a map of
171
+ * all annotations per policy for enriching evaluation results.
157
172
  */
158
- function extractPolicyIds(policyText) {
173
+ function extractPolicies(policyText) {
174
+ const annotationsMap = new Map();
159
175
  const parts = cedar.policySetTextToParts(policyText);
160
176
  if (parts.type !== "success" || parts.policies.length === 0) {
161
- return policyText;
177
+ return { policySet: policyText, annotations: annotationsMap };
162
178
  }
163
179
  const policyMap = {};
164
- let hasAnnotationIds = false;
165
180
  for (let i = 0; i < parts.policies.length; i++) {
166
181
  const policy = parts.policies[i];
167
- const idMatch = policy.match(/@id\("([^"]+)"\)/);
168
- if (idMatch) {
169
- policyMap[idMatch[1]] = policy;
170
- hasAnnotationIds = true;
171
- }
172
- else {
173
- policyMap[`policy${i}`] = policy;
174
- }
182
+ const policyAnnotations = extractAnnotationsFromText(policy);
183
+ const policyId = policyAnnotations["id"] || `policy${i}`;
184
+ policyMap[policyId] = policy;
185
+ annotationsMap.set(policyId, policyAnnotations);
175
186
  }
176
- return hasAnnotationIds ? policyMap : policyText;
187
+ return { policySet: policyMap, annotations: annotationsMap };
177
188
  }
178
189
  /**
179
190
  * PolicyEngine wraps cedar-wasm with Highflame schema types.
180
191
  */
181
192
  export class PolicyEngine {
182
193
  policySet = "";
194
+ policyAnnotations = new Map();
183
195
  schema;
184
196
  options;
185
197
  limits;
@@ -203,16 +215,22 @@ export class PolicyEngine {
203
215
  /**
204
216
  * Load a single Cedar policy text string.
205
217
  * Uses @id annotations as policy IDs when available.
218
+ * Stores all annotations per policy for enriching evaluation results.
206
219
  */
207
220
  loadPolicy(policy) {
208
- this.policySet = extractPolicyIds(policy);
221
+ const extracted = extractPolicies(policy);
222
+ this.policySet = extracted.policySet;
223
+ this.policyAnnotations = extracted.annotations;
209
224
  }
210
225
  /**
211
226
  * Load multiple Cedar policy texts (concatenated with newlines).
212
227
  * Uses @id annotations as policy IDs when available.
228
+ * Stores all annotations per policy for enriching evaluation results.
213
229
  */
214
230
  loadPolicies(policies) {
215
- this.policySet = extractPolicyIds(policies.join("\n"));
231
+ const extracted = extractPolicies(policies.join("\n"));
232
+ this.policySet = extracted.policySet;
233
+ this.policyAnnotations = extracted.annotations;
216
234
  }
217
235
  /**
218
236
  * Load schema from a Cedar schema string.
@@ -227,6 +245,13 @@ export class PolicyEngine {
227
245
  const content = fs.readFileSync(path, "utf-8");
228
246
  this.loadSchema(content);
229
247
  }
248
+ /**
249
+ * Returns stored annotations for a given policy ID.
250
+ * Returns undefined if the policy ID is not found.
251
+ */
252
+ getPolicyAnnotations(policyId) {
253
+ return this.policyAnnotations.get(policyId);
254
+ }
230
255
  /**
231
256
  * Evaluate a policy request and return a decision.
232
257
  * @throws InputValidationError if context validation fails
@@ -276,7 +301,12 @@ export class PolicyEngine {
276
301
  if (result.type === "failure") {
277
302
  return new Decision("Deny", [], result.errors.map(e => e.message).join("; "));
278
303
  }
279
- return new Decision(result.response.decision === "allow" ? "Allow" : "Deny", result.response.diagnostics.reason, result.response.diagnostics.errors.length > 0
304
+ // Build enriched DeterminingPolicy objects with annotations
305
+ const determiningPolicies = result.response.diagnostics.reason.map(id => ({
306
+ id,
307
+ annotations: this.policyAnnotations.get(id) || {},
308
+ }));
309
+ return new Decision(result.response.decision === "allow" ? "Allow" : "Deny", determiningPolicies, result.response.diagnostics.errors.length > 0
280
310
  ? result.response.diagnostics.errors.map(e => e.error.message).join("; ")
281
311
  : undefined);
282
312
  }
@@ -2,7 +2,9 @@
2
2
  * Entity types defined in the Highflame Cedar schema.
3
3
  */
4
4
  export declare const EntityType: {
5
+ readonly Account: "Account";
5
6
  readonly Agent: "Agent";
7
+ readonly App: "App";
6
8
  readonly Artifact: "Artifact";
7
9
  readonly ExternalAPI: "ExternalAPI";
8
10
  readonly FilePath: "FilePath";
@@ -12,12 +14,14 @@ export declare const EntityType: {
12
14
  readonly Memory: "Memory";
13
15
  readonly Model: "Model";
14
16
  readonly Package: "Package";
17
+ readonly Project: "Project";
15
18
  readonly Repository: "Repository";
16
19
  readonly Resource: "Resource";
17
20
  readonly ResponseData: "ResponseData";
18
21
  readonly Scanner: "Scanner";
19
22
  readonly Server: "Server";
20
23
  readonly Service: "Service";
24
+ readonly Session: "Session";
21
25
  readonly Tool: "Tool";
22
26
  readonly User: "User";
23
27
  };
@@ -4,7 +4,9 @@
4
4
  * Entity types defined in the Highflame Cedar schema.
5
5
  */
6
6
  export const EntityType = {
7
+ Account: 'Account',
7
8
  Agent: 'Agent',
9
+ App: 'App',
8
10
  Artifact: 'Artifact',
9
11
  ExternalAPI: 'ExternalAPI',
10
12
  FilePath: 'FilePath',
@@ -14,12 +16,14 @@ export const EntityType = {
14
16
  Memory: 'Memory',
15
17
  Model: 'Model',
16
18
  Package: 'Package',
19
+ Project: 'Project',
17
20
  Repository: 'Repository',
18
21
  Resource: 'Resource',
19
22
  ResponseData: 'ResponseData',
20
23
  Scanner: 'Scanner',
21
24
  Server: 'Server',
22
25
  Service: 'Service',
26
+ Session: 'Session',
23
27
  Tool: 'Tool',
24
28
  User: 'User',
25
29
  };
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Policy Decision Explanation
3
+ *
4
+ * Provides structured explanations for Cedar policy decisions by matching
5
+ * determining policies against their structured conditions and the request context.
6
+ *
7
+ * Browser-safe — no WASM or Node.js dependencies.
8
+ */
9
+ import type { PolicyRule, PolicyEffect, ConditionOperator, ConditionExpression } from "./builder.js";
10
+ /**
11
+ * Duck-typed decision input — accepts the Decision class from engine.ts
12
+ * without importing cedar-wasm (keeps this module browser-safe).
13
+ */
14
+ export interface DecisionInput {
15
+ effect: "Allow" | "Deny";
16
+ determining_policies: Array<{
17
+ id: string;
18
+ annotations: Record<string, string>;
19
+ }>;
20
+ }
21
+ /**
22
+ * Result of explaining a policy decision.
23
+ */
24
+ export interface ExplainedDecision {
25
+ /** The original decision effect */
26
+ effect: "Allow" | "Deny";
27
+ /** Enriched explanations for each determining policy */
28
+ explanations: PolicyExplanation[];
29
+ /** Determining policy IDs that had no matching rule in the provided rules array */
30
+ unmatched_policies: string[];
31
+ }
32
+ /**
33
+ * Explanation for a single determining policy.
34
+ */
35
+ export interface PolicyExplanation {
36
+ /** Policy ID */
37
+ policy_id: string;
38
+ /** Policy effect (permit or forbid) */
39
+ effect: PolicyEffect;
40
+ /** Human-readable summary */
41
+ summary: string;
42
+ /** Recursive evaluated condition tree with actual values (from conditionExpression) */
43
+ evaluated_expression?: EvaluatedExpression;
44
+ /** Per-condition match results (flat, from structured conditions[]) */
45
+ condition_results: ConditionResult[];
46
+ /** Raw Cedar condition text if the policy uses rawCondition instead of structured conditions */
47
+ raw_condition?: string;
48
+ }
49
+ /**
50
+ * Result of evaluating a single condition against the request context.
51
+ */
52
+ export interface ConditionResult {
53
+ /** Context field name */
54
+ field: string;
55
+ /** Comparison operator */
56
+ operator: ConditionOperator;
57
+ /** Expected value (threshold from the rule) */
58
+ expected: string | number | boolean | string[];
59
+ /** Actual value from the context (undefined if field was missing) */
60
+ actual?: unknown;
61
+ /** Whether this condition matched */
62
+ matched: boolean;
63
+ }
64
+ /** Evaluated comparison: context.field <op> value */
65
+ export interface EvaluatedComparison {
66
+ kind: 'comparison';
67
+ field: string;
68
+ operator: ConditionOperator;
69
+ expected: string | number | boolean | string[];
70
+ actual: unknown;
71
+ matched: boolean;
72
+ }
73
+ /** Evaluated contains: context.field.contains(value) */
74
+ export interface EvaluatedContains {
75
+ kind: 'contains';
76
+ field: string;
77
+ expected: string | number | boolean;
78
+ actual: unknown;
79
+ matched: boolean;
80
+ }
81
+ /** Evaluated like: context.field like "pattern" */
82
+ export interface EvaluatedLike {
83
+ kind: 'like';
84
+ field: string;
85
+ pattern: string;
86
+ actual: unknown;
87
+ matched: boolean;
88
+ }
89
+ /** Evaluated has: context has field */
90
+ export interface EvaluatedHas {
91
+ kind: 'has';
92
+ field: string;
93
+ matched: boolean;
94
+ }
95
+ /** Evaluated AND */
96
+ export interface EvaluatedAnd {
97
+ kind: 'and';
98
+ children: EvaluatedExpression[];
99
+ matched: boolean;
100
+ }
101
+ /** Evaluated OR */
102
+ export interface EvaluatedOr {
103
+ kind: 'or';
104
+ children: EvaluatedExpression[];
105
+ matched: boolean;
106
+ }
107
+ /** Evaluated NOT */
108
+ export interface EvaluatedNot {
109
+ kind: 'not';
110
+ child: EvaluatedExpression;
111
+ matched: boolean;
112
+ }
113
+ /** Evaluated raw (cannot be decomposed) */
114
+ export interface EvaluatedRaw {
115
+ kind: 'raw';
116
+ text: string;
117
+ matched: boolean;
118
+ }
119
+ /**
120
+ * Recursive evaluated condition expression tree.
121
+ * Produced by evaluateExpression() by walking ConditionExpression against context.
122
+ * Every node has a `matched` boolean; leaf nodes include `actual` values.
123
+ */
124
+ export type EvaluatedExpression = EvaluatedComparison | EvaluatedContains | EvaluatedLike | EvaluatedHas | EvaluatedAnd | EvaluatedOr | EvaluatedNot | EvaluatedRaw;
125
+ /**
126
+ * Explain a policy decision by matching determining policies against their
127
+ * structured conditions and the request context.
128
+ *
129
+ * @param decision - The decision from PolicyEngine.evaluate()
130
+ * @param rules - The PolicyRule[] that were loaded (parsed or built)
131
+ * @param context - The context map that was passed to evaluate()
132
+ * @returns Structured explanation with per-condition match details
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * const decision = engine.evaluate(request);
137
+ * const explained = explainDecision(decision, rules, request.context);
138
+ *
139
+ * for (const explanation of explained.explanations) {
140
+ * console.log(explanation.summary);
141
+ * // "forbid process_prompt — threat_count (10) > 5"
142
+ * }
143
+ * ```
144
+ */
145
+ export declare function explainDecision(decision: DecisionInput, rules: PolicyRule[], context: Record<string, unknown>): ExplainedDecision;
146
+ /**
147
+ * Recursively evaluate a ConditionExpression tree against a context map.
148
+ * Returns an EvaluatedExpression tree with `matched` booleans and `actual` values.
149
+ */
150
+ export declare function evaluateExpression(expr: ConditionExpression, context: Record<string, unknown>): EvaluatedExpression;