@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
|
@@ -25,12 +25,6 @@
|
|
|
25
25
|
"required": false,
|
|
26
26
|
"description": "Principal identity class projected from the token: 'human', 'agent', or 'service'. Use to apply identity-class-specific policies"
|
|
27
27
|
},
|
|
28
|
-
{
|
|
29
|
-
"key": "principal",
|
|
30
|
-
"type": "string",
|
|
31
|
-
"required": false,
|
|
32
|
-
"description": "Stable principal identifier projected from the token (e.g. a ZeroID / WIMSE URI or user id). Absent when the token carries no principal claim"
|
|
33
|
-
},
|
|
34
28
|
{
|
|
35
29
|
"key": "request_id",
|
|
36
30
|
"type": "string",
|
|
@@ -696,12 +690,6 @@
|
|
|
696
690
|
"required": false,
|
|
697
691
|
"description": "Principal identity class projected from the token: 'human', 'agent', or 'service'. Use to apply identity-class-specific policies"
|
|
698
692
|
},
|
|
699
|
-
{
|
|
700
|
-
"key": "principal",
|
|
701
|
-
"type": "string",
|
|
702
|
-
"required": false,
|
|
703
|
-
"description": "Stable principal identifier projected from the token (e.g. a ZeroID / WIMSE URI or user id). Absent when the token carries no principal claim"
|
|
704
|
-
},
|
|
705
693
|
{
|
|
706
694
|
"key": "request_id",
|
|
707
695
|
"type": "string",
|
|
@@ -1367,12 +1355,6 @@
|
|
|
1367
1355
|
"required": false,
|
|
1368
1356
|
"description": "Principal identity class projected from the token: 'human', 'agent', or 'service'. Use to apply identity-class-specific policies"
|
|
1369
1357
|
},
|
|
1370
|
-
{
|
|
1371
|
-
"key": "principal",
|
|
1372
|
-
"type": "string",
|
|
1373
|
-
"required": false,
|
|
1374
|
-
"description": "Stable principal identifier projected from the token (e.g. a ZeroID / WIMSE URI or user id). Absent when the token carries no principal claim"
|
|
1375
|
-
},
|
|
1376
1358
|
{
|
|
1377
1359
|
"key": "request_id",
|
|
1378
1360
|
"type": "string",
|
|
@@ -2098,12 +2080,6 @@
|
|
|
2098
2080
|
"required": false,
|
|
2099
2081
|
"description": "Principal identity class projected from the token: 'human', 'agent', or 'service'. Use to apply identity-class-specific policies"
|
|
2100
2082
|
},
|
|
2101
|
-
{
|
|
2102
|
-
"key": "principal",
|
|
2103
|
-
"type": "string",
|
|
2104
|
-
"required": false,
|
|
2105
|
-
"description": "Stable principal identifier projected from the token (e.g. a ZeroID / WIMSE URI or user id). Absent when the token carries no principal claim"
|
|
2106
|
-
},
|
|
2107
2083
|
{
|
|
2108
2084
|
"key": "request_id",
|
|
2109
2085
|
"type": "string",
|
|
@@ -2345,12 +2321,6 @@
|
|
|
2345
2321
|
"required": false,
|
|
2346
2322
|
"description": "Principal identity class projected from the token: 'human', 'agent', or 'service'. Use to apply identity-class-specific policies"
|
|
2347
2323
|
},
|
|
2348
|
-
{
|
|
2349
|
-
"key": "principal",
|
|
2350
|
-
"type": "string",
|
|
2351
|
-
"required": false,
|
|
2352
|
-
"description": "Stable principal identifier projected from the token (e.g. a ZeroID / WIMSE URI or user id). Absent when the token carries no principal claim"
|
|
2353
|
-
},
|
|
2354
2324
|
{
|
|
2355
2325
|
"key": "request_id",
|
|
2356
2326
|
"type": "string",
|
|
@@ -2605,12 +2575,6 @@
|
|
|
2605
2575
|
"required": false,
|
|
2606
2576
|
"description": "Principal identity class projected from the token: 'human', 'agent', or 'service'. Use to apply identity-class-specific policies"
|
|
2607
2577
|
},
|
|
2608
|
-
{
|
|
2609
|
-
"key": "principal",
|
|
2610
|
-
"type": "string",
|
|
2611
|
-
"required": false,
|
|
2612
|
-
"description": "Stable principal identifier projected from the token (e.g. a ZeroID / WIMSE URI or user id). Absent when the token carries no principal claim"
|
|
2613
|
-
},
|
|
2614
2578
|
{
|
|
2615
2579
|
"key": "request_id",
|
|
2616
2580
|
"type": "string",
|
|
@@ -105,6 +105,44 @@ namespace Guardrails {
|
|
|
105
105
|
// Context Types (Action-Specific)
|
|
106
106
|
// =========================================================================
|
|
107
107
|
|
|
108
|
+
/// The ZeroID NHI principal block (ADR 0009 / CAP-IDN-011).
|
|
109
|
+
///
|
|
110
|
+
/// A record, not a string: Shield projects the whole identity block under
|
|
111
|
+
/// `context.principal`, so policies read members off it —
|
|
112
|
+
///
|
|
113
|
+
/// permit ... when { context has principal &&
|
|
114
|
+
/// context.principal.trust_level == "first_party" };
|
|
115
|
+
/// forbid ... unless { context has principal &&
|
|
116
|
+
/// context.principal has act_sub };
|
|
117
|
+
///
|
|
118
|
+
/// Declared once and shared by every action context so a new member cannot
|
|
119
|
+
/// land on one action and go missing on another.
|
|
120
|
+
///
|
|
121
|
+
/// `act` (RFC 8693 delegation) is flattened to `act_sub` / `act_iss`
|
|
122
|
+
/// because Cedar records are flat key/value at each level; the full
|
|
123
|
+
/// delegation tree is reconstructable audit-side by joining on `mission_id`.
|
|
124
|
+
///
|
|
125
|
+
/// Every member is optional except `delegation_depth`: the projector omits
|
|
126
|
+
/// a field whose value is empty, but always emits the depth because zero is
|
|
127
|
+
/// the meaningful base case for a direct (non-delegated) token. The record
|
|
128
|
+
/// itself is optional — it is absent, not empty, when the request carries no
|
|
129
|
+
/// NHI claims (RS256 human tokens, internal-service callers), so
|
|
130
|
+
/// `context has principal` is false rather than true-but-empty.
|
|
131
|
+
type PrincipalContext = {
|
|
132
|
+
"identity_type"?: String, // "human" | "agent" | "service"
|
|
133
|
+
"sub_type"?: String, // Identity subtype (e.g. "coding_agent")
|
|
134
|
+
"trust_level"?: String, // "first_party" | "verified" | "unverified"
|
|
135
|
+
"framework"?: String, // Agent framework (e.g. "langchain")
|
|
136
|
+
"publisher"?: String, // Agent publisher
|
|
137
|
+
"capabilities"?: Set<String>, // Capabilities granted to the identity
|
|
138
|
+
"scopes"?: Set<String>, // OAuth scopes carried by the token
|
|
139
|
+
"delegation_depth": Long, // Delegation hops; 0 for a direct token
|
|
140
|
+
"act_sub"?: String, // RFC 8693 actor — the accountable human
|
|
141
|
+
"act_iss"?: String, // Issuer of the actor claim
|
|
142
|
+
"mission_id"?: String, // Correlates one delegation tree
|
|
143
|
+
"grant_type"?: String, // How the token was obtained (e.g. "api_key")
|
|
144
|
+
};
|
|
145
|
+
|
|
108
146
|
/// Context for the process_response action.
|
|
109
147
|
///
|
|
110
148
|
/// An alias, not a copy: prompts and responses are inspected by the same
|
|
@@ -121,7 +159,7 @@ namespace Guardrails {
|
|
|
121
159
|
"role"?: String,
|
|
122
160
|
"privilege_scope"?: Set<String>,
|
|
123
161
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
124
|
-
"principal"?:
|
|
162
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
125
163
|
// Core metadata (required)
|
|
126
164
|
"request_id": String,
|
|
127
165
|
"timestamp": Long,
|
|
@@ -273,7 +311,7 @@ namespace Guardrails {
|
|
|
273
311
|
"role"?: String,
|
|
274
312
|
"privilege_scope"?: Set<String>,
|
|
275
313
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
276
|
-
"principal"?:
|
|
314
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
277
315
|
// Core metadata (required)
|
|
278
316
|
"request_id": String,
|
|
279
317
|
"timestamp": Long,
|
|
@@ -469,7 +507,7 @@ namespace Guardrails {
|
|
|
469
507
|
"role"?: String,
|
|
470
508
|
"privilege_scope"?: Set<String>,
|
|
471
509
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
472
|
-
"principal"?:
|
|
510
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
473
511
|
// Core metadata (required)
|
|
474
512
|
"request_id": String,
|
|
475
513
|
"timestamp": Long,
|
|
@@ -542,7 +580,7 @@ namespace Guardrails {
|
|
|
542
580
|
"role"?: String,
|
|
543
581
|
"privilege_scope"?: Set<String>,
|
|
544
582
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
545
|
-
"principal"?:
|
|
583
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
546
584
|
// Core metadata (required)
|
|
547
585
|
"request_id": String,
|
|
548
586
|
"timestamp": Long,
|
|
@@ -619,7 +657,7 @@ namespace Guardrails {
|
|
|
619
657
|
"role"?: String,
|
|
620
658
|
"privilege_scope"?: Set<String>,
|
|
621
659
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
622
|
-
"principal"?:
|
|
660
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
623
661
|
// Core metadata (required)
|
|
624
662
|
"request_id": String,
|
|
625
663
|
"timestamp": Long,
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Dual Attribution
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Blocks privileged agent actions that cannot be attributed to a human. "Which
|
|
5
|
+
// agent did this?" is half an answer; the other half is "on whose behalf?" An
|
|
6
|
+
// agent is not an accountable party — the person who pointed it at the work
|
|
7
|
+
// is. Shield projects both sides of that pair, so the requirement can be a
|
|
8
|
+
// policy rather than a reporting convention nobody enforces.
|
|
9
|
+
//
|
|
10
|
+
// The agent comes from the authenticated identity, never from the request
|
|
11
|
+
// body: agent_id, agent_type, agent_trust_level, agent_framework. The human
|
|
12
|
+
// comes from the identity claims on the credential, as the `principal` record
|
|
13
|
+
// — its `act_sub` field names the party the agent is acting for.
|
|
14
|
+
//
|
|
15
|
+
// Deploy in MONITOR mode first. Section 2 blocks unverified agents, and a
|
|
16
|
+
// service key authenticates as unverified until the agent is registered and
|
|
17
|
+
// adopted in Studio. Monitor records what each rule would have blocked on
|
|
18
|
+
// every event without blocking anything; register the agent, then switch to
|
|
19
|
+
// enforce.
|
|
20
|
+
//
|
|
21
|
+
// Detection layers:
|
|
22
|
+
// - agent identity (authentication layer, always available)
|
|
23
|
+
// - tool_validator (tool_is_sensitive, tool_category, always available)
|
|
24
|
+
//
|
|
25
|
+
// Context keys consumed:
|
|
26
|
+
// - agent_id, agent_type, agent_trust_level: String
|
|
27
|
+
// - principal: record — act_sub names the accountable human
|
|
28
|
+
// - tool_is_sensitive: Bool
|
|
29
|
+
// - tool_category: String
|
|
30
|
+
//
|
|
31
|
+
// Compliance:
|
|
32
|
+
// - OWASP ASI01, OWASP ASI04
|
|
33
|
+
//
|
|
34
|
+
// Category: agent-identity
|
|
35
|
+
// Namespace: Guardrails
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Section 1: No unattributed privileged action
|
|
40
|
+
// `unless` because the rule must fire when attribution is ABSENT, and an
|
|
41
|
+
// absent field cannot be compared — only tested for.
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
@id("agent-identity.require-principal-for-sensitive-tools")
|
|
45
|
+
@name("Block sensitive agent tools without a principal")
|
|
46
|
+
@description("Blocks call_tool on a sensitive tool by an agent unless the credential carries a principal whose act_sub names the human the agent acts for.")
|
|
47
|
+
@severity("critical")
|
|
48
|
+
@tags("category:agent-identity,detection:rule,surface:call-tool,scope:per-agent,posture:deny-default,owasp:asi01")
|
|
49
|
+
@reject_message("Tool execution blocked: this agent invoked a sensitive tool with no accountable human attached. Every privileged agent action must name the person it acts for.")
|
|
50
|
+
forbid (
|
|
51
|
+
principal,
|
|
52
|
+
action == Guardrails::Action::"call_tool",
|
|
53
|
+
resource
|
|
54
|
+
)
|
|
55
|
+
when {
|
|
56
|
+
context has agent_id && context.agent_id != "" &&
|
|
57
|
+
context has tool_is_sensitive && context.tool_is_sensitive == true
|
|
58
|
+
}
|
|
59
|
+
unless {
|
|
60
|
+
context has principal && context.principal has act_sub
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Section 2: Trust level gates the blast radius
|
|
65
|
+
// Start in monitor: a service key is unverified until the agent is adopted.
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
@id("agent-identity.block-unverified-agent-sensitive-tools")
|
|
69
|
+
@name("Block unverified agents from sensitive tools")
|
|
70
|
+
@description("Blocks call_tool when agent_trust_level is unverified and the tool is sensitive or dangerous.")
|
|
71
|
+
@severity("critical")
|
|
72
|
+
@tags("category:agent-identity,detection:rule,surface:call-tool,scope:per-agent,posture:deny-default,owasp:asi01")
|
|
73
|
+
@reject_message("Tool execution blocked: unverified agents may not call sensitive or dangerous tools. Register and adopt the agent to raise its trust level.")
|
|
74
|
+
forbid (
|
|
75
|
+
principal,
|
|
76
|
+
action == Guardrails::Action::"call_tool",
|
|
77
|
+
resource
|
|
78
|
+
)
|
|
79
|
+
when {
|
|
80
|
+
context has agent_trust_level && context.agent_trust_level == "unverified" &&
|
|
81
|
+
(
|
|
82
|
+
(context has tool_is_sensitive && context.tool_is_sensitive == true) ||
|
|
83
|
+
(context has tool_category && context.tool_category == "dangerous")
|
|
84
|
+
)
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Section 3: Autonomous agents get a higher bar
|
|
89
|
+
// Nobody watches an autonomous agent in real time, so a sensitive tool call
|
|
90
|
+
// from one requires first-party trust rather than merely "not unverified".
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
@id("agent-identity.restrict-autonomous-agent-sensitive-tools")
|
|
94
|
+
@name("Block non-first-party autonomous agent tool use")
|
|
95
|
+
@description("Blocks call_tool on a sensitive tool when agent_type is autonomous unless agent_trust_level is first_party.")
|
|
96
|
+
@severity("high")
|
|
97
|
+
@tags("category:agent-identity,threat:escalation,detection:rule,surface:call-tool,scope:per-agent,owasp:asi04")
|
|
98
|
+
@reject_message("Tool execution blocked: autonomous agents must be first-party to call sensitive tools. No human is in the loop to catch a mistake.")
|
|
99
|
+
forbid (
|
|
100
|
+
principal,
|
|
101
|
+
action == Guardrails::Action::"call_tool",
|
|
102
|
+
resource
|
|
103
|
+
)
|
|
104
|
+
when {
|
|
105
|
+
context has agent_type && context.agent_type == "autonomous" &&
|
|
106
|
+
context has tool_is_sensitive && context.tool_is_sensitive == true
|
|
107
|
+
}
|
|
108
|
+
unless {
|
|
109
|
+
context has agent_trust_level && context.agent_trust_level == "first_party"
|
|
110
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Multi-Turn Trajectory Escalation
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Blocks a conversation whose TRAJECTORY is an attack, even when no single
|
|
5
|
+
// message is. A crescendo attack never sends a message alarming enough to
|
|
6
|
+
// block on its own; it walks the model there in small, plausible steps and
|
|
7
|
+
// asks for the payoff in a sentence that would pass any single-message filter.
|
|
8
|
+
//
|
|
9
|
+
// Shield scores every turn twice and exposes both scores separately:
|
|
10
|
+
// - injection_pulse_score / jailbreak_pulse_score — the single-turn
|
|
11
|
+
// classifier. Sees only the current message.
|
|
12
|
+
// - injection_deep_context_score / jailbreak_deep_context_score — the
|
|
13
|
+
// multi-turn model, which carries hidden state across turns keyed on
|
|
14
|
+
// session_id. Sees the whole conversation.
|
|
15
|
+
//
|
|
16
|
+
// Because they are separate keys, a rule can fire on the GAP between them:
|
|
17
|
+
// high trajectory score, low message score. In plain language, "the history
|
|
18
|
+
// is an attack and this message is not" — a condition a filter that only ever
|
|
19
|
+
// has one number cannot express. Section 1 is that rule. Section 2 is a safety
|
|
20
|
+
// net for a trajectory blatant enough to stand on its own.
|
|
21
|
+
//
|
|
22
|
+
// Requires a stable session_id on every request in the conversation. With no
|
|
23
|
+
// session_id there is no threaded state, multi_turn_detection is false, and
|
|
24
|
+
// none of these rules can fire.
|
|
25
|
+
//
|
|
26
|
+
// Detection layers:
|
|
27
|
+
// - injection (single-turn classifier, always available)
|
|
28
|
+
// - deepcontext (multi-turn model, always available)
|
|
29
|
+
//
|
|
30
|
+
// Context keys consumed:
|
|
31
|
+
// - multi_turn_detection: Bool — true only when threaded state was used
|
|
32
|
+
// - injection_deep_context_score, injection_pulse_score: Long (0-100)
|
|
33
|
+
// - jailbreak_deep_context_score, jailbreak_pulse_score: Long (0-100)
|
|
34
|
+
//
|
|
35
|
+
// Compliance:
|
|
36
|
+
// - OWASP LLM01, OWASP LLM02, MITRE ATLAS AML.T0051, AML.T0054
|
|
37
|
+
//
|
|
38
|
+
// Category: security
|
|
39
|
+
// Namespace: Guardrails
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Section 1: Trajectory/turn divergence
|
|
44
|
+
// The conversation scores as an attack; this message does not.
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
@id("security.block-trajectory-injection-divergence")
|
|
48
|
+
@name("Block multi-turn injection the current turn hides")
|
|
49
|
+
@description("Blocks process_prompt and process_response when threaded state is in use, injection_deep_context_score >= 60, and injection_pulse_score <= 39.")
|
|
50
|
+
@severity("critical")
|
|
51
|
+
@tags("category:security,threat:injection,detection:ml,surface:process-prompt,surface:process-response,scope:multi-turn,owasp:llm01,mitre:atlas-t0051")
|
|
52
|
+
@reject_message("Prompt blocked: this conversation is a progressive prompt-injection attempt. No single message crossed the line; the sequence did.")
|
|
53
|
+
forbid (
|
|
54
|
+
principal,
|
|
55
|
+
action in [Guardrails::Action::"process_prompt", Guardrails::Action::"process_response"],
|
|
56
|
+
resource
|
|
57
|
+
)
|
|
58
|
+
when {
|
|
59
|
+
context has multi_turn_detection && context.multi_turn_detection == true &&
|
|
60
|
+
context has injection_deep_context_score && context.injection_deep_context_score >= 60 &&
|
|
61
|
+
context has injection_pulse_score && context.injection_pulse_score <= 39
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
@id("security.block-trajectory-jailbreak-divergence")
|
|
65
|
+
@name("Block multi-turn jailbreak the current turn hides")
|
|
66
|
+
@description("Blocks process_prompt and process_response when threaded state is in use, jailbreak_deep_context_score >= 60, and jailbreak_pulse_score <= 39.")
|
|
67
|
+
@severity("critical")
|
|
68
|
+
@tags("category:security,threat:jailbreak,detection:ml,surface:process-prompt,surface:process-response,scope:multi-turn,owasp:llm02,mitre:atlas-t0054")
|
|
69
|
+
@reject_message("Prompt blocked: this conversation is a progressive jailbreak. No single message crossed the line; the sequence did.")
|
|
70
|
+
forbid (
|
|
71
|
+
principal,
|
|
72
|
+
action in [Guardrails::Action::"process_prompt", Guardrails::Action::"process_response"],
|
|
73
|
+
resource
|
|
74
|
+
)
|
|
75
|
+
when {
|
|
76
|
+
context has multi_turn_detection && context.multi_turn_detection == true &&
|
|
77
|
+
context has jailbreak_deep_context_score && context.jailbreak_deep_context_score >= 60 &&
|
|
78
|
+
context has jailbreak_pulse_score && context.jailbreak_pulse_score <= 39
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Section 2: Blatant trajectory, regardless of the message score
|
|
83
|
+
// Covers the band between Section 1 and the default single-turn thresholds.
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
@id("security.block-trajectory-injection-high")
|
|
87
|
+
@name("Block high-confidence multi-turn injection")
|
|
88
|
+
@description("Blocks process_prompt and process_response when threaded state is in use and injection_deep_context_score >= 75.")
|
|
89
|
+
@severity("high")
|
|
90
|
+
@tags("category:security,threat:injection,detection:ml,surface:process-prompt,surface:process-response,scope:multi-turn,owasp:llm01")
|
|
91
|
+
@reject_message("Prompt blocked: the multi-turn model scored this conversation as a prompt-injection attempt with high confidence.")
|
|
92
|
+
forbid (
|
|
93
|
+
principal,
|
|
94
|
+
action in [Guardrails::Action::"process_prompt", Guardrails::Action::"process_response"],
|
|
95
|
+
resource
|
|
96
|
+
)
|
|
97
|
+
when {
|
|
98
|
+
context has multi_turn_detection && context.multi_turn_detection == true &&
|
|
99
|
+
context has injection_deep_context_score && context.injection_deep_context_score >= 75
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
@id("security.block-trajectory-jailbreak-high")
|
|
103
|
+
@name("Block high-confidence multi-turn jailbreak")
|
|
104
|
+
@description("Blocks process_prompt and process_response when threaded state is in use and jailbreak_deep_context_score >= 75.")
|
|
105
|
+
@severity("high")
|
|
106
|
+
@tags("category:security,threat:jailbreak,detection:ml,surface:process-prompt,surface:process-response,scope:multi-turn,owasp:llm02")
|
|
107
|
+
@reject_message("Prompt blocked: the multi-turn model scored this conversation as a jailbreak attempt with high confidence.")
|
|
108
|
+
forbid (
|
|
109
|
+
principal,
|
|
110
|
+
action in [Guardrails::Action::"process_prompt", Guardrails::Action::"process_response"],
|
|
111
|
+
resource
|
|
112
|
+
)
|
|
113
|
+
when {
|
|
114
|
+
context has multi_turn_detection && context.multi_turn_detection == true &&
|
|
115
|
+
context has jailbreak_deep_context_score && context.jailbreak_deep_context_score >= 75
|
|
116
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Output Protection — Credentials
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Split from data_leakage.cedar: secrets are category `data-protection`, PII is
|
|
5
|
+
// `privacy`, and a template wrapper may only carry rules of its own category
|
|
6
|
+
// (HFP-LINT-TMPL-006). Relabelling one to fit the other would put a wrong
|
|
7
|
+
// category on the wire, where it drives signals[] and the severity rollup.
|
|
8
|
+
//
|
|
9
|
+
// Same scoping rule as the rest of this profile — process_response ALONE, so a
|
|
10
|
+
// tenant can police what the model says without touching what users send.
|
|
11
|
+
//
|
|
12
|
+
// Context keys consumed:
|
|
13
|
+
// - secrets_detected: Bool
|
|
14
|
+
//
|
|
15
|
+
// Compliance:
|
|
16
|
+
// - OWASP LLM02 (Sensitive Information Disclosure)
|
|
17
|
+
//
|
|
18
|
+
// Category: data-protection
|
|
19
|
+
// Namespace: Guardrails
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
@id("data-protection.output-block-secrets")
|
|
23
|
+
@name("Block secrets in model responses")
|
|
24
|
+
@description("Blocks process_response when the model's own output contains credentials, API keys or tokens — the canonical way a leaked secret reaches a caller.")
|
|
25
|
+
@severity("critical")
|
|
26
|
+
@tags("category:data-protection,threat:secrets,surface:process-response,detection:rule,owasp:llm02")
|
|
27
|
+
@reject_message("Response blocked: the model's output contained credentials.")
|
|
28
|
+
forbid (
|
|
29
|
+
principal,
|
|
30
|
+
action == Guardrails::Action::"process_response",
|
|
31
|
+
resource
|
|
32
|
+
)
|
|
33
|
+
when {
|
|
34
|
+
context has secrets_detected && context.secrets_detected == true
|
|
35
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Output Protection — Data Leakage
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Guards what the MODEL SAYS BACK, not what the user sends in.
|
|
5
|
+
//
|
|
6
|
+
// Every other guardrails template binds process_response alongside
|
|
7
|
+
// process_prompt and the tool/file actions, which is right for a threat that
|
|
8
|
+
// is the same in both directions. Data leakage is not that: the risk is
|
|
9
|
+
// specific to egress, and a tenant who wants to police only their outputs had
|
|
10
|
+
// no template to enable. That gap is why one deployed tenant hand-authored a
|
|
11
|
+
// rule named "Block PII in outputs" and bound it to process_prompt — the only
|
|
12
|
+
// action that covered responses before the direction split (ADR 0031).
|
|
13
|
+
//
|
|
14
|
+
// These rules bind process_response ALONE. That is the point: ADR 0031 created
|
|
15
|
+
// the distinct trigger precisely so a policy could be scoped to one direction
|
|
16
|
+
// from its head, and this profile is what that is for. Enabling it cannot
|
|
17
|
+
// change how prompts are treated.
|
|
18
|
+
//
|
|
19
|
+
// Context keys consumed:
|
|
20
|
+
// - pii_detected: Bool
|
|
21
|
+
// - pii_count: Long
|
|
22
|
+
//
|
|
23
|
+
// Compliance:
|
|
24
|
+
// - OWASP LLM02 (Sensitive Information Disclosure), OWASP LLM06
|
|
25
|
+
//
|
|
26
|
+
// Category: privacy
|
|
27
|
+
// Namespace: Guardrails
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
@id("privacy.output-block-pii")
|
|
31
|
+
@name("Block PII in model responses")
|
|
32
|
+
@description("Blocks process_response when the model's own output contains PII. Scoped to the response direction only — prompts are unaffected.")
|
|
33
|
+
@severity("high")
|
|
34
|
+
@tags("category:privacy,threat:data-leak,surface:process-response,detection:rule,owasp:llm02")
|
|
35
|
+
@reject_message("Response blocked: the model's output contained personal data.")
|
|
36
|
+
forbid (
|
|
37
|
+
principal,
|
|
38
|
+
action == Guardrails::Action::"process_response",
|
|
39
|
+
resource
|
|
40
|
+
)
|
|
41
|
+
when {
|
|
42
|
+
context has pii_detected && context.pii_detected == true
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Bulk disclosure is a separate finding from a single incidental match: a
|
|
46
|
+
// response carrying many distinct PII items is an exfiltration shape rather
|
|
47
|
+
// than a mention, and is worth its own severity and message even though the
|
|
48
|
+
// rule above already blocks it. Kept as a distinct rule so a tenant running
|
|
49
|
+
// the first in monitor mode can still enforce on bulk.
|
|
50
|
+
@id("privacy.output-block-bulk-pii")
|
|
51
|
+
@name("Block bulk PII disclosure in model responses")
|
|
52
|
+
@description("Blocks process_response when the output carries five or more PII items — a disclosure shape rather than an incidental mention.")
|
|
53
|
+
@severity("critical")
|
|
54
|
+
@tags("category:privacy,threat:data-leak,surface:process-response,detection:rule,owasp:llm06")
|
|
55
|
+
@reject_message("Response blocked: the model's output contained bulk personal data.")
|
|
56
|
+
forbid (
|
|
57
|
+
principal,
|
|
58
|
+
action == Guardrails::Action::"process_response",
|
|
59
|
+
resource
|
|
60
|
+
)
|
|
61
|
+
when {
|
|
62
|
+
context has pii_count && context.pii_count >= 5
|
|
63
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Output Protection — Harmful Content
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// The companion to data_leakage.cedar: what the model says back, judged for
|
|
5
|
+
// safety rather than for disclosure.
|
|
6
|
+
//
|
|
7
|
+
// Same scoping rule and same reason — process_response ALONE (ADR 0031). A
|
|
8
|
+
// tenant enabling this is saying "hold my model's answers to a standard",
|
|
9
|
+
// which is a different decision from "police what users send me", and the two
|
|
10
|
+
// should be separately enableable.
|
|
11
|
+
//
|
|
12
|
+
// Thresholds match the org-wide toxicity defaults rather than tightening them.
|
|
13
|
+
// An output profile that silently enforced stricter limits than the tenant's
|
|
14
|
+
// existing prompt rules would surprise anyone comparing the two.
|
|
15
|
+
//
|
|
16
|
+
// Context keys consumed:
|
|
17
|
+
// - violence_score: Long (0-100)
|
|
18
|
+
// - hate_speech_score: Long (0-100)
|
|
19
|
+
// - sexual_score: Long (0-100)
|
|
20
|
+
// - injection_score: Long (0-100)
|
|
21
|
+
//
|
|
22
|
+
// Compliance:
|
|
23
|
+
// - OWASP LLM05 (Improper Output Handling), OWASP LLM01
|
|
24
|
+
//
|
|
25
|
+
// Category: trust-safety
|
|
26
|
+
// Namespace: Guardrails
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
@id("trust-safety.output-block-toxic")
|
|
30
|
+
@name("Block toxic model responses")
|
|
31
|
+
@description("Blocks process_response when the model's own output scores high for violence or hate speech.")
|
|
32
|
+
@severity("high")
|
|
33
|
+
@tags("category:trust-safety,threat:harmful,surface:process-response,detection:ml,owasp:llm05")
|
|
34
|
+
@reject_message("Response blocked: the model's output was flagged as harmful.")
|
|
35
|
+
forbid (
|
|
36
|
+
principal,
|
|
37
|
+
action == Guardrails::Action::"process_response",
|
|
38
|
+
resource
|
|
39
|
+
)
|
|
40
|
+
when {
|
|
41
|
+
context has violence_score && context.violence_score >= 70 ||
|
|
42
|
+
context has hate_speech_score && context.hate_speech_score >= 70
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
@id("trust-safety.output-block-sexual")
|
|
46
|
+
@name("Block sexual content in model responses")
|
|
47
|
+
@description("Blocks process_response when the model's own output scores high for sexual content.")
|
|
48
|
+
@severity("critical")
|
|
49
|
+
@tags("category:trust-safety,threat:sexual,surface:process-response,detection:ml,owasp:llm05")
|
|
50
|
+
@reject_message("Response blocked: the model's output was flagged as explicit.")
|
|
51
|
+
forbid (
|
|
52
|
+
principal,
|
|
53
|
+
action == Guardrails::Action::"process_response",
|
|
54
|
+
resource
|
|
55
|
+
)
|
|
56
|
+
when {
|
|
57
|
+
context has sexual_score && context.sexual_score >= 91
|
|
58
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Output Protection — Injection Carried Back
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Split from harmful_content.cedar because its category is `security`, not
|
|
5
|
+
// `trust-safety`, and a template wrapper may only carry rules of its own
|
|
6
|
+
// category (HFP-LINT-TMPL-006). The split is the honest outcome: this is a
|
|
7
|
+
// different threat with a different owner, not a toxicity variant.
|
|
8
|
+
//
|
|
9
|
+
// Same scoping rule as the rest of this profile — process_response ALONE.
|
|
10
|
+
//
|
|
11
|
+
// Context keys consumed:
|
|
12
|
+
// - injection_score: Long (0-100)
|
|
13
|
+
//
|
|
14
|
+
// Compliance:
|
|
15
|
+
// - OWASP LLM05 (Improper Output Handling)
|
|
16
|
+
//
|
|
17
|
+
// Category: security
|
|
18
|
+
// Namespace: Guardrails
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
// Injection scored on an OUTPUT is a different finding from injection scored on
|
|
22
|
+
// a prompt. On the way in it is a user attacking the model; on the way out it
|
|
23
|
+
// is content the model is handing to the caller — a downstream agent, a
|
|
24
|
+
// renderer, or another tool — which is OWASP LLM05's improper output handling.
|
|
25
|
+
// Worth its own rule so the two can be tuned and reasoned about separately.
|
|
26
|
+
@id("security.output-block-injection-carried-back")
|
|
27
|
+
@name("Block injection payloads carried back in model responses")
|
|
28
|
+
@description("Blocks process_response when the output itself scores as an injection payload — content the caller may execute or forward.")
|
|
29
|
+
@severity("high")
|
|
30
|
+
@tags("category:security,threat:injection,surface:process-response,detection:ml,owasp:llm05")
|
|
31
|
+
@reject_message("Response blocked: the model's output contained an injection payload.")
|
|
32
|
+
forbid (
|
|
33
|
+
principal,
|
|
34
|
+
action == Guardrails::Action::"process_response",
|
|
35
|
+
resource
|
|
36
|
+
)
|
|
37
|
+
when {
|
|
38
|
+
context has injection_score && context.injection_score >= 80
|
|
39
|
+
};
|