@highflame/policy 1.2.0 → 2.0.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/README.md +219 -0
- package/_schemas/overwatch/context.json +463 -0
- package/_schemas/overwatch/schema.cedarschema +184 -0
- package/_schemas/palisade/context.json +325 -0
- package/_schemas/palisade/schema.cedarschema +168 -0
- package/dist/builder.d.ts +1 -2
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js.map +1 -1
- package/dist/context.gen.d.ts +1 -94
- package/dist/context.gen.d.ts.map +1 -1
- package/dist/context.gen.js +1 -97
- package/dist/context.gen.js.map +1 -1
- package/dist/engine.d.ts +18 -18
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +44 -28
- package/dist/engine.js.map +1 -1
- package/dist/engine.test.js.map +1 -1
- package/dist/entities.gen.d.ts +1 -0
- package/dist/entities.gen.d.ts.map +1 -1
- package/dist/entities.gen.js +1 -0
- package/dist/entities.gen.js.map +1 -1
- package/dist/errors.d.ts +102 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +127 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/overwatch-context.gen.d.ts +31 -0
- package/dist/overwatch-context.gen.d.ts.map +1 -0
- package/dist/overwatch-context.gen.js +32 -0
- package/dist/overwatch-context.gen.js.map +1 -0
- package/dist/palisade-context.gen.d.ts +25 -0
- package/dist/palisade-context.gen.d.ts.map +1 -0
- package/dist/palisade-context.gen.js +26 -0
- package/dist/palisade-context.gen.js.map +1 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +79 -34
- package/dist/parser.js.map +1 -1
- package/dist/parser.test.js +44 -0
- package/dist/parser.test.js.map +1 -1
- package/dist/schema.gen.d.ts +1 -1
- package/dist/schema.gen.d.ts.map +1 -1
- package/dist/schema.gen.js +60 -541
- package/dist/schema.gen.js.map +1 -1
- package/dist/schemas.d.ts +64 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +70 -0
- package/dist/schemas.js.map +1 -0
- package/dist/schemas.test.d.ts +8 -0
- package/dist/schemas.test.d.ts.map +1 -0
- package/dist/schemas.test.js +381 -0
- package/dist/schemas.test.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/package.json +13 -6
- package/src/builder.ts +1 -2
- package/src/context.gen.ts +0 -97
- package/src/engine.test.ts +0 -1
- package/src/engine.ts +62 -33
- package/src/entities.gen.ts +1 -0
- package/src/errors.ts +195 -0
- package/src/index.ts +2 -0
- package/src/overwatch-context.gen.ts +34 -0
- package/src/palisade-context.gen.ts +28 -0
- package/src/parser.test.ts +53 -0
- package/src/parser.ts +83 -36
- package/src/schema.gen.ts +60 -541
- package/src/schemas.test.ts +449 -0
- package/src/schemas.ts +91 -0
- package/src/types.ts +3 -0
package/src/context.gen.ts
CHANGED
|
@@ -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];
|
package/src/engine.test.ts
CHANGED
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
364
|
+
* Create a validator with a Cedar schema.
|
|
365
|
+
* @param schema Cedar schema string (required)
|
|
330
366
|
*/
|
|
331
|
-
constructor(schema
|
|
332
|
-
this.schema = 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
|
|
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";
|
package/src/entities.gen.ts
CHANGED
package/src/errors.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser error types and codes for highflame-policy.
|
|
3
|
+
*
|
|
4
|
+
* This module provides standardized error codes that are consistent
|
|
5
|
+
* across all language implementations (Rust, Go, TypeScript, Python).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Error codes for parser errors.
|
|
10
|
+
*
|
|
11
|
+
* These codes are stable and consistent across all language implementations.
|
|
12
|
+
* Format: HFP-<CATEGORY>-<NUMBER>
|
|
13
|
+
* - HFP = HighFlame Policy
|
|
14
|
+
* - CATEGORY = SCOPE | ACTION | COND | PARSE
|
|
15
|
+
* - NUMBER = 3-digit incremental
|
|
16
|
+
*/
|
|
17
|
+
export const ErrorCodes = {
|
|
18
|
+
// Scope constraint errors (HFP-SCOPE-xxx)
|
|
19
|
+
/** Scope constraint is missing an entity (for == operator) */
|
|
20
|
+
SCOPE_MISSING_ENTITY: "HFP-SCOPE-001",
|
|
21
|
+
/** Scope constraint is missing an entity type (for is operator) */
|
|
22
|
+
SCOPE_MISSING_ENTITY_TYPE: "HFP-SCOPE-002",
|
|
23
|
+
/** Scope constraint is missing entity list (for in operator) */
|
|
24
|
+
SCOPE_MISSING_ENTITY_LIST: "HFP-SCOPE-003",
|
|
25
|
+
/** Slot constraints are not supported in PolicyRule */
|
|
26
|
+
SCOPE_SLOT_NOT_SUPPORTED: "HFP-SCOPE-004",
|
|
27
|
+
/** Unsupported scope operator */
|
|
28
|
+
SCOPE_UNSUPPORTED_OP: "HFP-SCOPE-005",
|
|
29
|
+
|
|
30
|
+
// Action constraint errors (HFP-ACTION-xxx)
|
|
31
|
+
/** Action constraint is missing an entity (for == operator) */
|
|
32
|
+
ACTION_MISSING_ENTITY: "HFP-ACTION-001",
|
|
33
|
+
/** Action constraint is missing entities (for in operator) */
|
|
34
|
+
ACTION_MISSING_ENTITIES: "HFP-ACTION-002",
|
|
35
|
+
/** Unsupported action operator */
|
|
36
|
+
ACTION_UNSUPPORTED_OP: "HFP-ACTION-003",
|
|
37
|
+
/** Action scope is null/nil */
|
|
38
|
+
ACTION_SCOPE_NIL: "HFP-ACTION-004",
|
|
39
|
+
|
|
40
|
+
// Condition errors (HFP-COND-xxx)
|
|
41
|
+
/** Unless clauses are not supported in PolicyRule */
|
|
42
|
+
COND_UNLESS_NOT_SUPPORTED: "HFP-COND-001",
|
|
43
|
+
/** Complex condition cannot be parsed */
|
|
44
|
+
COND_COMPLEX_EXPRESSION: "HFP-COND-002",
|
|
45
|
+
|
|
46
|
+
// Parse errors (HFP-PARSE-xxx)
|
|
47
|
+
/** Invalid Cedar syntax */
|
|
48
|
+
PARSE_INVALID_SYNTAX: "HFP-PARSE-001",
|
|
49
|
+
/** Failed to convert policy to JSON */
|
|
50
|
+
PARSE_JSON_CONVERSION: "HFP-PARSE-002",
|
|
51
|
+
/** Failed to parse Cedar JSON structure */
|
|
52
|
+
PARSE_JSON_STRUCTURE: "HFP-PARSE-003",
|
|
53
|
+
/** Unknown policy effect (not permit or forbid) */
|
|
54
|
+
PARSE_UNKNOWN_EFFECT: "HFP-PARSE-004",
|
|
55
|
+
/** Duplicate policy ID found */
|
|
56
|
+
PARSE_DUPLICATE_ID: "HFP-PARSE-005",
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Context information for parser errors.
|
|
63
|
+
*/
|
|
64
|
+
export interface ErrorContext {
|
|
65
|
+
/** The operator that caused the error (e.g., "==", "in", "is") */
|
|
66
|
+
operator?: string;
|
|
67
|
+
/** The field that caused the error (e.g., "principal", "action", "resource") */
|
|
68
|
+
field?: string;
|
|
69
|
+
/** The policy ID if available */
|
|
70
|
+
policyId?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A structured parser error with code, message, and optional context.
|
|
75
|
+
*/
|
|
76
|
+
export class ParserError extends Error {
|
|
77
|
+
/** Machine-readable error code (e.g., "HFP-SCOPE-001") */
|
|
78
|
+
public readonly code: ErrorCode;
|
|
79
|
+
/** Optional context for debugging */
|
|
80
|
+
public readonly context?: ErrorContext;
|
|
81
|
+
|
|
82
|
+
constructor(code: ErrorCode, message: string, context?: ErrorContext) {
|
|
83
|
+
super(message);
|
|
84
|
+
this.name = "ParserError";
|
|
85
|
+
this.code = code;
|
|
86
|
+
this.context = context;
|
|
87
|
+
|
|
88
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
89
|
+
const ErrorWithCapture = Error as typeof Error & { captureStackTrace?: (err: Error, constructor: Function) => void };
|
|
90
|
+
if (ErrorWithCapture.captureStackTrace) {
|
|
91
|
+
ErrorWithCapture.captureStackTrace(this, ParserError);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns a string representation including the error code.
|
|
97
|
+
*/
|
|
98
|
+
override toString(): string {
|
|
99
|
+
return `[${this.code}] ${this.message}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Serializes the error to a plain object for JSON serialization.
|
|
104
|
+
*/
|
|
105
|
+
toJSON(): { code: string; message: string; context?: ErrorContext } {
|
|
106
|
+
return {
|
|
107
|
+
code: this.code,
|
|
108
|
+
message: this.message,
|
|
109
|
+
...(this.context && { context: this.context }),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Convenience static factory methods for common errors
|
|
114
|
+
|
|
115
|
+
/** Scope constraint is missing an entity */
|
|
116
|
+
static scopeMissingEntity(operator: string, field: string): ParserError {
|
|
117
|
+
return new ParserError(
|
|
118
|
+
ErrorCodes.SCOPE_MISSING_ENTITY,
|
|
119
|
+
`'${operator}' constraint is missing an entity`,
|
|
120
|
+
{ operator, field }
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Scope constraint is missing an entity type */
|
|
125
|
+
static scopeMissingEntityType(field: string): ParserError {
|
|
126
|
+
return new ParserError(
|
|
127
|
+
ErrorCodes.SCOPE_MISSING_ENTITY_TYPE,
|
|
128
|
+
"'is' constraint is missing an entity_type",
|
|
129
|
+
{ operator: "is", field }
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Scope constraint is missing entity list */
|
|
134
|
+
static scopeMissingEntityList(field: string): ParserError {
|
|
135
|
+
return new ParserError(
|
|
136
|
+
ErrorCodes.SCOPE_MISSING_ENTITY_LIST,
|
|
137
|
+
"'in' constraint is missing an entity",
|
|
138
|
+
{ operator: "in", field }
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Slot constraints are not supported */
|
|
143
|
+
static scopeSlotNotSupported(operator: string, field: string): ParserError {
|
|
144
|
+
return new ParserError(
|
|
145
|
+
ErrorCodes.SCOPE_SLOT_NOT_SUPPORTED,
|
|
146
|
+
`'${operator}' constraint with slot cannot be represented`,
|
|
147
|
+
{ operator, field }
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Unsupported scope operator */
|
|
152
|
+
static scopeUnsupportedOp(operator: string, field: string): ParserError {
|
|
153
|
+
return new ParserError(
|
|
154
|
+
ErrorCodes.SCOPE_UNSUPPORTED_OP,
|
|
155
|
+
`Unsupported scope operator: ${operator}`,
|
|
156
|
+
{ operator, field }
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Action constraint is missing an entity */
|
|
161
|
+
static actionMissingEntity(operator: string): ParserError {
|
|
162
|
+
return new ParserError(
|
|
163
|
+
ErrorCodes.ACTION_MISSING_ENTITY,
|
|
164
|
+
`Action '${operator}' constraint is missing an entity`,
|
|
165
|
+
{ operator, field: "action" }
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Action constraint is missing entities */
|
|
170
|
+
static actionMissingEntities(): ParserError {
|
|
171
|
+
return new ParserError(
|
|
172
|
+
ErrorCodes.ACTION_MISSING_ENTITIES,
|
|
173
|
+
"Action 'in' constraint is missing entities",
|
|
174
|
+
{ operator: "in", field: "action" }
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Unsupported action operator */
|
|
179
|
+
static actionUnsupportedOp(operator: string): ParserError {
|
|
180
|
+
return new ParserError(
|
|
181
|
+
ErrorCodes.ACTION_UNSUPPORTED_OP,
|
|
182
|
+
`Unsupported action operator: ${operator}`,
|
|
183
|
+
{ operator, field: "action" }
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Action scope is nil */
|
|
188
|
+
static actionScopeNil(): ParserError {
|
|
189
|
+
return new ParserError(
|
|
190
|
+
ErrorCodes.ACTION_SCOPE_NIL,
|
|
191
|
+
"Action scope is nil",
|
|
192
|
+
{ field: "action" }
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Code generated by highflame-policy-codegen. DO NOT EDIT.
|
|
2
|
+
// Source: schemas/overwatch/context.json
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context attribute keys for Overwatch Overwatch (Guardian) IDE security & policy enforcement.
|
|
6
|
+
*
|
|
7
|
+
* These constants correspond to the context attributes defined in the
|
|
8
|
+
* Overwatch Cedar schema and are used at policy evaluation time.
|
|
9
|
+
*/
|
|
10
|
+
export const OverwatchContextKey = {
|
|
11
|
+
ContainsSecrets: 'contains_secrets',
|
|
12
|
+
Content: 'content',
|
|
13
|
+
Cwd: 'cwd',
|
|
14
|
+
Event: 'event',
|
|
15
|
+
FilePath: 'file_path',
|
|
16
|
+
HighestSeverity: 'highest_severity',
|
|
17
|
+
MaxThreatSeverity: 'max_threat_severity',
|
|
18
|
+
McpServer: 'mcp_server',
|
|
19
|
+
McpTool: 'mcp_tool',
|
|
20
|
+
Path: 'path',
|
|
21
|
+
PromptText: 'prompt_text',
|
|
22
|
+
ResponseContent: 'response_content',
|
|
23
|
+
ServerName: 'server_name',
|
|
24
|
+
Source: 'source',
|
|
25
|
+
ThreatCategories: 'threat_categories',
|
|
26
|
+
ThreatCount: 'threat_count',
|
|
27
|
+
ThreatTypes: 'threat_types',
|
|
28
|
+
ToolName: 'tool_name',
|
|
29
|
+
UserEmail: 'user_email',
|
|
30
|
+
WorkspaceRoot: 'workspace_root',
|
|
31
|
+
YaraThreats: 'yara_threats',
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
export type OverwatchContextKey = (typeof OverwatchContextKey)[keyof typeof OverwatchContextKey];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Code generated by highflame-policy-codegen. DO NOT EDIT.
|
|
2
|
+
// Source: schemas/palisade/context.json
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context attribute keys for Palisade Palisade ML supply chain security & artifact scanning.
|
|
6
|
+
*
|
|
7
|
+
* These constants correspond to the context attributes defined in the
|
|
8
|
+
* Palisade Cedar schema and are used at policy evaluation time.
|
|
9
|
+
*/
|
|
10
|
+
export const PalisadeContextKey = {
|
|
11
|
+
AdapterBaseDigestMismatch: 'adapter_base_digest_mismatch',
|
|
12
|
+
ArtifactFormat: 'artifact_format',
|
|
13
|
+
ArtifactSigned: 'artifact_signed',
|
|
14
|
+
Environment: 'environment',
|
|
15
|
+
FindingType: 'finding_type',
|
|
16
|
+
GgufSuspiciousMetadata: 'gguf_suspicious_metadata',
|
|
17
|
+
MatchCount: 'match_count',
|
|
18
|
+
MetadataCosaiLevelNumeric: 'metadata_cosai_level_numeric',
|
|
19
|
+
MetadataMaliciousPattern: 'metadata_malicious_pattern',
|
|
20
|
+
Path: 'path',
|
|
21
|
+
PickleExecPathDetected: 'pickle_exec_path_detected',
|
|
22
|
+
ProvenanceSigner: 'provenance_signer',
|
|
23
|
+
SafetensorsIntegrityViolation: 'safetensors_integrity_violation',
|
|
24
|
+
Severity: 'severity',
|
|
25
|
+
TokenizerAddedTokensCount: 'tokenizer_added_tokens_count',
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export type PalisadeContextKey = (typeof PalisadeContextKey)[keyof typeof PalisadeContextKey];
|
package/src/parser.test.ts
CHANGED
|
@@ -113,4 +113,57 @@ describe('parseCedarToRules', () => {
|
|
|
113
113
|
expect(result.rules).toHaveLength(0);
|
|
114
114
|
expect(result.unstructured.length).toBeGreaterThan(0);
|
|
115
115
|
});
|
|
116
|
+
|
|
117
|
+
it('should store complex conditions as valid JSON array in rawCondition', () => {
|
|
118
|
+
// Use a condition with boolean AND that can't be mapped to structured format
|
|
119
|
+
const cedarText = `
|
|
120
|
+
@id("complex-condition")
|
|
121
|
+
permit(principal, action, resource)
|
|
122
|
+
when {
|
|
123
|
+
context.a == "x" && context.b == "y"
|
|
124
|
+
};
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
const result = parseCedarToRules(cedarText);
|
|
128
|
+
|
|
129
|
+
expect(result.errors).toHaveLength(0);
|
|
130
|
+
expect(result.rules).toHaveLength(1);
|
|
131
|
+
|
|
132
|
+
const rule = result.rules[0];
|
|
133
|
+
|
|
134
|
+
// The complex && condition should be in rawCondition as valid JSON array
|
|
135
|
+
if (rule.rawCondition) {
|
|
136
|
+
// Verify it's valid JSON (should not throw)
|
|
137
|
+
const parsed = JSON.parse(rule.rawCondition);
|
|
138
|
+
expect(Array.isArray(parsed)).toBe(true);
|
|
139
|
+
expect(parsed.length).toBeGreaterThan(0);
|
|
140
|
+
}
|
|
141
|
+
// Either conditions were mapped or rawCondition contains valid JSON
|
|
142
|
+
expect(rule.conditions.length > 0 || rule.rawCondition).toBeTruthy();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should warn about duplicate policy IDs', () => {
|
|
146
|
+
const cedarText = `
|
|
147
|
+
@id("duplicate-id")
|
|
148
|
+
permit(principal, action, resource);
|
|
149
|
+
|
|
150
|
+
@id("unique-id")
|
|
151
|
+
forbid(principal, action, resource);
|
|
152
|
+
|
|
153
|
+
@id("duplicate-id")
|
|
154
|
+
permit(principal is User, action, resource);
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
const result = parseCedarToRules(cedarText);
|
|
158
|
+
|
|
159
|
+
// All policies should still be parsed
|
|
160
|
+
expect(result.rules).toHaveLength(3);
|
|
161
|
+
|
|
162
|
+
// Should have a warning about duplicate ID
|
|
163
|
+
const duplicateError = result.errors.find(e => e.includes("HFP-PARSE-005"));
|
|
164
|
+
expect(duplicateError).toBeDefined();
|
|
165
|
+
expect(duplicateError).toContain("duplicate-id");
|
|
166
|
+
expect(duplicateError).toContain("[0"); // First occurrence at index 0
|
|
167
|
+
expect(duplicateError).toContain("2"); // Second occurrence at index 2
|
|
168
|
+
});
|
|
116
169
|
});
|