@highflame/policy 1.2.1 → 2.0.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 (73) hide show
  1. package/README.md +219 -0
  2. package/_schemas/overwatch/context.json +433 -0
  3. package/_schemas/overwatch/schema.cedarschema +179 -0
  4. package/_schemas/palisade/context.json +325 -0
  5. package/_schemas/palisade/schema.cedarschema +168 -0
  6. package/dist/builder.d.ts +1 -2
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js +16 -3
  9. package/dist/builder.js.map +1 -1
  10. package/dist/context.gen.d.ts +1 -94
  11. package/dist/context.gen.d.ts.map +1 -1
  12. package/dist/context.gen.js +1 -97
  13. package/dist/context.gen.js.map +1 -1
  14. package/dist/engine.d.ts +18 -18
  15. package/dist/engine.d.ts.map +1 -1
  16. package/dist/engine.js +44 -28
  17. package/dist/engine.js.map +1 -1
  18. package/dist/engine.test.js.map +1 -1
  19. package/dist/entities.gen.d.ts +1 -0
  20. package/dist/entities.gen.d.ts.map +1 -1
  21. package/dist/entities.gen.js +1 -0
  22. package/dist/entities.gen.js.map +1 -1
  23. package/dist/index.d.ts +4 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +5 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/overwatch-context.gen.d.ts +29 -0
  28. package/dist/overwatch-context.gen.d.ts.map +1 -0
  29. package/dist/overwatch-context.gen.js +30 -0
  30. package/dist/overwatch-context.gen.js.map +1 -0
  31. package/dist/palisade-context.gen.d.ts +25 -0
  32. package/dist/palisade-context.gen.d.ts.map +1 -0
  33. package/dist/palisade-context.gen.js +26 -0
  34. package/dist/palisade-context.gen.js.map +1 -0
  35. package/dist/schema.gen.d.ts +1 -1
  36. package/dist/schema.gen.d.ts.map +1 -1
  37. package/dist/schema.gen.js +60 -541
  38. package/dist/schema.gen.js.map +1 -1
  39. package/dist/schemas.d.ts +64 -0
  40. package/dist/schemas.d.ts.map +1 -0
  41. package/dist/schemas.js +70 -0
  42. package/dist/schemas.js.map +1 -0
  43. package/dist/schemas.test.d.ts +8 -0
  44. package/dist/schemas.test.d.ts.map +1 -0
  45. package/dist/schemas.test.js +377 -0
  46. package/dist/schemas.test.js.map +1 -0
  47. package/dist/service-schemas.gen.d.ts +48 -0
  48. package/dist/service-schemas.gen.d.ts.map +1 -0
  49. package/dist/service-schemas.gen.js +581 -0
  50. package/dist/service-schemas.gen.js.map +1 -0
  51. package/dist/studio-ui.test.d.ts +8 -0
  52. package/dist/studio-ui.test.d.ts.map +1 -0
  53. package/dist/studio-ui.test.js +165 -0
  54. package/dist/studio-ui.test.js.map +1 -0
  55. package/dist/types.d.ts +4 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/types.js +5 -0
  58. package/dist/types.js.map +1 -1
  59. package/package.json +9 -6
  60. package/src/builder.ts +18 -5
  61. package/src/context.gen.ts +0 -97
  62. package/src/engine.test.ts +0 -1
  63. package/src/engine.ts +62 -33
  64. package/src/entities.gen.ts +1 -0
  65. package/src/index.ts +17 -0
  66. package/src/overwatch-context.gen.ts +32 -0
  67. package/src/palisade-context.gen.ts +28 -0
  68. package/src/schema.gen.ts +60 -541
  69. package/src/schemas.test.ts +445 -0
  70. package/src/schemas.ts +91 -0
  71. package/src/service-schemas.gen.ts +608 -0
  72. package/src/studio-ui.test.ts +207 -0
  73. package/src/types.ts +17 -0
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Studio UI Integration Tests
3
+ *
4
+ * These tests simulate exactly how the Studio UI (Overwatch admin dashboard)
5
+ * will use the @highflame/policy npm package.
6
+ */
7
+ import { describe, it, expect } from 'vitest';
8
+ // Browser-safe imports (simulating '@highflame/policy/types')
9
+ import { PolicyBuilder, OVERWATCH_SCHEMA, PALISADE_SCHEMA, OVERWATCH_CONTEXT, OverwatchContextKey, PalisadeContextKey, } from './types.js';
10
+ // Node.js only imports (for API routes)
11
+ import { PolicyEngine, PolicyValidator, newEntityUID, newEntity, } from './index.js';
12
+ describe('Studio UI Integration Tests', () => {
13
+ /**
14
+ * Test: PolicyBuilder action formatting
15
+ *
16
+ * Tests that simple action names are properly formatted to Cedar syntax.
17
+ * This is the "normal approach" for non-namespaced schemas.
18
+ */
19
+ it('should format simple action names to Cedar syntax', () => {
20
+ // Using simple action name (normal approach)
21
+ const policy = PolicyBuilder.permit()
22
+ .principalType('User')
23
+ .action('call_tool')
24
+ .resourceType('Tool')
25
+ .build();
26
+ const cedarText = policy.toCedar();
27
+ // Simple actions should be wrapped as Action::"..."
28
+ expect(cedarText).toContain('action == Action::"call_tool"');
29
+ expect(cedarText).not.toContain('Action::"Action::'); // No double-wrapping
30
+ });
31
+ /**
32
+ * Test 1: Schema and Context Loading
33
+ *
34
+ * Studio UI needs to load schemas for validation and context metadata
35
+ * for populating form dropdowns.
36
+ */
37
+ it('should load schemas and context metadata for form builders', () => {
38
+ // Schemas should be strings
39
+ expect(typeof OVERWATCH_SCHEMA).toBe('string');
40
+ expect(OVERWATCH_SCHEMA).toContain('namespace Overwatch');
41
+ expect(typeof PALISADE_SCHEMA).toBe('string');
42
+ expect(PALISADE_SCHEMA).toContain('namespace Palisade');
43
+ // Context metadata should have action definitions
44
+ expect(OVERWATCH_CONTEXT.service).toBe('overwatch');
45
+ expect(OVERWATCH_CONTEXT.actions.length).toBeGreaterThan(0);
46
+ // Find call_tool action and verify context attributes
47
+ const callToolAction = OVERWATCH_CONTEXT.actions.find((a) => a.name === 'call_tool');
48
+ expect(callToolAction).toBeDefined();
49
+ expect(callToolAction.context_attributes.length).toBeGreaterThan(0);
50
+ // Context keys should be available for type-safe form building
51
+ expect(OverwatchContextKey.ToolName).toBe('tool_name');
52
+ expect(OverwatchContextKey.ThreatCount).toBe('threat_count');
53
+ expect(PalisadeContextKey.Severity).toBe('severity');
54
+ expect(PalisadeContextKey.Environment).toBe('environment');
55
+ });
56
+ /**
57
+ * Test 2: Full Round-Trip - Create Policy → Validate → Evaluate
58
+ *
59
+ * This simulates the complete flow:
60
+ * 1. User creates a policy using PolicyBuilder in the UI
61
+ * 2. API validates the policy against the schema
62
+ * 3. Runtime evaluates the policy
63
+ */
64
+ it('should complete full policy lifecycle for Overwatch', () => {
65
+ // Step 1: User creates policy in form UI
66
+ const policy = PolicyBuilder.permit()
67
+ .principalType('Overwatch::User')
68
+ .action('Overwatch::Action::"call_tool"')
69
+ .resourceType('Overwatch::Tool')
70
+ .whenRaw('context.threat_count < 5')
71
+ .build();
72
+ const cedarText = policy.toCedar();
73
+ expect(cedarText).toContain('permit');
74
+ expect(cedarText).toContain('Overwatch::User');
75
+ // Step 2: API validates policy against schema
76
+ const validator = new PolicyValidator(OVERWATCH_SCHEMA);
77
+ const validationResult = validator.validate(cedarText);
78
+ expect(validationResult.valid).toBe(true);
79
+ // Step 3: Runtime loads and evaluates policy
80
+ const engine = new PolicyEngine({ schema: OVERWATCH_SCHEMA });
81
+ engine.loadPolicies(cedarText);
82
+ const entities = [
83
+ newEntity('Overwatch::User', 'mcp_client', { user_type: 'external', email: 'test@example.com' }),
84
+ newEntity('Overwatch::Tool', 'shell', { tool_name: 'shell', risk_level: 'high' }),
85
+ ];
86
+ // Full context as Guardian will provide at runtime
87
+ const baseContext = {
88
+ content: 'ls -la',
89
+ source: 'claudecode',
90
+ event: 'PreToolUse',
91
+ user_email: 'test@example.com',
92
+ tool_name: 'shell',
93
+ mcp_server: 'filesystem',
94
+ mcp_tool: 'shell',
95
+ path: '/workspace',
96
+ cwd: '/workspace',
97
+ workspace_root: '/workspace',
98
+ highest_severity: 'low',
99
+ threat_categories: [],
100
+ threat_types: [],
101
+ yara_threats: [],
102
+ max_threat_severity: 1,
103
+ contains_secrets: false,
104
+ response_content: '',
105
+ };
106
+ // Should allow when threat_count < 5
107
+ const allowDecision = engine.evaluate({
108
+ principal: newEntityUID('Overwatch::User', 'mcp_client'),
109
+ action: 'Overwatch::Action::"call_tool"',
110
+ resource: newEntityUID('Overwatch::Tool', 'shell'),
111
+ context: { ...baseContext, threat_count: 3 },
112
+ entities,
113
+ });
114
+ expect(allowDecision.effect).toBe('Allow');
115
+ // Should deny when threat_count >= 5 (no matching permit)
116
+ const denyDecision = engine.evaluate({
117
+ principal: newEntityUID('Overwatch::User', 'mcp_client'),
118
+ action: 'Overwatch::Action::"call_tool"',
119
+ resource: newEntityUID('Overwatch::Tool', 'shell'),
120
+ context: { ...baseContext, threat_count: 10 },
121
+ entities,
122
+ });
123
+ expect(denyDecision.effect).toBe('Deny');
124
+ });
125
+ /**
126
+ * Test 3: Full Round-Trip for Palisade
127
+ *
128
+ * Same flow but for Palisade ML security policies.
129
+ */
130
+ it('should complete full policy lifecycle for Palisade', () => {
131
+ // Step 1: Create a forbid policy for CRITICAL findings
132
+ const policy = PolicyBuilder.forbid()
133
+ .principalType('Palisade::Scanner')
134
+ .action('Palisade::Action::"load_model"')
135
+ .resourceType('Palisade::Artifact')
136
+ .whenRaw('context.severity == "CRITICAL"')
137
+ .build();
138
+ const cedarText = policy.toCedar();
139
+ // Step 2: Validate
140
+ const validator = new PolicyValidator(PALISADE_SCHEMA);
141
+ expect(validator.validate(cedarText).valid).toBe(true);
142
+ // Step 3: Evaluate
143
+ const engine = new PolicyEngine({ schema: PALISADE_SCHEMA });
144
+ engine.loadPolicies(cedarText);
145
+ const entities = [
146
+ newEntity('Palisade::Scanner', 'palisade', { scanner_type: 'ml' }),
147
+ newEntity('Palisade::Artifact', 'model.pkl', {
148
+ artifact_format: 'pickle',
149
+ path: '/model.pkl',
150
+ signed: false,
151
+ signer: 'unsigned',
152
+ }),
153
+ ];
154
+ // Should deny CRITICAL findings
155
+ const decision = engine.evaluate({
156
+ principal: newEntityUID('Palisade::Scanner', 'palisade'),
157
+ action: 'Palisade::Action::"load_model"',
158
+ resource: newEntityUID('Palisade::Artifact', 'model.pkl'),
159
+ context: { severity: 'CRITICAL' },
160
+ entities,
161
+ });
162
+ expect(decision.effect).toBe('Deny');
163
+ });
164
+ });
165
+ //# sourceMappingURL=studio-ui.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"studio-ui.test.js","sourceRoot":"","sources":["../src/studio-ui.test.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -4,4 +4,8 @@ export * from './context.gen.js';
4
4
  export * from './schema.gen.js';
