@highflame/policy 2.0.6 → 2.0.8
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 +163 -1
- package/_schemas/overwatch/schema.cedarschema +45 -0
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +13 -4
- package/dist/builder.js.map +1 -1
- package/dist/overwatch-context.gen.d.ts +13 -0
- package/dist/overwatch-context.gen.d.ts.map +1 -1
- package/dist/overwatch-context.gen.js +13 -0
- package/dist/overwatch-context.gen.js.map +1 -1
- package/dist/overwatch-defaults.gen.d.ts +1 -1
- package/dist/overwatch-defaults.gen.d.ts.map +1 -1
- package/dist/overwatch-defaults.gen.js +346 -1
- package/dist/overwatch-defaults.gen.js.map +1 -1
- package/dist/overwatch-defaults.test.js +5 -5
- package/dist/overwatch-defaults.test.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +60 -12
- package/dist/parser.js.map +1 -1
- package/dist/parser.test.js +76 -7
- package/dist/parser.test.js.map +1 -1
- package/dist/schemas.test.js +32 -0
- package/dist/schemas.test.js.map +1 -1
- package/package.json +1 -1
- package/src/builder.ts +11 -4
- package/src/overwatch-context.gen.ts +13 -0
- package/src/overwatch-defaults.gen.ts +350 -2
- package/src/overwatch-defaults.test.ts +5 -5
- package/src/parser.test.ts +89 -7
- package/src/parser.ts +66 -12
- package/src/schemas.test.ts +32 -0
package/src/parser.ts
CHANGED
|
@@ -119,7 +119,14 @@ export function parseCedarToRules(cedarText: string): ParseResult {
|
|
|
119
119
|
|
|
120
120
|
const policy = jsonResult.json as CedarPolicyJSON;
|
|
121
121
|
const policyId = policy.annotations?.id || `policy${index}`;
|
|
122
|
-
|
|
122
|
+
|
|
123
|
+
// Get engine-serialized Cedar text for rawCondition extraction.
|
|
124
|
+
// Using cedar-wasm's policyToText ensures we get the official engine's
|
|
125
|
+
// representation rather than relying on our own text extraction.
|
|
126
|
+
const engineTextResult = cedar.policyToText(jsonResult.json);
|
|
127
|
+
const engineText = engineTextResult.type === "success" ? engineTextResult.text : policyText;
|
|
128
|
+
|
|
129
|
+
const conversion = cedarJsonToRule(policy, policyId, index, engineText);
|
|
123
130
|
|
|
124
131
|
if (conversion.error) {
|
|
125
132
|
result.errors.push(`Policy ${policyId}: ${conversion.error}`);
|
|
@@ -207,7 +214,7 @@ function cedarJsonToRule(
|
|
|
207
214
|
};
|
|
208
215
|
|
|
209
216
|
// Map conditions
|
|
210
|
-
const { conditions, rawCondition } = mapConditions(policy.conditions);
|
|
217
|
+
const { conditions, rawCondition } = mapConditions(policy.conditions, originalText);
|
|
211
218
|
rule.conditions = conditions;
|
|
212
219
|
if (rawCondition) {
|
|
213
220
|
rule.rawCondition = rawCondition;
|
|
@@ -325,6 +332,20 @@ function mapScopeToEntity(scope: CedarScopeConstraint, field: string): PolicyEnt
|
|
|
325
332
|
*
|
|
326
333
|
* Throws ParserError for malformed constraints to prevent silent misinterpretation.
|
|
327
334
|
*/
|
|
335
|
+
/**
|
|
336
|
+
* Format action entity reference, preserving namespace if present.
|
|
337
|
+
*
|
|
338
|
+
* If entity type is just "Action", returns just the id (e.g., "process_prompt").
|
|
339
|
+
* If entity type has a namespace (e.g., "Overwatch::Action"),
|
|
340
|
+
* returns the fully qualified action string (e.g., 'Overwatch::Action::"process_prompt"').
|
|
341
|
+
*/
|
|
342
|
+
function formatActionEntity(entity: { type: string; id: string }): string {
|
|
343
|
+
if (entity.type !== "Action") {
|
|
344
|
+
return `${entity.type}::"${entity.id}"`;
|
|
345
|
+
}
|
|
346
|
+
return entity.id;
|
|
347
|
+
}
|
|
348
|
+
|
|
328
349
|
function mapActionScope(scope: CedarActionConstraint): string | string[] {
|
|
329
350
|
if (scope.op === "All") {
|
|
330
351
|
return "*";
|
|
@@ -333,19 +354,19 @@ function mapActionScope(scope: CedarActionConstraint): string | string[] {
|
|
|
333
354
|
if (scope.op === "==") {
|
|
334
355
|
if ("entity" in scope) {
|
|
335
356
|
const entity = normalizeEntityRef(scope.entity);
|
|
336
|
-
return entity
|
|
357
|
+
return formatActionEntity(entity);
|
|
337
358
|
}
|
|
338
359
|
throw ParserError.actionMissingEntity("==");
|
|
339
360
|
}
|
|
340
361
|
|
|
341
362
|
if (scope.op === "in") {
|
|
342
363
|
if ("entities" in scope) {
|
|
343
|
-
const actions = scope.entities.map(e => normalizeEntityRef(e)
|
|
364
|
+
const actions = scope.entities.map(e => formatActionEntity(normalizeEntityRef(e)));
|
|
344
365
|
return actions.length === 1 ? actions[0] : actions;
|
|
345
366
|
}
|
|
346
367
|
if ("entity" in scope) {
|
|
347
368
|
const entity = normalizeEntityRef(scope.entity);
|
|
348
|
-
return entity
|
|
369
|
+
return formatActionEntity(entity);
|
|
349
370
|
}
|
|
350
371
|
throw ParserError.actionMissingEntities();
|
|
351
372
|
}
|
|
@@ -354,14 +375,16 @@ function mapActionScope(scope: CedarActionConstraint): string | string[] {
|
|
|
354
375
|
}
|
|
355
376
|
|
|
356
377
|
/**
|
|
357
|
-
* Map Cedar conditions to PolicyCondition array
|
|
378
|
+
* Map Cedar conditions to PolicyCondition array.
|
|
379
|
+
* When conditions can't be mapped to structured format, extract the raw Cedar
|
|
380
|
+
* condition text from the engine-serialized policy text (not JSON AST).
|
|
358
381
|
*/
|
|
359
|
-
function mapConditions(conditions: CedarCondition[]): {
|
|
382
|
+
function mapConditions(conditions: CedarCondition[], originalText?: string): {
|
|
360
383
|
conditions: PolicyCondition[];
|
|
361
384
|
rawCondition?: string;
|
|
362
385
|
} {
|
|
363
386
|
const result: PolicyCondition[] = [];
|
|
364
|
-
|
|
387
|
+
let hasUnmapped = false;
|
|
365
388
|
|
|
366
389
|
for (const cond of conditions) {
|
|
367
390
|
if (cond.kind !== "when") {
|
|
@@ -372,18 +395,49 @@ function mapConditions(conditions: CedarCondition[]): {
|
|
|
372
395
|
if (parsed.condition) {
|
|
373
396
|
result.push(parsed.condition);
|
|
374
397
|
} else if (parsed.raw) {
|
|
375
|
-
|
|
398
|
+
hasUnmapped = true;
|
|
376
399
|
}
|
|
377
400
|
}
|
|
378
401
|
|
|
379
|
-
//
|
|
380
|
-
|
|
402
|
+
// Extract readable Cedar condition text instead of storing JSON AST
|
|
403
|
+
let rawCondition: string | undefined;
|
|
404
|
+
if (hasUnmapped && originalText) {
|
|
405
|
+
rawCondition = extractWhenClause(originalText);
|
|
406
|
+
}
|
|
407
|
+
|
|
381
408
|
return {
|
|
382
409
|
conditions: result,
|
|
383
|
-
rawCondition:
|
|
410
|
+
rawCondition: rawCondition || undefined,
|
|
384
411
|
};
|
|
385
412
|
}
|
|
386
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Extract the readable condition text from a Cedar policy's when clause.
|
|
416
|
+
* Given: `forbid (...)\nwhen { context.path like "/etc/*" };`
|
|
417
|
+
* Returns: `context.path like "/etc/*"`
|
|
418
|
+
*/
|
|
419
|
+
function extractWhenClause(cedarText: string): string {
|
|
420
|
+
const whenPrefix = "when {";
|
|
421
|
+
const idx = cedarText.indexOf(whenPrefix);
|
|
422
|
+
if (idx < 0) {
|
|
423
|
+
return "";
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const body = cedarText.substring(idx + whenPrefix.length);
|
|
427
|
+
let depth = 1;
|
|
428
|
+
for (let i = 0; i < body.length; i++) {
|
|
429
|
+
if (body[i] === "{") depth++;
|
|
430
|
+
if (body[i] === "}") {
|
|
431
|
+
depth--;
|
|
432
|
+
if (depth === 0) {
|
|
433
|
+
return body.substring(0, i).trim();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return body.trim();
|
|
439
|
+
}
|
|
440
|
+
|
|
387
441
|
/**
|
|
388
442
|
* Cedar expression types (subset used for condition mapping)
|
|
389
443
|
*/
|
package/src/schemas.test.ts
CHANGED
|
@@ -216,6 +216,22 @@ describe('Service-Specific Schemas', () => {
|
|
|
216
216
|
max_threat_severity: 1,
|
|
217
217
|
contains_secrets: false,
|
|
218
218
|
response_content: '',
|
|
219
|
+
// Trust/Safety scores
|
|
220
|
+
violence_score: 0,
|
|
221
|
+
weapons_score: 0,
|
|
222
|
+
hate_speech_score: 0,
|
|
223
|
+
crime_score: 0,
|
|
224
|
+
sexual_score: 0,
|
|
225
|
+
profanity_score: 0,
|
|
226
|
+
// Detector confidence
|
|
227
|
+
pii_confidence: 0,
|
|
228
|
+
injection_confidence: 0,
|
|
229
|
+
jailbreak_confidence: 0,
|
|
230
|
+
// Agent security
|
|
231
|
+
tool_poisoning_score: 0,
|
|
232
|
+
rug_pull_score: 0,
|
|
233
|
+
indirect_injection_score: 0,
|
|
234
|
+
mcp_server_verified: false,
|
|
219
235
|
},
|
|
220
236
|
entities,
|
|
221
237
|
});
|
|
@@ -397,6 +413,22 @@ describe('Service-Specific Schemas', () => {
|
|
|
397
413
|
max_threat_severity: 2,
|
|
398
414
|
contains_secrets: false,
|
|
399
415
|
response_content: '',
|
|
416
|
+
// Trust/Safety scores
|
|
417
|
+
violence_score: 0,
|
|
418
|
+
weapons_score: 0,
|
|
419
|
+
hate_speech_score: 0,
|
|
420
|
+
crime_score: 0,
|
|
421
|
+
sexual_score: 0,
|
|
422
|
+
profanity_score: 0,
|
|
423
|
+
// Detector confidence
|
|
424
|
+
pii_confidence: 0,
|
|
425
|
+
injection_confidence: 0,
|
|
426
|
+
jailbreak_confidence: 0,
|
|
427
|
+
// Agent security
|
|
428
|
+
tool_poisoning_score: 0,
|
|
429
|
+
rug_pull_score: 0,
|
|
430
|
+
indirect_injection_score: 0,
|
|
431
|
+
mcp_server_verified: false,
|
|
400
432
|
},
|
|
401
433
|
entities,
|
|
402
434
|
});
|