@highflame/policy 2.2.42 → 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.
@@ -25,12 +25,6 @@
25
25
  "required": false,
26
26
  "description": "Principal identity class projected from the token: 'human', 'agent', 'service', or 'mcp_server'"
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)"
33
- },
34
28
  {
35
29
  "key": "request_id",
36
30
  "type": "string",
@@ -817,12 +811,6 @@
817
811
  "required": false,
818
812
  "description": "Principal identity class projected from the token: 'human', 'agent', 'service', or 'mcp_server'"
819
813
  },
820
- {
821
- "key": "principal",
822
- "type": "string",
823
- "required": false,
824
- "description": "Stable principal identifier projected from the token (e.g. a ZeroID / WIMSE URI or user id)"
825
- },
826
814
  {
827
815
  "key": "request_id",
828
816
  "type": "string",
@@ -1609,12 +1597,6 @@
1609
1597
  "required": false,
1610
1598
  "description": "Principal identity class: 'human', 'agent', 'service', or 'mcp_server'"
1611
1599
  },
1612
- {
1613
- "key": "principal",
1614
- "type": "string",
1615
- "required": false,
1616
- "description": "Stable principal identifier"
1617
- },
1618
1600
  {
1619
1601
  "key": "request_id",
1620
1602
  "type": "string",
@@ -2372,12 +2354,6 @@
2372
2354
  "required": false,
2373
2355
  "description": "Principal identity class: 'human', 'agent', 'service', or 'mcp_server'"
2374
2356
  },
2375
- {
2376
- "key": "principal",
2377
- "type": "string",
2378
- "required": false,
2379
- "description": "Stable principal identifier"
2380
- },
2381
2357
  {
2382
2358
  "key": "request_id",
2383
2359
  "type": "string",
@@ -2752,12 +2728,6 @@
2752
2728
  "required": false,
2753
2729
  "description": "Principal identity class: 'human', 'agent', 'service', or 'mcp_server'"
2754
2730
  },
2755
- {
2756
- "key": "principal",
2757
- "type": "string",
2758
- "required": false,
2759
- "description": "Stable principal identifier"
2760
- },
2761
2731
  {
2762
2732
  "key": "request_id",
2763
2733
  "type": "string",
@@ -3145,12 +3115,6 @@
3145
3115
  "required": false,
3146
3116
  "description": "Principal identity class: 'human', 'agent', 'service', or 'mcp_server'"
3147
3117
  },
3148
- {
3149
- "key": "principal",
3150
- "type": "string",
3151
- "required": false,
3152
- "description": "Stable principal identifier"
3153
- },
3154
3118
  {
3155
3119
  "key": "request_id",
3156
3120
  "type": "string",
@@ -3565,12 +3529,6 @@
3565
3529
  "required": false,
3566
3530
  "description": "Principal identity class: 'human', 'agent', 'service', or 'mcp_server'"
3567
3531
  },
