@highflame/policy 2.0.8 → 2.0.10
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/_schemas/overwatch/context.json +54 -54
- package/_schemas/overwatch/schema.cedarschema +77 -68
- package/dist/actions.gen.d.ts +0 -1
- package/dist/actions.gen.js +0 -1
- package/dist/annotations.d.ts +0 -1
- package/dist/annotations.js +0 -1
- package/dist/builder.d.ts +45 -14
- package/dist/builder.js +99 -33
- package/dist/context.gen.d.ts +0 -1
- package/dist/context.gen.js +0 -1
- package/dist/engine.d.ts +20 -3
- package/dist/engine.js +50 -21
- package/dist/entities.gen.d.ts +0 -1
- package/dist/entities.gen.js +0 -1
- package/dist/entity-metadata-types.gen.d.ts +0 -1
- package/dist/entity-metadata-types.gen.js +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/overwatch-context.gen.d.ts +0 -1
- package/dist/overwatch-context.gen.js +0 -1
- package/dist/overwatch-defaults.gen.d.ts +0 -1
- package/dist/overwatch-defaults.gen.js +24 -3
- package/dist/overwatch-entities.gen.d.ts +0 -1
- package/dist/overwatch-entities.gen.js +0 -1
- package/dist/palisade-context.gen.d.ts +0 -1
- package/dist/palisade-context.gen.js +0 -1
- package/dist/palisade-entities.gen.d.ts +0 -1
- package/dist/palisade-entities.gen.js +0 -1
- package/dist/parser.d.ts +0 -1
- package/dist/parser.js +0 -1
- package/dist/schema.gen.d.ts +0 -1
- package/dist/schema.gen.js +0 -1
- package/dist/schemas.d.ts +0 -1
- package/dist/schemas.js +0 -1
- package/dist/service-schemas.gen.d.ts +5 -12
- package/dist/service-schemas.gen.js +172 -84
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/package.json +1 -2
- package/dist/actions.gen.d.ts.map +0 -1
- package/dist/actions.gen.js.map +0 -1
- package/dist/annotations.d.ts.map +0 -1
- package/dist/annotations.js.map +0 -1
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js.map +0 -1
- package/dist/context.gen.d.ts.map +0 -1
- package/dist/context.gen.js.map +0 -1
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js.map +0 -1
- package/dist/engine.test.d.ts +0 -8
- package/dist/engine.test.d.ts.map +0 -1
- package/dist/engine.test.js +0 -190
- package/dist/engine.test.js.map +0 -1
- package/dist/entities.gen.d.ts.map +0 -1
- package/dist/entities.gen.js.map +0 -1
- package/dist/entity-metadata-types.gen.d.ts.map +0 -1
- package/dist/entity-metadata-types.gen.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/overwatch-context.gen.d.ts.map +0 -1
- package/dist/overwatch-context.gen.js.map +0 -1
- package/dist/overwatch-defaults.gen.d.ts.map +0 -1
- package/dist/overwatch-defaults.gen.js.map +0 -1
- package/dist/overwatch-defaults.test.d.ts +0 -8
- package/dist/overwatch-defaults.test.d.ts.map +0 -1
- package/dist/overwatch-defaults.test.js +0 -145
- package/dist/overwatch-defaults.test.js.map +0 -1
- package/dist/overwatch-entities.gen.d.ts.map +0 -1
- package/dist/overwatch-entities.gen.js.map +0 -1
- package/dist/overwatch-rebac.test.d.ts +0 -25
- package/dist/overwatch-rebac.test.d.ts.map +0 -1
- package/dist/overwatch-rebac.test.js +0 -301
- package/dist/overwatch-rebac.test.js.map +0 -1
- package/dist/palisade-context.gen.d.ts.map +0 -1
- package/dist/palisade-context.gen.js.map +0 -1
- package/dist/palisade-entities.gen.d.ts.map +0 -1
- package/dist/palisade-entities.gen.js.map +0 -1
- package/dist/parser.d.ts.map +0 -1
- package/dist/parser.js.map +0 -1
- package/dist/parser.test.d.ts +0 -8
- package/dist/parser.test.d.ts.map +0 -1
- package/dist/parser.test.js +0 -212
- package/dist/parser.test.js.map +0 -1
- package/dist/schema.gen.d.ts.map +0 -1
- package/dist/schema.gen.js.map +0 -1
- package/dist/schemas.d.ts.map +0 -1
- package/dist/schemas.js.map +0 -1
- package/dist/schemas.test.d.ts +0 -8
- package/dist/schemas.test.d.ts.map +0 -1
- package/dist/schemas.test.js +0 -407
- package/dist/schemas.test.js.map +0 -1
- package/dist/service-schemas.gen.d.ts.map +0 -1
- package/dist/service-schemas.gen.js.map +0 -1
- package/dist/studio-ui.test.d.ts +0 -8
- package/dist/studio-ui.test.d.ts.map +0 -1
- package/dist/studio-ui.test.js +0 -687
- package/dist/studio-ui.test.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/src/actions.gen.ts +0 -57
- package/src/annotations.ts +0 -243
- package/src/builder.ts +0 -799
- package/src/context.gen.ts +0 -10
- package/src/engine.test.ts +0 -370
- package/src/engine.ts +0 -497
- package/src/entities.gen.ts +0 -65
- package/src/entity-metadata-types.gen.ts +0 -19
- package/src/errors.ts +0 -195
- package/src/index.ts +0 -62
- package/src/overwatch-context.gen.ts +0 -45
- package/src/overwatch-defaults.gen.ts +0 -1255
- package/src/overwatch-defaults.test.ts +0 -176
- package/src/overwatch-entities.gen.ts +0 -41
- package/src/overwatch-rebac.test.ts +0 -346
- package/src/palisade-context.gen.ts +0 -28
- package/src/palisade-entities.gen.ts +0 -49
- package/src/parser.test.ts +0 -251
- package/src/parser.ts +0 -579
- package/src/schema.gen.ts +0 -134
- package/src/schemas.test.ts +0 -477
- package/src/schemas.ts +0 -91
- package/src/service-schemas.gen.ts +0 -608
- package/src/studio-ui.test.ts +0 -813
- package/src/types.ts +0 -66
package/dist/builder.js
CHANGED
|
@@ -133,8 +133,11 @@ export class Policy {
|
|
|
133
133
|
/**
|
|
134
134
|
* Convert to Cedar policy text.
|
|
135
135
|
* Uses proper Cedar @annotation syntax.
|
|
136
|
+
*
|
|
137
|
+
* @param optionalFields - Set of context field names that are optional and need `context has` guards.
|
|
138
|
+
* Use `getOptionalFields()` to compute this from service context metadata.
|
|
136
139
|
*/
|
|
137
|
-
toCedar() {
|
|
140
|
+
toCedar(optionalFields) {
|
|
138
141
|
const lines = [];
|
|
139
142
|
// Generate proper Cedar annotations
|
|
140
143
|
if (this.data.id || this.data.name) {
|
|
@@ -145,7 +148,7 @@ export class Policy {
|
|
|
145
148
|
lines.push(...generateAnnotationLines(annotations));
|
|
146
149
|
}
|
|
147
150
|
// Generate policy body
|
|
148
|
-
lines.push(generatePolicyBody(this.data.effect, this.data.principal, this.data.action, this.data.resource, this.data.conditions, this.data.rawCondition));
|
|
151
|
+
lines.push(generatePolicyBody(this.data.effect, this.data.principal, this.data.action, this.data.resource, this.data.conditions, this.data.rawCondition, optionalFields));
|
|
149
152
|
return lines.join('\n');
|
|
150
153
|
}
|
|
151
154
|
/**
|
|
@@ -170,40 +173,99 @@ export class Policy {
|
|
|
170
173
|
// ============================================================================
|
|
171
174
|
// Cedar Generation Functions
|
|
172
175
|
// ============================================================================
|
|
176
|
+
/**
|
|
177
|
+
* Get the set of optional context fields for the given action(s).
|
|
178
|
+
*
|
|
179
|
+
* A field is considered optional if it has `required: false` in ANY of the
|
|
180
|
+
* targeted actions. This is the safe choice because at evaluation time,
|
|
181
|
+
* the policy could be matched against any of the specified actions.
|
|
182
|
+
*
|
|
183
|
+
* @param serviceContext - The service context metadata (from OVERWATCH_CONTEXT, etc.)
|
|
184
|
+
* @param actions - Single action name or array of action names
|
|
185
|
+
* @returns Set of field names that are optional and need `context has` guards
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* ```typescript
|
|
189
|
+
* import { OVERWATCH_CONTEXT } from '@highflame/policy/types';
|
|
190
|
+
*
|
|
191
|
+
* const optionalFields = getOptionalFields(OVERWATCH_CONTEXT, 'call_tool');
|
|
192
|
+
* // Set { 'tool_name', 'mcp_server', 'threat_count', ... }
|
|
193
|
+
*
|
|
194
|
+
* const cedar = ruleToCedar(rule, optionalFields);
|
|
195
|
+
* // Conditions on optional fields auto-get `context has` guards
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
export function getOptionalFields(serviceContext, actions) {
|
|
199
|
+
const actionList = Array.isArray(actions) ? actions : [actions];
|
|
200
|
+
const optionalFields = new Set();
|
|
201
|
+
for (const actionName of actionList) {
|
|
202
|
+
const action = serviceContext.actions.find(a => a.name === actionName);
|
|
203
|
+
if (action) {
|
|
204
|
+
for (const attr of action.context_attributes) {
|
|
205
|
+
if (!attr.required) {
|
|
206
|
+
optionalFields.add(attr.key);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return optionalFields;
|
|
212
|
+
}
|
|
173
213
|
/**
|
|
174
214
|
* Convert a condition to Cedar syntax.
|
|
175
215
|
* Field names are sanitized to prevent injection attacks.
|
|
216
|
+
*
|
|
217
|
+
* When `optionalFields` is provided and the condition's field is in the set,
|
|
218
|
+
* the output is wrapped with a `context has` guard:
|
|
219
|
+
* `context has field && context.field > value`
|
|
176
220
|
*/
|
|
177
|
-
function conditionToCedar(condition) {
|
|
221
|
+
function conditionToCedar(condition, optionalFields) {
|
|
178
222
|
const field = sanitizeIdentifier(condition.field, 'field');
|
|
179
223
|
const { operator, value } = condition;
|
|
180
224
|
const valueStr = valueToString(value);
|
|
225
|
+
let expr;
|
|
181
226
|
switch (operator) {
|
|
182
227
|
case 'eq':
|
|
183
|
-
|
|
228
|
+
expr = `context.${field} == ${valueStr}`;
|
|
229
|
+
break;
|
|
184
230
|
case 'neq':
|
|
185
|
-
|
|
231
|
+
expr = `context.${field} != ${valueStr}`;
|
|
232
|
+
break;
|
|
186
233
|
case 'lt':
|
|
187
|
-
|
|
234
|
+
expr = `context.${field} < ${valueStr}`;
|
|
235
|
+
break;
|
|
188
236
|
case 'lte':
|
|
189
|
-
|
|
237
|
+
expr = `context.${field} <= ${valueStr}`;
|
|
238
|
+
break;
|
|
190
239
|
case 'gt':
|
|
191
|
-
|
|
240
|
+
expr = `context.${field} > ${valueStr}`;
|
|
241
|
+
break;
|
|
192
242
|
case 'gte':
|
|
193
|
-
|
|
243
|
+
expr = `context.${field} >= ${valueStr}`;
|
|
244
|
+
break;
|
|
194
245
|
case 'contains':
|
|
195
|
-
|
|
246
|
+
expr = `context.${field}.contains(${valueStr})`;
|
|
247
|
+
break;
|
|
196
248
|
case 'in':
|
|
197
249
|
if (Array.isArray(value)) {
|
|
198
250
|
const items = value.map(v => `"${escapeCedarString(v)}"`).join(', ');
|
|
199
|
-
|
|
251
|
+
expr = `context.${field} in [${items}]`;
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
expr = `context.${field} in ${valueStr}`;
|
|
200
255
|
}
|
|
201
|
-
|
|
256
|
+
break;
|
|
202
257
|
case 'like':
|
|
203
|
-
|
|
258
|
+
expr = `context.${field} like ${valueStr}`;
|
|
259
|
+
break;
|
|
204
260
|
default:
|
|
205
|
-
|
|
261
|
+
expr = `context.${field} == ${valueStr}`;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
// Auto-inject `context has` guard for optional fields
|
|
265
|
+
if (optionalFields?.has(field)) {
|
|
266
|
+
return `context has ${field} && ${expr}`;
|
|
206
267
|
}
|
|
268
|
+
return expr;
|
|
207
269
|
}
|
|
208
270
|
/**
|
|
209
271
|
* Convert a value to Cedar string representation.
|
|
@@ -225,7 +287,7 @@ function valueToString(value) {
|
|
|
225
287
|
* Generate the Cedar policy body (permit/forbid statement).
|
|
226
288
|
* All inputs are sanitized/escaped to prevent injection attacks.
|
|
227
289
|
*/
|
|
228
|
-
function generatePolicyBody(effect, principal, action, resource, conditions, rawCondition) {
|
|
290
|
+
function generatePolicyBody(effect, principal, action, resource, conditions, rawCondition, optionalFields) {
|
|
229
291
|
let policyLine = `${effect} (`;
|
|
230
292
|
// Principal
|
|
231
293
|
if (principal) {
|
|
@@ -283,7 +345,7 @@ function generatePolicyBody(effect, principal, action, resource, conditions, raw
|
|
|
283
345
|
}
|
|
284
346
|
else if (conditions.length > 0) {
|
|
285
347
|
// Fallback to structured conditions if rawCondition is rejected
|
|
286
|
-
const conditionStr = conditions.map(c => conditionToCedar(c)).join(' && ');
|
|
348
|
+
const conditionStr = conditions.map(c => conditionToCedar(c, optionalFields)).join(' && ');
|
|
287
349
|
policyLine += `\nwhen { ${conditionStr} };`;
|
|
288
350
|
}
|
|
289
351
|
else {
|
|
@@ -291,7 +353,7 @@ function generatePolicyBody(effect, principal, action, resource, conditions, raw
|
|
|
291
353
|
}
|
|
292
354
|
}
|
|
293
355
|
else if (conditions.length > 0) {
|
|
294
|
-
const conditionStr = conditions.map(c => conditionToCedar(c)).join(' && ');
|
|
356
|
+
const conditionStr = conditions.map(c => conditionToCedar(c, optionalFields)).join(' && ');
|
|
295
357
|
policyLine += `\nwhen { ${conditionStr} };`;
|
|
296
358
|
}
|
|
297
359
|
else {
|
|
@@ -303,6 +365,8 @@ function generatePolicyBody(effect, principal, action, resource, conditions, raw
|
|
|
303
365
|
* Convert a PolicyRule to Cedar policy text with proper annotations.
|
|
304
366
|
*
|
|
305
367
|
* @param rule - The PolicyRule to convert
|
|
368
|
+
* @param optionalFields - Set of context field names that are optional and need `context has` guards.
|
|
369
|
+
* Use `getOptionalFields()` to compute this from service context metadata.
|
|
306
370
|
* @returns Cedar policy text string
|
|
307
371
|
*
|
|
308
372
|
* @example
|
|
@@ -318,25 +382,22 @@ function generatePolicyBody(effect, principal, action, resource, conditions, raw
|
|
|
318
382
|
* order: 0,
|
|
319
383
|
* };
|
|
320
384
|
*
|
|
385
|
+
* // Without optional fields - no guards injected
|
|
321
386
|
* const cedar = ruleToCedar(rule);
|
|
322
|
-
*
|
|
323
|
-
* //
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
* //
|
|
328
|
-
* // action == Action::"call_tool",
|
|
329
|
-
* // resource
|
|
330
|
-
* // )
|
|
331
|
-
* // when { context.threat_count > 0 };
|
|
387
|
+
*
|
|
388
|
+
* // With optional fields - auto-injects `context has` guards
|
|
389
|
+
* import { OVERWATCH_CONTEXT } from '@highflame/policy/types';
|
|
390
|
+
* const optionalFields = getOptionalFields(OVERWATCH_CONTEXT, 'call_tool');
|
|
391
|
+
* const cedarWithGuards = ruleToCedar(rule, optionalFields);
|
|
392
|
+
* // when { context has threat_count && context.threat_count > 0 };
|
|
332
393
|
* ```
|
|
333
394
|
*/
|
|
334
|
-
export function ruleToCedar(rule) {
|
|
395
|
+
export function ruleToCedar(rule, optionalFields) {
|
|
335
396
|
const lines = [];
|
|
336
397
|
// Generate Cedar annotations
|
|
337
398
|
lines.push(...generateAnnotationLines(rule.annotations, rule.customAnnotations));
|
|
338
399
|
// Generate policy body
|
|
339
|
-
lines.push(generatePolicyBody(rule.effect, rule.principal, rule.action, rule.resource, rule.conditions, rule.rawCondition));
|
|
400
|
+
lines.push(generatePolicyBody(rule.effect, rule.principal, rule.action, rule.resource, rule.conditions, rule.rawCondition, optionalFields));
|
|
340
401
|
return lines.join('\n');
|
|
341
402
|
}
|
|
342
403
|
/**
|
|
@@ -345,24 +406,30 @@ export function ruleToCedar(rule) {
|
|
|
345
406
|
*
|
|
346
407
|
* @param rules - Array of PolicyRules to convert
|
|
347
408
|
* @param includeDisabled - If true, include disabled rules as comments (default: false)
|
|
409
|
+
* @param optionalFields - Set of context field names that are optional and need `context has` guards.
|
|
410
|
+
* Use `getOptionalFields()` to compute this from service context metadata.
|
|
348
411
|
* @returns Cedar policy text with all rules separated by blank lines
|
|
349
412
|
*
|
|
350
413
|
* @example
|
|
351
414
|
* ```typescript
|
|
352
415
|
* const rules: PolicyRule[] = [...];
|
|
353
416
|
* const cedarText = rulesToCedar(rules);
|
|
417
|
+
*
|
|
418
|
+
* // With optional fields awareness
|
|
419
|
+
* const optionalFields = getOptionalFields(OVERWATCH_CONTEXT, 'call_tool');
|
|
420
|
+
* const cedarText = rulesToCedar(rules, false, optionalFields);
|
|
354
421
|
* ```
|
|
355
422
|
*/
|
|
356
|
-
export function rulesToCedar(rules, includeDisabled = false) {
|
|
423
|
+
export function rulesToCedar(rules, includeDisabled = false, optionalFields) {
|
|
357
424
|
const sortedRules = [...rules].sort((a, b) => a.order - b.order);
|
|
358
425
|
const cedarPolicies = [];
|
|
359
426
|
for (const rule of sortedRules) {
|
|
360
427
|
if (rule.enabled) {
|
|
361
|
-
cedarPolicies.push(ruleToCedar(rule));
|
|
428
|
+
cedarPolicies.push(ruleToCedar(rule, optionalFields));
|
|
362
429
|
}
|
|
363
430
|
else if (includeDisabled) {
|
|
364
431
|
// Include disabled rules as comments
|
|
365
|
-
const cedarLines = ruleToCedar(rule).split('\n');
|
|
432
|
+
const cedarLines = ruleToCedar(rule, optionalFields).split('\n');
|
|
366
433
|
cedarPolicies.push(cedarLines.map(line => `// [DISABLED] ${line}`).join('\n'));
|
|
367
434
|
}
|
|
368
435
|
}
|
|
@@ -586,4 +653,3 @@ export function parseCedarPolicy(cedarText) {
|
|
|
586
653
|
return null;
|
|
587
654
|
}
|
|
588
655
|
}
|
|
589
|
-
//# sourceMappingURL=builder.js.map
|
package/dist/context.gen.d.ts
CHANGED
package/dist/context.gen.js
CHANGED
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:
|
|
53
|
+
readonly determining_policies: DeterminingPolicy[];
|
|
44
54
|
readonly reason?: string;
|
|
45
|
-
constructor(effect: "Allow" | "Deny", determining_policies:
|
|
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
|
|
@@ -143,4 +161,3 @@ export declare function validatePolicy(schema: string, policy: string): {
|
|
|
143
161
|
};
|
|
144
162
|
export { EntityType, EntityUID, Entity, newEntityUID, newEntity } from "./entities.gen.js";
|
|
145
163
|
export { ActionType, actionUID } from "./actions.gen.js";
|
|
146
|
-
//# sourceMappingURL=engine.d.ts.map
|
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
|
|
153
|
-
*
|
|
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
|
|
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
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -374,4 +404,3 @@ export function validatePolicy(schema, policy) {
|
|
|
374
404
|
// Re-export types
|
|
375
405
|
export { EntityType, newEntityUID, newEntity } from "./entities.gen.js";
|
|
376
406
|
export { ActionType, actionUID } from "./actions.gen.js";
|
|
377
|
-
//# sourceMappingURL=engine.js.map
|
package/dist/entities.gen.d.ts
CHANGED
|
@@ -47,4 +47,3 @@ export declare function newEntityUID(type: EntityType | string, id: string): Ent
|
|
|
47
47
|
* Create a new Entity.
|
|
48
48
|
*/
|
|
49
49
|
export declare function newEntity(type: EntityType | string, id: string, attrs?: Record<string, unknown>): Entity;
|
|
50
|
-
//# sourceMappingURL=entities.gen.d.ts.map
|
package/dist/entities.gen.js
CHANGED
package/dist/errors.d.ts
CHANGED
package/dist/errors.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -16,4 +16,3 @@ export { PALISADE_ENTITIES, PALISADE_ACTION_ENTITIES, } from './palisade-entitie
|
|
|
16
16
|
export type { ServiceEntityMetadata, ActionEntityMetadata } from './entity-metadata-types.gen.js';
|
|
17
17
|
export { OVERWATCH_DEFAULTS, OVERWATCH_TEMPLATES, OVERWATCH_CATEGORIES, OVERWATCH_TEMPLATES_JSON, getOverwatchDefaultsByCategory, getOverwatchTemplatesByCategory, getOverwatchTemplateById, } from './overwatch-defaults.gen.js';
|
|
18
18
|
export type { OverwatchCategory, OverwatchCategoryInfo, OverwatchDefaultPolicy, OverwatchTemplate, } from './overwatch-defaults.gen.js';
|
|
19
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -23,4 +23,3 @@ export { OVERWATCH_ENTITIES, OVERWATCH_ACTION_ENTITIES, } from './overwatch-enti
|
|
|
23
23
|
export { PALISADE_ENTITIES, PALISADE_ACTION_ENTITIES, } from './palisade-entities.gen.js';
|
|
24
24
|
// Service-specific default policies, templates, and categories
|
|
25
25
|
export { OVERWATCH_DEFAULTS, OVERWATCH_TEMPLATES, OVERWATCH_CATEGORIES, OVERWATCH_TEMPLATES_JSON, getOverwatchDefaultsByCategory, getOverwatchTemplatesByCategory, getOverwatchTemplateById, } from './overwatch-defaults.gen.js';
|
|
26
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -59,4 +59,3 @@ export declare const OVERWATCH_TEMPLATES_JSON: string;
|
|
|
59
59
|
export declare function getOverwatchDefaultsByCategory(category: OverwatchCategory): OverwatchDefaultPolicy[];
|
|
60
60
|
export declare function getOverwatchTemplatesByCategory(category: OverwatchCategory): OverwatchTemplate[];
|
|
61
61
|
export declare function getOverwatchTemplateById(id: string): OverwatchTemplate | undefined;
|
|
62
|
-
//# sourceMappingURL=overwatch-defaults.gen.d.ts.map
|