@highflame/policy 2.2.45 → 2.4.0
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/schema.cedarschema +22 -0
- package/_schemas/ai_gateway/schema.cedarschema +49 -0
- package/_schemas/ai_gateway/templates/single_use_sensitive_tools.cedar +54 -0
- package/_schemas/ai_gateway/templates/templates.json +37 -0
- package/_schemas/ai_gateway/templates/unadopted_agents.cedar +64 -0
- package/_schemas/guardrails/schema.cedarschema +21 -0
- package/_schemas/guardrails/templates/dual_attribution.cedar +143 -48
- package/_schemas/guardrails/templates/single_use_sensitive_tools.cedar +54 -0
- package/_schemas/guardrails/templates/templates.json +34 -2
- package/_schemas/guardrails/templates/unadopted_agents.cedar +64 -0
- package/dist/aarm-annotation.d.ts +58 -4
- package/dist/aarm-annotation.js +177 -4
- package/dist/aarm-annotations.gen.js +85 -1
- package/dist/ai_gateway-defaults.gen.d.ts +1 -1
- package/dist/ai_gateway-defaults.gen.js +176 -0
- package/dist/annotations.d.ts +3 -2
- package/dist/annotations.js +6 -2
- package/dist/guardrails-defaults.gen.js +309 -44
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/service-schemas.gen.d.ts +3 -3
- package/dist/service-schemas.gen.js +92 -0
- package/dist/step-up-role-conflict.d.ts +49 -0
- package/dist/step-up-role-conflict.js +117 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +3 -0
- package/package.json +1 -1
|
@@ -136,6 +136,18 @@ namespace AgentOps {
|
|
|
136
136
|
context: ProvisionSandboxContext
|
|
137
137
|
};
|
|
138
138
|
|
|
139
|
+
/// The acting agent's lifecycle and IdP provenance, projected as
|
|
140
|
+
/// \`context.actor\`. Same four attributes, names and types as the legacy
|
|
141
|
+
/// members on PrincipalContext (which keeps them). Every member is optional
|
|
142
|
+
/// and the record is absent when none is supplied; absence means unknown,
|
|
143
|
+
/// not "active".
|
|
144
|
+
type ActorContext = {
|
|
145
|
+
"status"?: String, // Agent lifecycle: "discovered" | "pending" | "active"
|
|
146
|
+
"origin"?: String, // "native" | "entra" | "okta" | ...
|
|
147
|
+
"idp_iss"?: String, // Upstream IdP issuer for IdP-registered agents
|
|
148
|
+
"idp_parent"?: String, // Upstream parent object (e.g. Entra blueprint app id)
|
|
149
|
+
};
|
|
150
|
+
|
|
139
151
|
// =========================================================================
|
|
140
152
|
// Context Types (Action-Specific)
|
|
141
153
|
// =========================================================================
|
|
@@ -176,6 +188,10 @@ namespace AgentOps {
|
|
|
176
188
|
"act_iss"?: String, // Issuer of the actor claim
|
|
177
189
|
"mission_id"?: String, // Correlates one delegation tree
|
|
178
190
|
"grant_type"?: String, // How the token was obtained (e.g. "api_key")
|
|
191
|
+
"status"?: String, // Agent lifecycle: "discovered" | "pending" | "active"; absent = unknown, not active
|
|
192
|
+
"origin"?: String, // Where the identity came from: "native" | "entra" | "okta" | ...
|
|
193
|
+
"idp_iss"?: String, // Upstream IdP issuer for IdP-registered agents
|
|
194
|
+
"idp_parent"?: String, // Upstream parent object (e.g. Entra blueprint app id)
|
|
179
195
|
};
|
|
180
196
|
|
|
181
197
|
/// Context for the process_response action.
|
|
@@ -193,6 +209,7 @@ namespace AgentOps {
|
|
|
193
209
|
"privilege_scope"?: Set<String>,
|
|
194
210
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service" | "mcp_server"
|
|
195
211
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
212
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
196
213
|
// Core metadata (required)
|
|
197
214
|
"request_id": String,
|
|
198
215
|
"timestamp": Long,
|
|
@@ -388,6 +405,7 @@ namespace AgentOps {
|
|
|
388
405
|
"privilege_scope"?: Set<String>,
|
|
389
406
|
"identity_type"?: String,
|
|
390
407
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
408
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
391
409
|
// Core metadata (required)
|
|
392
410
|
"request_id": String,
|
|
393
411
|
"timestamp": Long,
|
|
@@ -599,6 +617,7 @@ namespace AgentOps {
|
|
|
599
617
|
"privilege_scope"?: Set<String>,
|
|
600
618
|
"identity_type"?: String,
|
|
601
619
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
620
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
602
621
|
// Core metadata (required)
|
|
603
622
|
"request_id": String,
|
|
604
623
|
"timestamp": Long,
|
|
@@ -707,6 +726,7 @@ namespace AgentOps {
|
|
|
707
726
|
"privilege_scope"?: Set<String>,
|
|
708
727
|
"identity_type"?: String,
|
|
709
728
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
729
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
710
730
|
// Core metadata (required)
|
|
711
731
|
"request_id": String,
|
|
712
732
|
"timestamp": Long,
|
|
@@ -819,6 +839,7 @@ namespace AgentOps {
|
|
|
819
839
|
"privilege_scope"?: Set<String>,
|
|
820
840
|
"identity_type"?: String,
|
|
821
841
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
842
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
822
843
|
// Core metadata (required)
|
|
823
844
|
"request_id": String,
|
|
824
845
|
"timestamp": Long,
|
|
@@ -952,6 +973,7 @@ namespace AgentOps {
|
|
|
952
973
|
"privilege_scope"?: Set<String>,
|
|
953
974
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service" | "mcp_server"
|
|
954
975
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
976
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
955
977
|
"agent_trust_level"?: String, // "untrusted" | "low" | "verified" | "trusted"
|
|
956
978
|
|
|
957
979
|
// Core metadata (required)
|
|
@@ -1072,6 +1094,8 @@ action call_tool appliesTo {
|
|
|
1072
1094
|
// Identity (AARM R6 / CAP-IDN-011) — projected from the principal's token; optional.
|
|
1073
1095
|
role?: String,
|
|
1074
1096
|
privilege_scope?: Set<String>,
|
|
1097
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
1098
|
+
"actor"?: ActorContext, // Acting agent status/origin/IdP provenance; absent when none supplied
|
|
1075
1099
|
// --- Content ---
|
|
1076
1100
|
content: String, // Raw content being scanned
|
|
1077
1101
|
|
|
@@ -1194,6 +1218,8 @@ action connect_server appliesTo {
|
|
|
1194
1218
|
// Identity (AARM R6 / CAP-IDN-011) — projected from the principal's token; optional.
|
|
1195
1219
|
role?: String,
|
|
1196
1220
|
privilege_scope?: Set<String>,
|
|
1221
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
1222
|
+
"actor"?: ActorContext, // Acting agent status/origin/IdP provenance; absent when none supplied
|
|
1197
1223
|
content?: String, // Server config content (if available)
|
|
1198
1224
|
mcp_server?: String,
|
|
1199
1225
|
|
|
@@ -1291,6 +1317,8 @@ action list appliesTo {
|
|
|
1291
1317
|
// Identity (AARM R6 / CAP-IDN-011) — projected from the principal's token; optional.
|
|
1292
1318
|
role?: String,
|
|
1293
1319
|
privilege_scope?: Set<String>,
|
|
1320
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
1321
|
+
"actor"?: ActorContext, // Acting agent status/origin/IdP provenance; absent when none supplied
|
|
1294
1322
|
|
|
1295
1323
|
// --- Content ---
|
|
1296
1324
|
// The enumerated item's description. Optional: a tool may declare none,
|
|
@@ -1365,6 +1393,43 @@ action list appliesTo {
|
|
|
1365
1393
|
},
|
|
1366
1394
|
};
|
|
1367
1395
|
|
|
1396
|
+
// ZeroID NHI principal block, projected by Shield as \`context.principal\` when
|
|
1397
|
+
// the caller's token carries NHI claims. Same shape as the Guardrails and
|
|
1398
|
+
// AgentOps PrincipalContext, so one policy text reads the same keys on every
|
|
1399
|
+
// product. Every member is optional except \`delegation_depth\` (always emitted;
|
|
1400
|
+
// 0 for a direct token). The record is absent, not empty, when the request
|
|
1401
|
+
// carries no NHI claims, so \`context has principal\` is false.
|
|
1402
|
+
type PrincipalContext = {
|
|
1403
|
+
"identity_type"?: String, // "human" | "agent" | "service" | "mcp_server"
|
|
1404
|
+
"sub_type"?: String, // Identity subtype (e.g. "coding_agent")
|
|
1405
|
+
"trust_level"?: String, // "first_party" | "verified" | "unverified"
|
|
1406
|
+
"framework"?: String, // Agent framework (e.g. "langchain")
|
|
1407
|
+
"publisher"?: String, // Agent publisher
|
|
1408
|
+
"capabilities"?: Set<String>, // Capabilities granted to the identity
|
|
1409
|
+
"scopes"?: Set<String>, // OAuth scopes carried by the token
|
|
1410
|
+
"delegation_depth": Long, // Delegation hops; 0 for a direct token
|
|
1411
|
+
"act_sub"?: String, // RFC 8693 actor: the accountable human
|
|
1412
|
+
"act_iss"?: String, // Issuer of the actor claim
|
|
1413
|
+
"mission_id"?: String, // Correlates one delegation tree
|
|
1414
|
+
"grant_type"?: String, // How the token was obtained (e.g. "api_key")
|
|
1415
|
+
"status"?: String, // Agent lifecycle: "discovered" | "pending" | "active"; absent = unknown, not active
|
|
1416
|
+
"origin"?: String, // Where the identity came from: "native" | "entra" | "okta" | ...
|
|
1417
|
+
"idp_iss"?: String, // Upstream IdP issuer for IdP-registered agents
|
|
1418
|
+
"idp_parent"?: String, // Upstream parent object (e.g. Entra blueprint app id)
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1421
|
+
// The acting agent's lifecycle and IdP provenance, projected as
|
|
1422
|
+
// \`context.actor\`. Firehog traffic is evaluated in this namespace, which is
|
|
1423
|
+
// where agents registered in Okta or Entra reach Shield (ADR 0041 D5,
|
|
1424
|
+
// ADR 0042). Every member is optional and the record is absent when none is
|
|
1425
|
+
// supplied; absence means unknown, not "active".
|
|
1426
|
+
type ActorContext = {
|
|
1427
|
+
"status"?: String, // Agent lifecycle: "discovered" | "pending" | "active"
|
|
1428
|
+
"origin"?: String, // "native" | "entra" | "okta" | ...
|
|
1429
|
+
"idp_iss"?: String, // Upstream IdP issuer for IdP-registered agents
|
|
1430
|
+
"idp_parent"?: String, // Upstream parent object (e.g. Entra blueprint app id)
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1368
1433
|
// Shared content-inspection context.
|
|
1369
1434
|
//
|
|
1370
1435
|
// Prompts and responses are inspected by the same detector pipeline and so
|
|
@@ -1375,6 +1440,8 @@ type ContentInspectionContext = {
|
|
|
1375
1440
|
// Identity (AARM R6 / CAP-IDN-011) — projected from the principal's token; optional.
|
|
1376
1441
|
role?: String,
|
|
1377
1442
|
privilege_scope?: Set<String>,
|
|
1443
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
1444
|
+
"actor"?: ActorContext, // Acting agent status/origin/IdP provenance; absent when none supplied
|
|
1378
1445
|
content: String,
|
|
1379
1446
|
mcp_server?: String,
|
|
1380
1447
|
|
|
@@ -1486,6 +1553,8 @@ action read_file appliesTo {
|
|
|
1486
1553
|
// Identity (AARM R6 / CAP-IDN-011) — projected from the principal's token; optional.
|
|
1487
1554
|
role?: String,
|
|
1488
1555
|
privilege_scope?: Set<String>,
|
|
1556
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
1557
|
+
"actor"?: ActorContext, // Acting agent status/origin/IdP provenance; absent when none supplied
|
|
1489
1558
|
content: String,
|
|
1490
1559
|
mcp_server?: String,
|
|
1491
1560
|
|
|
@@ -1553,6 +1622,8 @@ action write_file appliesTo {
|
|
|
1553
1622
|
// Identity (AARM R6 / CAP-IDN-011) — projected from the principal's token; optional.
|
|
1554
1623
|
role?: String,
|
|
1555
1624
|
privilege_scope?: Set<String>,
|
|
1625
|
+
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
1626
|
+
"actor"?: ActorContext, // Acting agent status/origin/IdP provenance; absent when none supplied
|
|
1556
1627
|
content: String,
|
|
1557
1628
|
mcp_server?: String,
|
|
1558
1629
|
|
|
@@ -1761,6 +1832,22 @@ namespace Guardrails {
|
|
|
1761
1832
|
"act_iss"?: String, // Issuer of the actor claim
|
|
1762
1833
|
"mission_id"?: String, // Correlates one delegation tree
|
|
1763
1834
|
"grant_type"?: String, // How the token was obtained (e.g. "api_key")
|
|
1835
|
+
"status"?: String, // Agent lifecycle: "discovered" | "pending" | "active"; absent = unknown, not active
|
|
1836
|
+
"origin"?: String, // Where the identity came from: "native" | "entra" | "okta" | ...
|
|
1837
|
+
"idp_iss"?: String, // Upstream IdP issuer for IdP-registered agents
|
|
1838
|
+
"idp_parent"?: String, // Upstream parent object (e.g. Entra blueprint app id)
|
|
1839
|
+
};
|
|
1840
|
+
|
|
1841
|
+
/// The acting agent's lifecycle and IdP provenance, projected as
|
|
1842
|
+
/// \`context.actor\`. Same four attributes, names and types as the legacy
|
|
1843
|
+
/// members on PrincipalContext (which keeps them). Every member is optional
|
|
1844
|
+
/// and the record is absent when none is supplied; absence means unknown,
|
|
1845
|
+
/// not "active".
|
|
1846
|
+
type ActorContext = {
|
|
1847
|
+
"status"?: String, // Agent lifecycle: "discovered" | "pending" | "active"
|
|
1848
|
+
"origin"?: String, // "native" | "entra" | "okta" | ...
|
|
1849
|
+
"idp_iss"?: String, // Upstream IdP issuer for IdP-registered agents
|
|
1850
|
+
"idp_parent"?: String, // Upstream parent object (e.g. Entra blueprint app id)
|
|
1764
1851
|
};
|
|
1765
1852
|
|
|
1766
1853
|
/// Context for the process_response action.
|
|
@@ -1780,6 +1867,7 @@ namespace Guardrails {
|
|
|
1780
1867
|
"privilege_scope"?: Set<String>,
|
|
1781
1868
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
1782
1869
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
1870
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
1783
1871
|
// Core metadata (required)
|
|
1784
1872
|
"request_id": String,
|
|
1785
1873
|
"timestamp": Long,
|
|
@@ -1932,6 +2020,7 @@ namespace Guardrails {
|
|
|
1932
2020
|
"privilege_scope"?: Set<String>,
|
|
1933
2021
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
1934
2022
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
2023
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
1935
2024
|
// Core metadata (required)
|
|
1936
2025
|
"request_id": String,
|
|
1937
2026
|
"timestamp": Long,
|
|
@@ -2128,6 +2217,7 @@ namespace Guardrails {
|
|
|
2128
2217
|
"privilege_scope"?: Set<String>,
|
|
2129
2218
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
2130
2219
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
2220
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
2131
2221
|
// Core metadata (required)
|
|
2132
2222
|
"request_id": String,
|
|
2133
2223
|
"timestamp": Long,
|
|
@@ -2201,6 +2291,7 @@ namespace Guardrails {
|
|
|
2201
2291
|
"privilege_scope"?: Set<String>,
|
|
2202
2292
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
2203
2293
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
2294
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
2204
2295
|
// Core metadata (required)
|
|
2205
2296
|
"request_id": String,
|
|
2206
2297
|
"timestamp": Long,
|
|
@@ -2278,6 +2369,7 @@ namespace Guardrails {
|
|
|
2278
2369
|
"privilege_scope"?: Set<String>,
|
|
2279
2370
|
"identity_type"?: String, // Principal identity class: "human" | "agent" | "service"
|
|
2280
2371
|
"principal"?: PrincipalContext, // ZeroID NHI identity block; absent when the token carries no NHI claims
|
|
2372
|
+
"actor"?: ActorContext, // Acting agent attributes (status/origin/IdP provenance); absent when none supplied
|
|
2281
2373
|
// Core metadata (required)
|
|
2282
2374
|
"request_id": String,
|
|
2283
2375
|
"timestamp": Long,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authoring-time lint for @step_up_required role conflicts across a policy set.
|
|
3
|
+
*
|
|
4
|
+
* Shield picks one approver role per call. When two enforce-mode step-up
|
|
5
|
+
* policies of the same effect match one call and name different roles, no
|
|
6
|
+
* single approval satisfies both, so Shield denies the call with
|
|
7
|
+
* step_up_internal_error (highflame-shield internal/cedar/annotations.go,
|
|
8
|
+
* resolveStepUpRole). This lint warns about such pairs before they ship.
|
|
9
|
+
*
|
|
10
|
+
* It is a WARNING, not an error: the policy set is valid Cedar and the
|
|
11
|
+
* conflict only bites on calls both policies match.
|
|
12
|
+
*
|
|
13
|
+
* Parity: packages/go/step_up_role_conflict.go.
|
|
14
|
+
*/
|
|
15
|
+
import type { PolicyRule } from './builder.js';
|
|
16
|
+
/** Stable code carried by every StepUpRoleConflictWarning. */
|
|
17
|
+
export declare const STEP_UP_ROLE_CONFLICT_CODE = "step_up_role_conflict";
|
|
18
|
+
/** Owner-approval role. Any named role outranks it, so it never conflicts. */
|
|
19
|
+
export declare const STEP_UP_ROLE_SELF = "self";
|
|
20
|
+
/** Two step-up policies whose roles differ and whose scopes could match the same call. */
|
|
21
|
+
export interface StepUpRoleConflictWarning {
|
|
22
|
+
code: typeof STEP_UP_ROLE_CONFLICT_CODE;
|
|
23
|
+
message: string;
|
|
24
|
+
ruleIdA: string;
|
|
25
|
+
roleA: string;
|
|
26
|
+
ruleIdB: string;
|
|
27
|
+
roleB: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Returns one warning per pair of enabled rules that Shield would treat as a
|
|
31
|
+
* role conflict if both matched one call.
|
|
32
|
+
*
|
|
33
|
+
* The pair rule mirrors Shield's resolveStepUpRole:
|
|
34
|
+
* - both rules carry a valid @step_up_required;
|
|
35
|
+
* - both have the same effect (a forbid's step-up outranks a permit's, so a
|
|
36
|
+
* forbid/permit pair never conflicts);
|
|
37
|
+
* - neither role is "self" (a named role outranks owner approval);
|
|
38
|
+
* - the roles differ (exact, case-sensitive comparison).
|
|
39
|
+
*
|
|
40
|
+
* Overlap heuristic (conservative; exact Cedar satisfiability is out of
|
|
41
|
+
* scope): the pair warns unless the scope heads clearly cannot match the same
|
|
42
|
+
* request. Heads are disjoint when the literal action sets do not intersect
|
|
43
|
+
* (no "*"), or when principal or resource are both exact/`is` constraints on
|
|
44
|
+
* different entity types, or on the same type with different entity IDs.
|
|
45
|
+
* `in` (hierarchy) constraints and when/unless conditions are assumed to
|
|
46
|
+
* overlap. Per-policy mode is not visible in Cedar text, so observe-only
|
|
47
|
+
* (monitor/alert) policies, which Shield excludes, can still warn here.
|
|
48
|
+
*/
|
|
49
|
+
export declare function lintStepUpRoleConflicts(rules: readonly PolicyRule[]): StepUpRoleConflictWarning[];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authoring-time lint for @step_up_required role conflicts across a policy set.
|
|
3
|
+
*
|
|
4
|
+
* Shield picks one approver role per call. When two enforce-mode step-up
|
|
5
|
+
* policies of the same effect match one call and name different roles, no
|
|
6
|
+
* single approval satisfies both, so Shield denies the call with
|
|
7
|
+
* step_up_internal_error (highflame-shield internal/cedar/annotations.go,
|
|
8
|
+
* resolveStepUpRole). This lint warns about such pairs before they ship.
|
|
9
|
+
*
|
|
10
|
+
* It is a WARNING, not an error: the policy set is valid Cedar and the
|
|
11
|
+
* conflict only bites on calls both policies match.
|
|
12
|
+
*
|
|
13
|
+
* Parity: packages/go/step_up_role_conflict.go.
|
|
14
|
+
*/
|
|
15
|
+
import { parseAARMAnnotations } from './aarm-annotation.js';
|
|
16
|
+
/** Stable code carried by every StepUpRoleConflictWarning. */
|
|
17
|
+
export const STEP_UP_ROLE_CONFLICT_CODE = 'step_up_role_conflict';
|
|
18
|
+
/** Owner-approval role. Any named role outranks it, so it never conflicts. */
|
|
19
|
+
export const STEP_UP_ROLE_SELF = 'self';
|
|
20
|
+
/**
|
|
21
|
+
* Returns one warning per pair of enabled rules that Shield would treat as a
|
|
22
|
+
* role conflict if both matched one call.
|
|
23
|
+
*
|
|
24
|
+
* The pair rule mirrors Shield's resolveStepUpRole:
|
|
25
|
+
* - both rules carry a valid @step_up_required;
|
|
26
|
+
* - both have the same effect (a forbid's step-up outranks a permit's, so a
|
|
27
|
+
* forbid/permit pair never conflicts);
|
|
28
|
+
* - neither role is "self" (a named role outranks owner approval);
|
|
29
|
+
* - the roles differ (exact, case-sensitive comparison).
|
|
30
|
+
*
|
|
31
|
+
* Overlap heuristic (conservative; exact Cedar satisfiability is out of
|
|
32
|
+
* scope): the pair warns unless the scope heads clearly cannot match the same
|
|
33
|
+
* request. Heads are disjoint when the literal action sets do not intersect
|
|
34
|
+
* (no "*"), or when principal or resource are both exact/`is` constraints on
|
|
35
|
+
* different entity types, or on the same type with different entity IDs.
|
|
36
|
+
* `in` (hierarchy) constraints and when/unless conditions are assumed to
|
|
37
|
+
* overlap. Per-policy mode is not visible in Cedar text, so observe-only
|
|
38
|
+
* (monitor/alert) policies, which Shield excludes, can still warn here.
|
|
39
|
+
*/
|
|
40
|
+
export function lintStepUpRoleConflicts(rules) {
|
|
41
|
+
const candidates = [];
|
|
42
|
+
for (const rule of rules) {
|
|
43
|
+
if (!rule.enabled || !rule.customAnnotations)
|
|
44
|
+
continue;
|
|
45
|
+
const role = parseAARMAnnotations(rule.customAnnotations).directives.stepUpRequired?.role;
|
|
46
|
+
if (!role || role === STEP_UP_ROLE_SELF)
|
|
47
|
+
continue;
|
|
48
|
+
candidates.push({ rule, role });
|
|
49
|
+
}
|
|
50
|
+
const warnings = [];
|
|
51
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
52
|
+
for (let j = i + 1; j < candidates.length; j++) {
|
|
53
|
+
const a = candidates[i];
|
|
54
|
+
const b = candidates[j];
|
|
55
|
+
if (a.role === b.role || a.rule.effect !== b.rule.effect)
|
|
56
|
+
continue;
|
|
57
|
+
if (!actionsCanOverlap(a.rule.action, b.rule.action) ||
|
|
58
|
+
!entitiesCanOverlap(a.rule.principal, b.rule.principal) ||
|
|
59
|
+
!entitiesCanOverlap(a.rule.resource, b.rule.resource)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const idA = ruleLabel(a.rule);
|
|
63
|
+
const idB = ruleLabel(b.rule);
|
|
64
|
+
warnings.push({
|
|
65
|
+
code: STEP_UP_ROLE_CONFLICT_CODE,
|
|
66
|
+
message: `Rules "${idA}" (@step_up_required("${a.role}")) and "${idB}" (@step_up_required("${b.role}")) ` +
|
|
67
|
+
'can match the same call but require different approver roles; Shield allows one role per call ' +
|
|
68
|
+
'and denies such calls with step_up_internal_error',
|
|
69
|
+
ruleIdA: idA,
|
|
70
|
+
roleA: a.role,
|
|
71
|
+
ruleIdB: idB,
|
|
72
|
+
roleB: b.role,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return warnings;
|
|
77
|
+
}
|
|
78
|
+
function ruleLabel(rule) {
|
|
79
|
+
return rule.annotations?.id || rule.annotations?.name || '';
|
|
80
|
+
}
|
|
81
|
+
/** Literal action set, or null for a wildcard (or unrecognized shape). */
|
|
82
|
+
function actionSet(action) {
|
|
83
|
+
const list = typeof action === 'string' ? [action] : Array.isArray(action) ? action : null;
|
|
84
|
+
if (!list || list.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
const set = new Set();
|
|
87
|
+
for (const a of list) {
|
|
88
|
+
if (typeof a !== 'string' || a === '*' || a === '')
|
|
89
|
+
return null;
|
|
90
|
+
set.add(a);
|
|
91
|
+
}
|
|
92
|
+
return set;
|
|
93
|
+
}
|
|
94
|
+
function actionsCanOverlap(a, b) {
|
|
95
|
+
const sa = actionSet(a);
|
|
96
|
+
const sb = actionSet(b);
|
|
97
|
+
if (!sa || !sb)
|
|
98
|
+
return true;
|
|
99
|
+
for (const x of sa)
|
|
100
|
+
if (sb.has(x))
|
|
101
|
+
return true;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Whether two principal/resource constraints could both match one entity.
|
|
106
|
+
* null means any entity. An `in` constraint names an ancestor, whose type says
|
|
107
|
+
* nothing about the matched entity's type, so it is assumed to overlap.
|
|
108
|
+
*/
|
|
109
|
+
function entitiesCanOverlap(a, b) {
|
|
110
|
+
if (!a || !b || a.operator === 'in' || b.operator === 'in')
|
|
111
|
+
return true;
|
|
112
|
+
if (a.type !== b.type)
|
|
113
|
+
return false;
|
|
114
|
+
if (a.id && b.id && a.id !== b.id)
|
|
115
|
+
return false;
|
|
116
|
+
return true;
|
|
117
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './category-layers.gen.js';
|
|
|
6
6
|
export * from './decision-effects.gen.js';
|
|
7
7
|
export * from './aarm-annotations.gen.js';
|
|
8
8
|
export * from './aarm-annotation.js';
|
|
9
|
+
export * from './step-up-role-conflict.js';
|
|
9
10
|
export * from './privilege-catalog.gen.js';
|
|
10
11
|
export * from './builder.js';
|
|
11
12
|
export * from './errors.js';
|
package/dist/types.js
CHANGED
|
@@ -19,6 +19,9 @@ export * from './aarm-annotations.gen.js';
|
|
|
19
19
|
// AARM annotation parser/validator (browser-safe — typed parse + fail-closed
|
|
20
20
|
// validation; Studio lints with the exact rules Shield runs at sync time).
|
|
21
21
|
export * from './aarm-annotation.js';
|
|
22
|
+
// Policy-set lint: warns when step-up policies naming different approver
|
|
23
|
+
// roles can match the same call (Shield denies with step_up_internal_error).
|
|
24
|
+
export * from './step-up-role-conflict.js';
|
|
22
25
|
// Role -> capability privilege catalog (browser-safe — Studio surfaces
|
|
23
26
|
// the default scope per role; checked by role-conditioned Cedar policies).
|
|
24
27
|
export * from './privilege-catalog.gen.js';
|