3568
- {
3569
- "key": "principal",
3570
- "type": "string",
3571
- "required": false,
3572
- "description": "Stable principal identifier (ZeroID / WIMSE URI or user id)"
3573
- },
3574
3532
  {
3575
3533
  "key": "agent_trust_level",
3576
3534
  "type": "string",
@@ -122,6 +122,44 @@ namespace AgentOps {
122
122
  // Context Types (Action-Specific)
123
123
  // =========================================================================
124
124
 
125
+ /// The ZeroID NHI principal block (ADR 0009 / CAP-IDN-011).
126
+ ///
127
+ /// A record, not a string: Shield projects the whole identity block under
128
+ /// `context.principal`, so policies read members off it —
129
+ ///
130
+ /// permit ... when { context has principal &&
131
+ /// context.principal.trust_level == "first_party" };
132
+ /// forbid ... unless { context has principal &&
133
+ /// context.principal has act_sub };
134
+ ///
135
+ /// Declared once and shared by every action context so a new member cannot
136
+ /// land on one action and go missing on another.
137
+ ///
138
+ /// `act` (RFC 8693 delegation) is flattened to `act_sub` / `act_iss`
139
+ /// because Cedar records are flat key/value at each level; the full
140
+ /// delegation tree is reconstructable audit-side by joining on `mission_id`.
141
+ ///
142
+ /// Every member is optional except `delegation_depth`: the projector omits
143
+ /// a field whose value is empty, but always emits the depth because zero is
144
+ /// the meaningful base case for a direct (non-delegated) token. The record
145
+ /// itself is optional — it is absent, not empty, when the request carries no
146
+ /// NHI claims (RS256 human tokens, internal-service callers), so
147
+ /// `context has principal` is false rather than true-but-empty.
148
+ type PrincipalContext = {
149
+ "identity_type"?: String, // "human" | "agent" | "service" | "mcp_server"
150
+ "sub_type"?: String, // Identity subtype (e.g. "coding_agent")
151
+ "trust_level"?: String, // "first_party" | "verified" | "unverified"
152
+ "framework"?: String, // Agent framework (e.g. "langchain")
153
+ "publisher"?: String, // Agent publisher
154
+ "capabilities"?: Set<String>, // Capabilities granted to the identity
155
+ "scopes"?: Set<String>, // OAuth scopes carried by the token
156
+ "delegation_depth": Long, // Delegation hops; 0 for a direct token
157
+ "act_sub"?: String, // RFC 8693 actor — the accountable human
158
+ "act_iss"?: String, // Issuer of the actor claim
159
+ "mission_id"?: String, // Correlates one delegation tree
160
+ "grant_type"?: String, // How the token was obtained (e.g. "api_key")
161
+ };
162
+
125
163
  /// Context for the process_response action.
126
164
  ///
127
165
  /// An alias, not a copy: prompts and responses are inspected by the same
@@ -136,7 +174,7 @@ namespace AgentOps {
136
174
  "role"?: String,
137
175
  "privilege_scope"?: Set<String>,
138
176
  "identity_type"?: String, // Principal identity class: "human" | "agent" | "service" | "mcp_server"
139
- "principal"?: String, // Stable principal identifier (e.g. ZeroID / WIMSE URI or user id)
177
+ "principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
140
178
  // Core metadata (required)
141
179
  "request_id": String,
142
180
  "timestamp": Long,
@@ -331,7 +369,7 @@ namespace AgentOps {
331
369
  "role"?: String,
332
370
  "privilege_scope"?: Set<String>,
333
371
  "identity_type"?: String,
334
- "principal"?: String,
372
+ "principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
335
373
  // Core metadata (required)
336
374
  "request_id": String,
337
375
  "timestamp": Long,
@@ -542,7 +580,7 @@ namespace AgentOps {
542
580
  "role"?: String,
543
581
  "privilege_scope"?: Set<String>,
544
582
  "identity_type"?: String,
545
- "principal"?: String,
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,
@@ -650,7 +688,7 @@ namespace AgentOps {
650
688
  "role"?: String,
651
689
  "privilege_scope"?: Set<String>,
652
690
  "identity_type"?: String,
653
- "principal"?: String,
691
+ "principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
654
692
  // Core metadata (required)
655
693
  "request_id": String,
656
694
  "timestamp": Long,
@@ -762,7 +800,7 @@ namespace AgentOps {
762
800
  "role"?: String,
763
801
  "privilege_scope"?: Set<String>,
764
802
  "identity_type"?: String,
765
- "principal"?: String,
803
+ "principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
766
804
  // Core metadata (required)
767
805
  "request_id": String,
768
806
  "timestamp": Long,
@@ -895,7 +933,7 @@ namespace AgentOps {
895
933
  "role"?: String,
896
934
  "privilege_scope"?: Set<String>,
897
935
  "identity_type"?: String, // Principal identity class: "human" | "agent" | "service" | "mcp_server"
898
- "principal"?: String, // Stable principal identifier (ZeroID / WIMSE URI or user id)
936
+ "principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
899
937
  "agent_trust_level"?: String, // "untrusted" | "low" | "verified" | "trusted"
900
938
 
901
939
  // Core metadata (required)
@@ -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",
@@ -1742,7 +1742,6 @@
1742
1742
  "param_type_violation",
1743
1743
  "param_type_violations",
1744
1744
  "path",
1745
- "principal",
1746
1745
  "privilege_scope",
1747
1746
  "request_id",
1748
1747
  "role",
@@ -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"?: String, // Stable principal identifier (e.g. ZeroID / WIMSE URI or user id)
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"?: String, // Stable principal identifier (e.g. ZeroID / WIMSE URI or user id)
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"?: String, // Stable principal identifier (e.g. ZeroID / WIMSE URI or user id)
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"?: String, // Stable principal identifier (e.g. ZeroID / WIMSE URI or user id)
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"?: String, // Stable principal identifier (e.g. ZeroID / WIMSE URI or user id)
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
+ };