5
5
  export * from './builder.js';
6
6
  export * from './errors.js';
7
+ export { OVERWATCH_SCHEMA, PALISADE_SCHEMA, OVERWATCH_CONTEXT, PALISADE_CONTEXT, } from './service-schemas.gen.js';
8
+ export type { ContextAttribute, ActionContext, ServiceContext, } from './service-schemas.gen.js';
9
+ export { OverwatchContextKey } from './overwatch-context.gen.js';
10
+ export { PalisadeContextKey } from './palisade-context.gen.js';
7
11
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAQA,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAGhC,cAAc,cAAc,CAAC;AAG7B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAQA,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAGhC,cAAc,cAAc,CAAC;AAG7B,cAAc,aAAa,CAAC;AAG5B,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,cAAc,GACf,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/types.js CHANGED
@@ -13,4 +13,9 @@ export * from './schema.gen.js';
13
13
  export * from './builder.js';
14
14
  // Error types - works in browser (no WASM dependency)
15
15
  export * from './errors.js';
16
+ // Service-specific schemas and context (inlined, browser-safe)
17
+ export { OVERWATCH_SCHEMA, PALISADE_SCHEMA, OVERWATCH_CONTEXT, PALISADE_CONTEXT, } from './service-schemas.gen.js';
18
+ // Service-specific context key enums
19
+ export { OverwatchContextKey } from './overwatch-context.gen.js';
20
+ export { PalisadeContextKey } from './palisade-context.gen.js';
16
21
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,uCAAuC;AACvC,EAAE;AACF,6CAA6C;AAC7C,gDAAgD;AAChD,yEAAyE;AAEzE,gDAAgD;AAChD,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAEhC,wDAAwD;AACxD,cAAc,cAAc,CAAC;AAE7B,sDAAsD;AACtD,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,uCAAuC;AACvC,EAAE;AACF,6CAA6C;AAC7C,gDAAgD;AAChD,yEAAyE;AAEzE,gDAAgD;AAChD,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAEhC,wDAAwD;AACxD,cAAc,cAAc,CAAC;AAE7B,sDAAsD;AACtD,cAAc,aAAa,CAAC;AAE5B,+DAA+D;AAC/D,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,0BAA0B,CAAC;AAOlC,qCAAqC;AACrC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@highflame/policy",
3
- "version": "1.2.1",
3
+ "version": "2.0.1",
4
4
  "description": "Highflame Cedar policy types and engine wrapper",
