@highflame/policy 2.2.41 → 2.2.43
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/agent_ops/context.json +0 -42
- package/_schemas/agent_ops/schema.cedarschema +44 -6
- package/_schemas/ai_gateway/context.json +300 -0
- package/_schemas/ai_gateway/schema.cedarschema +107 -3
- package/_schemas/guardrails/context.json +0 -36
- package/_schemas/guardrails/detectors.json +0 -1
- package/_schemas/guardrails/schema.cedarschema +43 -5
- package/_schemas/guardrails/templates/dual_attribution.cedar +110 -0
- package/_schemas/guardrails/templates/multi_turn_trajectory.cedar +116 -0
- package/_schemas/guardrails/templates/profiles/output_protection/credentials.cedar +35 -0
- package/_schemas/guardrails/templates/profiles/output_protection/data_leakage.cedar +63 -0
- package/_schemas/guardrails/templates/profiles/output_protection/harmful_content.cedar +58 -0
- package/_schemas/guardrails/templates/profiles/output_protection/injection_carried_back.cedar +39 -0
- package/_schemas/guardrails/templates/session_risk_accumulation.cedar +103 -0
- package/_schemas/guardrails/templates/templates.json +157 -11
- package/_schemas/overwatch/context.json +65 -0
- package/_schemas/overwatch/detectors.json +47 -0
- package/_schemas/overwatch/schema.cedarschema +90 -0
- package/dist/agent_ops-context.gen.d.ts +1 -2
- package/dist/agent_ops-context.gen.js +0 -2
- package/dist/ai_gateway-entities.gen.js +5 -1
- package/dist/guardrails-context.gen.d.ts +1 -2
- package/dist/guardrails-context.gen.js +0 -2
- package/dist/guardrails-defaults.gen.js +751 -11
- package/dist/overwatch-context.gen.d.ts +3 -1
- package/dist/overwatch-context.gen.js +4 -0
- package/dist/overwatch-detectors.gen.js +16 -0
- package/dist/parser.d.ts +2 -1
- package/dist/parser.js +124 -37
- package/dist/service-schemas.gen.d.ts +4 -4
- package/dist/service-schemas.gen.js +354 -32
- package/package.json +1 -1
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Overwatch Cedar schema and are used at policy evaluation time.
|
|
6
6
|
*/
|
|
7
7
|
export declare const OverwatchContextKey: {
|
|
8
|
+
readonly BudgetExceeded: "budget_exceeded";
|
|
9
|
+
readonly BudgetRemainingPct: "budget_remaining_pct";
|
|
8
10
|
readonly Content: "content";
|
|
9
11
|
readonly CrimeScore: "crime_score";
|
|
10
12
|
readonly Cwd: "cwd";
|
|
@@ -99,4 +101,4 @@ export type OverwatchContextKey = (typeof OverwatchContextKey)[keyof typeof Over
|
|
|
99
101
|
* The full set of authorable context attribute keys for Overwatch.
|
|
100
102
|
* Iterate this to enumerate the authorable surface (cockpit, conformance).
|
|
101
103
|
*/
|
|
102
|
-
export declare const OverwatchContextKeys: readonly ["content", "crime_score", "cwd", "detected_threats", "event", "exec_target_paths", "exit_code", "hate_speech_score", "highest_severity", "indirect_injection_score", "injection_deep_context_score", "injection_pulse_score", "injection_score", "invisible_chars_detected", "invisible_chars_score", "jailbreak_deep_context_score", "jailbreak_pulse_score", "jailbreak_score", "loop_count", "loop_detected", "loop_tool", "malicious_package_detected", "malicious_package_score", "malicious_packages", "max_threat_severity", "mcp_config_risk", "mcp_risk_score", "mcp_server", "mcp_server_verified", "mcp_tool", "package_advisory_count", "package_check_status", "package_ecosystems", "package_install_detected", "package_names", "package_risk_score", "packages_checked", "path", "pattern_type", "pii_count", "pii_detected", "pii_score", "pii_types", "privilege_scope", "profanity_score", "prompt_text", "read_target_paths", "resolved_target_paths", "response_content", "role", "rug_pull_detected", "rug_pull_score", "secret_count", "secret_types", "secrets_detected", "sequence_risk", "session_command_injection", "session_cumulative_risk_score", "session_injection_detected", "session_max_command_injection_score", "session_max_injection_score", "session_max_jailbreak_score", "session_max_pii_score", "session_max_secret_score", "session_pii_detected", "session_pii_types", "session_secret_types", "session_secrets_detected", "session_threat_turns", "sexual_score", "source", "suspicious_pattern", "threat_categories", "threat_count", "tool_category", "tool_is_builtin", "tool_is_sensitive", "tool_name", "tool_operation_classes", "tool_poisoning_detected", "tool_poisoning_score", "tool_risk_score", "unresolved_target", "user_email", "violence_score", "weapons_score", "workspace_root", "write_target_paths"];
|
|
104
|
+
export declare const OverwatchContextKeys: readonly ["budget_exceeded", "budget_remaining_pct", "content", "crime_score", "cwd", "detected_threats", "event", "exec_target_paths", "exit_code", "hate_speech_score", "highest_severity", "indirect_injection_score", "injection_deep_context_score", "injection_pulse_score", "injection_score", "invisible_chars_detected", "invisible_chars_score", "jailbreak_deep_context_score", "jailbreak_pulse_score", "jailbreak_score", "loop_count", "loop_detected", "loop_tool", "malicious_package_detected", "malicious_package_score", "malicious_packages", "max_threat_severity", "mcp_config_risk", "mcp_risk_score", "mcp_server", "mcp_server_verified", "mcp_tool", "package_advisory_count", "package_check_status", "package_ecosystems", "package_install_detected", "package_names", "package_risk_score", "packages_checked", "path", "pattern_type", "pii_count", "pii_detected", "pii_score", "pii_types", "privilege_scope", "profanity_score", "prompt_text", "read_target_paths", "resolved_target_paths", "response_content", "role", "rug_pull_detected", "rug_pull_score", "secret_count", "secret_types", "secrets_detected", "sequence_risk", "session_command_injection", "session_cumulative_risk_score", "session_injection_detected", "session_max_command_injection_score", "session_max_injection_score", "session_max_jailbreak_score", "session_max_pii_score", "session_max_secret_score", "session_pii_detected", "session_pii_types", "session_secret_types", "session_secrets_detected", "session_threat_turns", "sexual_score", "source", "suspicious_pattern", "threat_categories", "threat_count", "tool_category", "tool_is_builtin", "tool_is_sensitive", "tool_name", "tool_operation_classes", "tool_poisoning_detected", "tool_poisoning_score", "tool_risk_score", "unresolved_target", "user_email", "violence_score", "weapons_score", "workspace_root", "write_target_paths"];
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* Overwatch Cedar schema and are used at policy evaluation time.
|
|
8
8
|
*/
|
|
9
9
|
export const OverwatchContextKey = {
|
|
10
|
+
BudgetExceeded: 'budget_exceeded',
|
|
11
|
+
BudgetRemainingPct: 'budget_remaining_pct',
|
|
10
12
|
Content: 'content',
|
|
11
13
|
CrimeScore: 'crime_score',
|
|
12
14
|
Cwd: 'cwd',
|
|
@@ -101,6 +103,8 @@ export const OverwatchContextKey = {
|
|
|
101
103
|
* Iterate this to enumerate the authorable surface (cockpit, conformance).
|
|
102
104
|
*/
|
|
103
105
|
export const OverwatchContextKeys = [
|
|
106
|
+
OverwatchContextKey.BudgetExceeded,
|
|
107
|
+
OverwatchContextKey.BudgetRemainingPct,
|
|
104
108
|
OverwatchContextKey.Content,
|
|
105
109
|
OverwatchContextKey.CrimeScore,
|
|
106
110
|
OverwatchContextKey.Cwd,
|
|
@@ -84,6 +84,20 @@ export const OVERWATCH_DETECTORS = [
|
|
|
84
84
|
defendsAgainst: ["unbounded_consumption", "excessive_agency"],
|
|
85
85
|
exampleAttacks: [{ title: "Runaway tool loop", vulnerabilityId: "unbounded_consumption", snippet: "(agentic) the same shell command is invoked 30x in a row", expectedSignal: { "loop_detected": true } }],
|
|
86
86
|
},
|
|
87
|
+
{
|
|
88
|
+
id: "budget_checker",
|
|
89
|
+
displayName: "Budget Checker",
|
|
90
|
+
category: "agent_behavior",
|
|
91
|
+
stability: "stable",
|
|
92
|
+
tier: "fast",
|
|
93
|
+
inhouse: false,
|
|
94
|
+
model: null,
|
|
95
|
+
latencyP50Ms: null,
|
|
96
|
+
emits: [{ name: "budget_remaining_pct", type: "Long", modifiable: false, semantic: "severity_0_100", description: "Remaining session token budget (0-100). Default-filled to 100 when no metering ran." }, { name: "budget_exceeded", type: "Bool", modifiable: false, semantic: "boolean_flag", description: "True iff the session token budget has been exceeded. Absent when no metering ran, so policies must guard with `context has`." }],
|
|
97
|
+
supportedModes: ["enforce", "monitor", "alert"],
|
|
98
|
+
defendsAgainst: ["unbounded_consumption"],
|
|
99
|
+
exampleAttacks: [{ title: "Runaway autonomous session", vulnerabilityId: "unbounded_consumption", snippet: "(agentic) one prompt drives hundreds of tool calls, burning the session's token budget unattended", expectedSignal: { "budget_exceeded": true } }],
|
|
100
|
+
},
|
|
87
101
|
{
|
|
88
102
|
id: "tool_risk",
|
|
89
103
|
displayName: "Tool Risk",
|
|
@@ -214,6 +228,8 @@ export const OVERWATCH_DETECTORS = [
|
|
|
214
228
|
// Semantic field → contributing detector ids (producesAttrs + normalizationAliases,
|
|
215
229
|
// resolved at codegen). Used by the client field→detector resolver — no Shield round-trip.
|
|
216
230
|
export const OVERWATCH_FIELD_TO_DETECTORS = {
|
|
231
|
+
"budget_exceeded": ["budget_checker"],
|
|
232
|
+
"budget_remaining_pct": ["budget_checker"],
|
|
217
233
|
"crime_score": ["toxicity"],
|
|
218
234
|
"exec_target_paths": ["bash_ast_classifier"],
|
|
219
235
|
"hate_speech_score": ["toxicity"],
|
package/dist/parser.d.ts
CHANGED
|
@@ -25,7 +25,8 @@ export interface ParseResult {
|
|
|
25
25
|
*
|
|
26
26
|
* Uses the official cedar-wasm engine for parsing, ensuring correctness.
|
|
27
27
|
* Policies with features that can't be represented as PolicyRule (e.g.,
|
|
28
|
-
*
|
|
28
|
+
* template slots) are returned in the unstructured array. Cedar `unless`
|
|
29
|
+
* clauses are represented as negated condition expressions.
|
|
29
30
|
*
|
|
30
31
|
* @param cedarText - Cedar policy text to parse
|
|
31
32
|
* @returns ParseResult with structured rules, unstructured policies, and errors
|
package/dist/parser.js
CHANGED
|
@@ -25,7 +25,8 @@ function normalizeEntityRef(ref) {
|
|
|
25
25
|
*
|
|
26
26
|
* Uses the official cedar-wasm engine for parsing, ensuring correctness.
|
|
27
27
|
* Policies with features that can't be represented as PolicyRule (e.g.,
|
|
28
|
-
*
|
|
28
|
+
* template slots) are returned in the unstructured array. Cedar `unless`
|
|
29
|
+
* clauses are represented as negated condition expressions.
|
|
29
30
|
*
|
|
30
31
|
* @param cedarText - Cedar policy text to parse
|
|
31
32
|
* @returns ParseResult with structured rules, unstructured policies, and errors
|
|
@@ -154,12 +155,6 @@ function cedarJsonToRule(policy, policyId, index, originalText) {
|
|
|
154
155
|
* Check if a Cedar policy can be represented as PolicyRule
|
|
155
156
|
*/
|
|
156
157
|
function canRepresentAsRule(policy) {
|
|
157
|
-
// Unless clauses can't be represented
|
|
158
|
-
for (const cond of policy.conditions) {
|
|
159
|
-
if (cond.kind === "unless") {
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
158
|
// Template slots can't be represented
|
|
164
159
|
if (hasSlot(policy.principal) || hasSlot(policy.resource)) {
|
|
165
160
|
return false;
|
|
@@ -289,27 +284,38 @@ function mapActionScope(scope) {
|
|
|
289
284
|
function mapConditions(conditions, originalText) {
|
|
290
285
|
const result = [];
|
|
291
286
|
let hasUnmapped = false;
|
|
292
|
-
// Collect condition expressions from all when clauses
|
|
287
|
+
// Collect condition expressions from all when/unless clauses.
|
|
293
288
|
const expressions = [];
|
|
289
|
+
// A policy may carry several `when` clauses, which Cedar ANDs together.
|
|
290
|
+
// Each clause must recover ITS OWN text: a previous version always read the
|
|
291
|
+
// first clause, so a second clause that fell through to the `raw` fallback
|
|
292
|
+
// was stored as a duplicate of the first and its real content was lost.
|
|
293
|
+
const clauses = originalText ? extractConditionClauses(originalText) : [];
|
|
294
|
+
let clauseIndex = 0;
|
|
294
295
|
for (const cond of conditions) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
296
|
+
const clauseText = clauses[clauseIndex]?.text;
|
|
297
|
+
clauseIndex++;
|
|
298
298
|
// Flat mapping (backward compat)
|
|
299
299
|
const parsed = mapConditionBody(cond.body);
|
|
300
|
-
if (parsed.condition) {
|
|
300
|
+
if (cond.kind === "when" && parsed.condition) {
|
|
301
301
|
result.push(parsed.condition);
|
|
302
302
|
}
|
|
303
|
-
else if (parsed.raw) {
|
|
303
|
+
else if (parsed.raw || cond.kind === "unless") {
|
|
304
304
|
hasUnmapped = true;
|
|
305
305
|
}
|
|
306
|
-
|
|
307
|
-
expressions.push(
|
|
306
|
+
const expression = mapConditionBodyToExpression(cond.body, clauseText);
|
|
307
|
+
expressions.push(cond.kind === "unless" ? { kind: 'not', child: expression } : expression);
|
|
308
308
|
}
|
|
309
|
-
// Extract readable Cedar condition text instead of storing JSON AST
|
|
309
|
+
// Extract readable Cedar condition text instead of storing JSON AST.
|
|
310
|
+
// With several clauses, join them — `when { A } when { B }` is `A && B`, so
|
|
311
|
+
// collapsing them into one guard keeps the regenerated policy equivalent
|
|
312
|
+
// rather than silently dropping every clause after the first.
|
|
310
313
|
let rawCondition;
|
|
311
|
-
if (hasUnmapped &&
|
|
312
|
-
|
|
314
|
+
if (hasUnmapped && clauses.length > 0) {
|
|
315
|
+
const equivalent = clauses.map(({ kind, text }) => kind === 'unless' ? `!(${text})` : `(${text})`);
|
|
316
|
+
rawCondition = clauses.length === 1 && clauses[0].kind === 'when'
|
|
317
|
+
? clauses[0].text
|
|
318
|
+
: equivalent.join(" && ");
|
|
313
319
|
}
|
|
314
320
|
// Build the final condition expression
|
|
315
321
|
let conditionExpression;
|
|
@@ -326,29 +332,105 @@ function mapConditions(conditions, originalText) {
|
|
|
326
332
|
};
|
|
327
333
|
}
|
|
328
334
|
/**
|
|
329
|
-
* Extract the readable condition text
|
|
330
|
-
*
|
|
331
|
-
*
|
|
335
|
+
* Extract the readable condition text of every `when` clause, in source order.
|
|
336
|
+
*
|
|
337
|
+
* Given: `forbid (...)\nwhen { context.path like "/etc/*" }\nwhen { resource in X };`
|
|
338
|
+
* Returns: `['context.path like "/etc/*"', 'resource in X']`
|
|
339
|
+
*
|
|
340
|
+
* Scanning is string-literal aware, which matters for correctness rather than
|
|
341
|
+
* tidiness — a naive brace count mis-reads a clause in two ways, and this text
|
|
342
|
+
* is re-emitted into policy Cedar via `rawCondition`:
|
|
343
|
+
*
|
|
344
|
+
* when { context.path like "/etc/}" && context.a == 1 }
|
|
345
|
+
* → a `}` inside the literal ends the clause early, yielding text that is
|
|
346
|
+
* cut mid-string and is not valid Cedar.
|
|
347
|
+
* when { context.msg == "when { x" }
|
|
348
|
+
* → the `{` inside the literal unbalances the count, so the scan runs past
|
|
349
|
+
* the clause and swallows the `;` and whatever follows.
|
|
350
|
+
*
|
|
351
|
+
* The builder's dangerous-pattern guard rejects the second shape, but a
|
|
352
|
+
* rejected rawCondition with no flat conditions to fall back on emits the rule
|
|
353
|
+
* with NO `when` clause at all — an unconditional permit or forbid. Getting the
|
|
354
|
+
* boundaries right is what keeps that path unreachable.
|
|
355
|
+
*
|
|
356
|
+
* Brace depth still applies outside literals, so record literals (`{ a: 1 }`)
|
|
357
|
+
* inside a clause do not terminate it early.
|
|
332
358
|
*/
|
|
333
|
-
function
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
359
|
+
function extractWhenClauses(cedarText) {
|
|
360
|
+
return extractConditionClauses(cedarText)
|
|
361
|
+
.filter(({ kind }) => kind === 'when')
|
|
362
|
+
.map(({ text }) => text);
|
|
363
|
+
}
|
|
364
|
+
function extractConditionClauses(cedarText) {
|
|
365
|
+
const result = [];
|
|
366
|
+
let i = 0;
|
|
367
|
+
let inString = false;
|
|
368
|
+
while (i < cedarText.length) {
|
|
369
|
+
const ch = cedarText[i];
|
|
370
|
+
if (inString) {
|
|
371
|
+
if (ch === "\\") {
|
|
372
|
+
i += 2;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (ch === '"')
|
|
376
|
+
inString = false;
|
|
377
|
+
i++;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (ch === '"') {
|
|
381
|
+
inString = true;
|
|
382
|
+
i++;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const precededByIdent = i > 0 && /[A-Za-z0-9_]/.test(cedarText[i - 1]);
|
|
386
|
+
const kind = cedarText.startsWith('when {', i)
|
|
387
|
+
? 'when'
|
|
388
|
+
: cedarText.startsWith('unless {', i)
|
|
389
|
+
? 'unless'
|
|
390
|
+
: undefined;
|
|
391
|
+
if (!precededByIdent && kind) {
|
|
392
|
+
const start = i + `${kind} {`.length;
|
|
393
|
+
const end = findClauseEnd(cedarText, start);
|
|
394
|
+
result.push({ kind, text: cedarText.substring(start, end).trim() });
|
|
395
|
+
i = Math.min(end + 1, cedarText.length);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
i++;
|
|
338
399
|
}
|
|
339
|
-
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Index of the `}` closing a clause body that starts at `start`, or the end of
|
|
404
|
+
* the text when the braces never balance. String-literal aware — see
|
|
405
|
+
* `extractWhenClauses`.
|
|
406
|
+
*/
|
|
407
|
+
function findClauseEnd(cedarText, start) {
|
|
340
408
|
let depth = 1;
|
|
341
|
-
|
|
342
|
-
|
|
409
|
+
let inString = false;
|
|
410
|
+
for (let i = start; i < cedarText.length; i++) {
|
|
411
|
+
const ch = cedarText[i];
|
|
412
|
+
if (inString) {
|
|
413
|
+
if (ch === "\\") {
|
|
414
|
+
i++;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (ch === '"')
|
|
418
|
+
inString = false;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (ch === '"') {
|
|
422
|
+
inString = true;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (ch === "{")
|
|
343
426
|
depth++;
|
|
344
|
-
if (
|
|
427
|
+
if (ch === "}") {
|
|
345
428
|
depth--;
|
|
346
|
-
if (depth === 0)
|
|
347
|
-
return
|
|
348
|
-
}
|
|
429
|
+
if (depth === 0)
|
|
430
|
+
return i;
|
|
349
431
|
}
|
|
350
432
|
}
|
|
351
|
-
return
|
|
433
|
+
return cedarText.length;
|
|
352
434
|
}
|
|
353
435
|
/**
|
|
354
436
|
* Map a Cedar expression body to PolicyCondition
|
|
@@ -388,8 +470,13 @@ function mapConditionBody(body) {
|
|
|
388
470
|
/**
|
|
389
471
|
* Recursively walk the Cedar JSON AST to build a ConditionExpression tree.
|
|
390
472
|
* Flattens binary && / || chains into n-ary and/or nodes.
|
|
473
|
+
*
|
|
474
|
+
* `clauseText` is the source text of the `when` clause this expression came
|
|
475
|
+
* from — already extracted by the caller, never the whole policy. It is only
|
|
476
|
+
* used for the `raw` fallback below, so a clause the walker cannot model keeps
|
|
477
|
+
* its own text instead of borrowing another clause's.
|
|
391
478
|
*/
|
|
392
|
-
function mapConditionBodyToExpression(expr,
|
|
479
|
+
function mapConditionBodyToExpression(expr, clauseText) {
|
|
393
480
|
// Logical AND — flatten binary chain
|
|
394
481
|
if (expr["&&"]) {
|
|
395
482
|
const children = flattenBinaryChain("&&", expr);
|
|
@@ -402,7 +489,7 @@ function mapConditionBodyToExpression(expr, originalText) {
|
|
|
402
489
|
}
|
|
403
490
|
// Negation
|
|
404
491
|
if (expr["!"]) {
|
|
405
|
-
const child = mapConditionBodyToExpression(expr["!"].arg,
|
|
492
|
+
const child = mapConditionBodyToExpression(expr["!"].arg, clauseText);
|
|
406
493
|
return { kind: 'not', child };
|
|
407
494
|
}
|
|
408
495
|
// Has (existence check): { has: { left: { Var: "context" }, attr: "field" } }
|
|
@@ -434,7 +521,7 @@ function mapConditionBodyToExpression(expr, originalText) {
|
|
|
434
521
|
return mapped;
|
|
435
522
|
}
|
|
436
523
|
// Fallback — extract readable text if possible
|
|
437
|
-
const text =
|
|
524
|
+
const text = clauseText ?? JSON.stringify(expr);
|
|
438
525
|
return { kind: 'raw', text };
|
|
439
526
|
}
|
|
440
527
|
/**
|