5
+ "readme": "README.md",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "type": "module",
@@ -26,10 +27,10 @@
26
27
  "import": "./dist/actions.gen.js",
27
28
  "types": "./dist/actions.gen.d.ts"
28
29
  },
29
- "./context": {
30
- "import": "./dist/context.gen.js",
31
- "types": "./dist/context.gen.d.ts"
32
- },
30
+ "./schemas/overwatch": "./_schemas/overwatch/schema.cedarschema",
31
+ "./schemas/palisade": "./_schemas/palisade/schema.cedarschema",
32
+ "./context/overwatch": "./_schemas/overwatch/context.json",
33
+ "./context/palisade": "./_schemas/palisade/context.json",
33
34
  "./engine": {
34
35
  "import": "./dist/engine.js",
35
36
  "types": "./dist/engine.d.ts"
@@ -53,12 +54,14 @@
53
54
  "@cedar-policy/cedar-wasm": "^4.0.0"
54
55
  },
55
56
  "devDependencies": {
57
+ "@types/node": "^25.2.0",
56
58
  "typescript": "^5.3.0",
57
59
  "vitest": "^2.0.0"
58
60
  },
59
61
  "files": [
60
62
  "dist",
61
- "src"
63
+ "src",
64
+ "_schemas"
62
65
  ],
63
66
  "keywords": [
64
67
  "cedar",
package/src/builder.ts CHANGED
@@ -24,7 +24,20 @@
24
24
 
25
25
  import { EntityType, EntityUID } from './entities.gen.js';
26
26
  import { ActionType } from './actions.gen.js';
27
- import { ContextKey } from './context.gen.js';
27
+
28
+ /**
29
+ * Format an action string for Cedar policy text.
30
+ * Detects if action is already namespaced (contains 'Action::"') and preserves it,
31
+ * otherwise wraps with Action::"...".
32
+ */
33
+ function formatAction(action: string): string {
34
+ if (action.includes('Action::"')) {
35
+ // Already namespaced (e.g., 'Overwatch::Action::"call_tool"')
36
+ return action;
37
+ }
38
+ // Non-namespaced, wrap with Action::"..."
39
+ return `Action::"${action}"`;
40
+ }
28
41
 
29
42
  /**
30
43
  * Policy effect - permit or forbid
@@ -164,13 +177,13 @@ export class Policy {
164
177
  // Action
165
178
  if (Array.isArray(this.data.action)) {
166
179
  if (this.data.action.length === 1) {
167
- policyLine += `,\n action == Action::\"${this.data.action[0]}\"`;
180
+ policyLine += `,\n action == ${formatAction(this.data.action[0])}`;
168
181
  } else {
169
- const actions = this.data.action.map(a => `Action::\"${a}\"`).join(', ');
182
+ const actions = this.data.action.map(a => formatAction(a)).join(', ');
170
183
  policyLine += `,\n action in [${actions}]`;
171
184
  }
172
185
  } else {
173
- policyLine += `,\n action == Action::\"${this.data.action}\"`;
186
+ policyLine += `,\n action == ${formatAction(this.data.action)}`;
174
187
  }
175
188
 
176
189
  // Resource
@@ -397,7 +410,7 @@ export class PolicyBuilder {
397
410
  /**
398
411
  * Add a structured condition
399
412
  */
400
- when(field: ContextKey | string, operator: ConditionOperator, value: string | number | boolean | string[]): PolicyBuilder {
413
+ when(field: string, operator: ConditionOperator, value: string | number | boolean | string[]): PolicyBuilder {
401
414
  this.data.conditions.push({ field, operator, value });
402
415
  return this;
403
416
  }
@@ -5,103 +5,6 @@
5
5
  * Context attribute keys for Cedar policy evaluation.
6
6
  */
7
7
  export const ContextKey = {
8
- // Guardrails/Core context attributes
9
- /** Name of tool being called */
10
- ToolName: 'tool_name',
11
- /** Name of resource being accessed */
12
- ResourceName: 'resource_name',
13
- /** Name of prompt */
14
- PromptName: 'prompt_name',
15
- /** Raw prompt text */
16
- PromptText: 'prompt_text',
17
- /** Response size in megabytes */
18
- ResponseSizeMb: 'response_size_mb',
19
- /** Set of detected YARA threat names */
20
- YaraThreats: 'yara_threats',
21
- /** Number of threats detected */
22
- ThreatCount: 'threat_count',
23
- /** Highest severity (0-4) */
24
- MaxThreatSeverity: 'max_threat_severity',
25
- /** User type: external or internal */
26
- UserType: 'user_type',
27
- /** Whether monitoring is active */
28
- MonitoringEnabled: 'monitoring_enabled',
29
- /** File path */
30
- Path: 'path',
31
- /** HTTP hostname */
32
- Hostname: 'hostname',
33
- /** IP address */
34
- IpAddress: 'ip_address',
35
- /** Whether the IP is private/loopback (set by application layer) */
36
- IsPrivateIp: 'is_private_ip',
37
- /** HTTP scheme */
38
- Scheme: 'scheme',
39
- /** Port number */
40
- Port: 'port',
41
-
42
- // Palisade context attributes
43
- /** Environment: production, development, research */
44
- Environment: 'environment',
45
- /** Format: pickle, safetensors, gguf, onnx */
46
- ArtifactFormat: 'artifact_format',
47
- /** Whether artifact has signature */
48
- ArtifactSigned: 'artifact_signed',
49
- /** Severity: CRITICAL, HIGH, MEDIUM, LOW, INFO */
50
- Severity: 'severity',
51
- /** Type of security finding */
52
- FindingType: 'finding_type',
53
- /** Who signed the artifact */
54
- ProvenanceSigner: 'provenance_signer',
55
- /** RCE path found in pickle */
56
- PickleExecPathDetected: 'pickle_exec_path_detected',
57
- /** Malicious pattern in metadata */
58
- MetadataMaliciousPattern: 'metadata_malicious_pattern',
59
- /** Number of added tokens */
60
- TokenizerAddedTokensCount: 'tokenizer_added_tokens_count',
61
- /** Safetensors integrity failed */
62
- SafetensorsIntegrityViolation: 'safetensors_integrity_violation',
63
- /** Suspicious GGUF metadata */
64
- GgufSuspiciousMetadata: 'gguf_suspicious_metadata',
65
- /** LoRA adapter digest mismatch */
66
- AdapterBaseDigestMismatch: 'adapter_base_digest_mismatch',
67
- /** CoSAI maturity level (0-5) */
68
- MetadataCosaiLevelNumeric: 'metadata_cosai_level_numeric',
69
-
70
- // Overwatch context attributes
71
- /** IDE source: cursor, claudecode, vscode, geminicli */
72
- Source: 'source',
73
- /** Hook event type: beforeShellExecution, PreToolUse, etc. */
74
- Event: 'event',
75
- /** The prompt/request content being evaluated */
76
- Content: 'content',
77
- /** User's email address (or 'anonymous') */
78
- UserEmail: 'user_email',
79
- /** Custom principal ID for policy evaluation */
80
- CedarPrincipal: 'cedar_principal',
81
- /** MCP server name: filesystem, playwright, etc. */
82
- ServerName: 'server_name',
83
- /** Whether the path is within the workspace */
84
- IsWithinWorkspace: 'is_within_workspace',
85
- /** Response content from tool execution */
86
- ResponseContent: 'response_content',
87
- /** Highest severity level: critical, high, medium, low */
88
- HighestSeverity: 'highest_severity',
89
- /** Array of threat types detected */
90
- ThreatTypes: 'threat_types',
91
- /** Array of threat categories found */
92
- ThreatCategories: 'threat_categories',
93
- /** Whether secrets were detected in the content */
94
- ContainsSecrets: 'contains_secrets',
95
- /** Number of concurrent calls */
96
- ConcurrentCalls: 'concurrent_calls',
97
- /** Request rate per minute */
98
- RequestsPerMinute: 'requests_per_minute',
99
- /** User trust level: high, medium, low */
100
- UserTrustLevel: 'user_trust_level',
101
- /** Whether alerting is enabled for this request */
102
- AlertEnabled: 'alert_enabled',
103
- /** Type of security scan being performed */
104
- ScanType: 'scan_type',
105
8
  } as const;
106
9
 
107
10
  export type ContextKey = (typeof ContextKey)[keyof typeof ContextKey];
@@ -11,7 +11,6 @@ import {
11
11
  Decision,
12
12
  EntityType,
13
13
  ActionType,
14
- ContextKey,
15
14
  InputValidationError,
16
15
  DEFAULT_LIMITS,
17
16
  } from './index.js';
package/src/engine.ts CHANGED
@@ -6,7 +6,6 @@
6
6
  import * as cedar from "@cedar-policy/cedar-wasm/nodejs";
7
7
  import { EntityType, EntityUID, Entity } from "./entities.gen.js";
8
8
  import { ActionType } from "./actions.gen.js";
9
- import { CEDAR_SCHEMA } from "./schema.gen.js";
10
9
 
11
10
  // =============================================================================
12
11
  // INPUT VALIDATION LIMITS (consistent across all language SDKs)
@@ -35,6 +34,8 @@ export interface InputLimits {
35
34
  }
36
35
 
37
36
  export interface EngineOptions {
37
+ /** Cedar schema string (optional - can also use loadSchema() method) */
38
+ schema?: string;
38
39
  /** Custom input validation limits */
39
40
  limits?: InputLimits;
40
41
  /** Skip input validation (not recommended for production) */
@@ -142,9 +143,15 @@ export interface Decision {
142
143
 
143
144
  export interface EvaluateRequest {
144
145
  principal: EntityUID;
145
- action: ActionType;
146
+ /**
147
+ * Action to evaluate. Can be:
148
+ * - A generated ActionType value (e.g., ActionType.CallTool)
149
+ * - A namespaced action string (e.g., 'Overwatch::Action::"call_tool"')
150
+ */
151
+ action: ActionType | string;
146
152
  resource: EntityUID;
147
153
  context?: Record<string, unknown>;
154
+ entities?: Entity[];
148
155
  }
149
156
 
150
157
  /**
@@ -170,6 +177,33 @@ function toCedarValue(value: unknown): cedar.CedarValueJson {
170
177
  return String(value);
171
178
  }
172
179
 
180
+ /**
181
+ * Parse an action string into Cedar EntityUID format.
182
+ * Handles both non-namespaced and namespaced actions:
183
+ * - "call_tool" → { type: "Action", id: "call_tool" }
184
+ * - "Overwatch::Action::\"call_tool\"" → { type: "Overwatch::Action", id: "call_tool" }
185
+ * - "Overwatch::Action::call_tool" → { type: "Overwatch::Action", id: "call_tool" }
186
+ */
187
+ function parseActionString(action: string): cedar.EntityUidJson {
188
+ // Check if action contains namespace separator
189
+ if (!action.includes("::")) {
190
+ // Non-namespaced action
191
+ return { type: "Action", id: action };
192
+ }
193
+
194
+ // Namespaced action - find the last :: separator
195
+ const lastSeparator = action.lastIndexOf("::");
196
+ const actionType = action.substring(0, lastSeparator);
197
+ let actionId = action.substring(lastSeparator + 2);
198
+
199
+ // Remove surrounding quotes if present (e.g., "call_tool" → call_tool)
200
+ if (actionId.startsWith('"') && actionId.endsWith('"')) {
201
+ actionId = actionId.slice(1, -1);
202
+ }
203
+
204
+ return { type: actionType, id: actionId };
205
+ }
206
+
173
207
  /**
174
208
  * PolicyEngine wraps cedar-wasm with Highflame schema types.
175
209
  */
@@ -181,6 +215,7 @@ export class PolicyEngine {
181
215
 
182
216
  constructor(options?: EngineOptions) {
183
217
  this.options = options ?? {};
218
+ this.schema = options?.schema;
184
219
  this.limits = {
185
220
  maxContextKeys: options?.limits?.maxContextKeys ?? DEFAULT_LIMITS.maxContextKeys,
186
221
  maxStringLength: options?.limits?.maxStringLength ?? DEFAULT_LIMITS.maxStringLength,
@@ -198,19 +233,11 @@ export class PolicyEngine {
198
233
 
199
234
  /**
200
235
  * Load schema from a Cedar schema string.
201
- * If not called, uses the embedded Highflame schema.
202
236
  */
203
237
  loadSchema(schema: string): void {
204
238
  this.schema = schema;
205
239
  }
206
240
 
207
- /**
208
- * Load the embedded Highflame schema.
209
- */
210
- loadHighflameSchema(): void {
211
- this.schema = CEDAR_SCHEMA;
212
- }
213
-
214
241
  /**
215
242
  * Evaluate a policy request and return a decision.
216
243
  * @throws InputValidationError if context validation fails
@@ -226,10 +253,7 @@ export class PolicyEngine {
226
253
  type: req.principal.type,
227
254
  id: req.principal.id,
228
255
  };
229
- const action: cedar.EntityUidJson = {
230
- type: "Action",
231
- id: req.action,
232
- };
256
+ const action: cedar.EntityUidJson = parseActionString(req.action);
233
257
  const resource: cedar.EntityUidJson = {
234
258
  type: req.resource.type,
235
259
  id: req.resource.id,
@@ -243,6 +267,15 @@ export class PolicyEngine {
243
267
  }
244
268
  }
245
269
 
270
+ // Convert entities to Cedar JSON format
271
+ const entities = (req.entities || []).map(e => ({
272
+ uid: e.uid,
273
+ attrs: e.attrs ? Object.fromEntries(
274
+ Object.entries(e.attrs).map(([k, v]) => [k, toCedarValue(v)])
275
+ ) : {},
276
+ parents: e.parents || [],
277
+ }));
278
+
246
279
  // Build the authorization call
247
280
  const call: cedar.AuthorizationCall = {
248
281
  principal,
@@ -250,7 +283,7 @@ export class PolicyEngine {
250
283
  resource,
251
284
  context,
252
285
  policies: { staticPolicies: this.policies },
253
- entities: [],
286
+ entities,
254
287
  };
255
288
 
256
289
  // Add schema if available
@@ -284,7 +317,7 @@ export class PolicyEngine {
284
317
  evaluateSimple(
285
318
  principalType: EntityType,
286
319
  principalId: string,
287
- action: ActionType,
320
+ action: ActionType | string,
288
321
  resourceType: EntityType,
289
322
  resourceId: string,
290
323
  context?: Record<string, unknown>
@@ -302,11 +335,13 @@ export class PolicyEngine {
302
335
  * Returns validation errors or empty array if valid.
303
336
  */
304
337
  validatePolicies(policies: string): string[] {
305
- const schemaToUse = this.schema ?? CEDAR_SCHEMA;
338
+ if (!this.schema) {
339
+ throw new Error("Schema is required for validation. Provide schema via constructor options or loadSchema().");
340
+ }
306
341
 
307
342
  const result = cedar.validate({
308
343
  validationSettings: { mode: "strict" },
309
- schema: schemaToUse,
344
+ schema: this.schema,
310
345
  policies: { staticPolicies: policies },
311
346
  });
312
347
 
@@ -326,10 +361,11 @@ export class PolicyValidator {
326
361
  private schema: string;
327
362
 
328
363
  /**
329
- * Create a validator with the embedded Highflame schema.
364
+ * Create a validator with a Cedar schema.
365
+ * @param schema Cedar schema string (required)
330
366
  */
331
- constructor(schema?: string) {
332
- this.schema = schema ?? CEDAR_SCHEMA;
367
+ constructor(schema: string) {
368
+ this.schema = schema;
333
369
  }
334
370
 
335
371
  /**
@@ -375,23 +411,16 @@ export class PolicyValidator {
375
411
  }
376
412
 
377
413
  /**
378
- * Validate a Cedar policy against the Highflame schema.
414
+ * Validate a Cedar policy against a schema.
379
415
  * Convenience function that doesn't require creating a validator instance.
416
+ * @param schema Cedar schema string
417
+ * @param policy Policy text to validate
380
418
  */
381
- export function validatePolicy(policy: string): { valid: boolean; errors: string[] } {
382
- const validator = new PolicyValidator();
419
+ export function validatePolicy(schema: string, policy: string): { valid: boolean; errors: string[] } {
420
+ const validator = new PolicyValidator(schema);
383
421
  return validator.validate(policy);
384
422
  }
385
423
 
386
- /**
387
- * Get the embedded Highflame Cedar schema.
388
- */
389
- export function getHighflameSchema(): string {
390
- return CEDAR_SCHEMA;
391
- }
392
-
393
424
  // Re-export types
394
425
  export { EntityType, EntityUID, Entity, newEntityUID, newEntity } from "./entities.gen.js";
395
426
  export { ActionType, actionUID } from "./actions.gen.js";
396
- export * from "./context.gen.js";
397
- export { CEDAR_SCHEMA } from "./schema.gen.js";
@@ -11,6 +11,7 @@ export const EntityType = {
11
11
  FilePath: 'FilePath',
12
12
  GitBranch: 'GitBranch',
13
13
  HttpEndpoint: 'HttpEndpoint',
14
+ LlmPrompt: 'LlmPrompt',
14
15
  Memory: 'Memory',
15
16
  Model: 'Model',
16
17
  Package: 'Package',
package/src/index.ts CHANGED
@@ -14,3 +14,20 @@ export * from './engine.js';
14
14
  export * from './builder.js';
15
15
  export * from './parser.js';
16
16
  export * from './errors.js';
17
+
18
+ // Service-specific schemas and context (inlined)
19
+ export {
20
+ OVERWATCH_SCHEMA,
21
+ PALISADE_SCHEMA,
22
+ OVERWATCH_CONTEXT,
23
+ PALISADE_CONTEXT,
24
+ } from './service-schemas.gen.js';
25
+ export type {
26
+ ContextAttribute,
27
+ ActionContext,
28
+ ServiceContext,
29
+ } from './service-schemas.gen.js';
30
+
31
+ // Service-specific context key enums
32
+ export { OverwatchContextKey } from './overwatch-context.gen.js';
33
+ export { PalisadeContextKey } from './palisade-context.gen.js';