@highflame/policy 2.1.2 → 2.1.4

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.
Files changed (32) hide show
  1. package/_schemas/guardrails/context.json +37 -73
  2. package/_schemas/overwatch/context.json +211 -1
  3. package/_schemas/palisade/context.json +1 -1
  4. package/_schemas/sentry/context.json +1165 -0
  5. package/_schemas/sentry/schema.cedarschema +388 -0
  6. package/_schemas/sentry/templates/defaults/baseline.cedar +24 -0
  7. package/_schemas/sentry/templates/defaults/content_safety.cedar +232 -0
  8. package/_schemas/sentry/templates/defaults/file_safety.cedar +174 -0
  9. package/_schemas/sentry/templates/defaults/organization.cedar +207 -0
  10. package/_schemas/sentry/templates/defaults/pii.cedar +229 -0
  11. package/_schemas/sentry/templates/defaults/semantic.cedar +167 -0
  12. package/_schemas/sentry/templates/templates.json +93 -0
  13. package/dist/builder.d.ts +32 -0
  14. package/dist/builder.js +6 -6
  15. package/dist/condition-groups.d.ts +69 -0
  16. package/dist/condition-groups.js +305 -0
  17. package/dist/index.d.ts +6 -1
  18. package/dist/index.js +6 -1
  19. package/dist/overwatch-context.gen.d.ts +7 -0
  20. package/dist/overwatch-context.gen.js +7 -0
  21. package/dist/overwatch-defaults.gen.js +358 -370
  22. package/dist/sentry-context.gen.d.ts +76 -0
  23. package/dist/sentry-context.gen.js +77 -0
  24. package/dist/sentry-defaults.gen.d.ts +61 -0
  25. package/dist/sentry-defaults.gen.js +1235 -0
  26. package/dist/sentry-entities.gen.d.ts +11 -0
  27. package/dist/sentry-entities.gen.js +33 -0
  28. package/dist/service-schemas.gen.d.ts +10 -0
  29. package/dist/service-schemas.gen.js +659 -6
  30. package/dist/types.d.ts +6 -1
  31. package/dist/types.js +6 -1
  32. package/package.json +1 -1
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Condition Groups — flat UI-friendly representation of ConditionExpression trees.
3
+ *
4
+ * Provides bidirectional conversion between recursive ConditionExpression ASTs
5
+ * and flat ConditionGroup arrays suitable for visual condition builder UIs.
6
+ *
7
+ * Also provides:
8
+ * - expressionToCedar(): render any AST node to valid Cedar condition text
9
+ * - extractContextFields(): collect all context field names from an AST
10
+ */
11
+ import { conditionToCedar, sanitizeIdentifier, isValidRawCondition, } from './builder.js';
12
+ /** Sentinel field name used for raw (unparseable) conditions. */
13
+ export const RAW_CONDITION_FIELD = '__raw';
14
+ // ---------------------------------------------------------------------------
15
+ // ID generation
16
+ // ---------------------------------------------------------------------------
17
+ let _groupCounter = 0;
18
+ /** Generate a unique group ID. Uses a simple counter for deterministic output. */
19
+ function nextGroupId() {
20
+ return `group-${++_groupCounter}`;
21
+ }
22
+ /** Reset the group counter (for testing). */
23
+ export function resetGroupCounter() {
24
+ _groupCounter = 0;
25
+ }
26
+ // ---------------------------------------------------------------------------
27
+ // expressionToGroups
28
+ // ---------------------------------------------------------------------------
29
+ /**
30
+ * Convert a ConditionExpression AST into a flat array of ConditionGroups.
31
+ *
32
+ * The top-level AND is split into separate groups. Each OR subtree becomes
33
+ * a single group with `logic: 'or'`. NOT wrappers set `negated: true`.
34
+ * Raw nodes produce a sentinel condition with `field: "__raw"`.
35
+ */
36
+ export function expressionToGroups(expr) {
37
+ // Top-level AND → split children into groups
38
+ if (expr.kind === 'and') {
39
+ const groups = [];
40
+ for (const child of expr.children) {
41
+ groups.push(...nodeToGroups(child));
42
+ }
43
+ return groups;
44
+ }
45
+ // Anything else → delegate
46
+ return nodeToGroups(expr);
47
+ }
48
+ /**
49
+ * Convert a single AST node (not a top-level AND) into one or more groups.
50
+ */
51
+ function nodeToGroups(expr) {
52
+ switch (expr.kind) {
53
+ case 'and': {
54
+ // Nested AND within a top-level AND child → single AND group
55
+ const conditions = collectLeafConditions(expr.children);
56
+ return [{ id: nextGroupId(), logic: 'and', conditions, negated: false }];
57
+ }
58
+ case 'or': {
59
+ const conditions = collectLeafConditions(expr.children);
60
+ return [{ id: nextGroupId(), logic: 'or', conditions, negated: false }];
61
+ }
62
+ case 'not': {
63
+ return notToGroups(expr.child);
64
+ }
65
+ case 'raw': {
66
+ return [{
67
+ id: nextGroupId(),
68
+ logic: 'and',
69
+ conditions: [{ field: RAW_CONDITION_FIELD, operator: 'eq', value: expr.text }],
70
+ negated: false,
71
+ }];
72
+ }
73
+ default: {
74
+ // Leaf node → single AND group with one condition
75
+ const cond = leafToCondition(expr);
76
+ return [{ id: nextGroupId(), logic: 'and', conditions: [cond], negated: false }];
77
+ }
78
+ }
79
+ }
80
+ /** Convert a NOT node's inner expression into negated group(s). */
81
+ function notToGroups(inner) {
82
+ switch (inner.kind) {
83
+ case 'or': {
84
+ const conditions = collectLeafConditions(inner.children);
85
+ return [{ id: nextGroupId(), logic: 'or', conditions, negated: true }];
86
+ }
87
+ case 'and': {
88
+ const conditions = collectLeafConditions(inner.children);
89
+ return [{ id: nextGroupId(), logic: 'and', conditions, negated: true }];
90
+ }
91
+ case 'not': {
92
+ // Double negation: NOT(NOT(x)) → just x
93
+ return nodeToGroups(inner.child);
94
+ }
95
+ case 'raw': {
96
+ return [{
97
+ id: nextGroupId(),
98
+ logic: 'and',
99
+ conditions: [{ field: RAW_CONDITION_FIELD, operator: 'eq', value: inner.text }],
100
+ negated: true,
101
+ }];
102
+ }
103
+ default: {
104
+ // NOT wrapping a leaf
105
+ const cond = leafToCondition(inner);
106
+ return [{ id: nextGroupId(), logic: 'and', conditions: [cond], negated: true }];
107
+ }
108
+ }
109
+ }
110
+ /** Collect leaf conditions from an array of children. Non-leaf children become raw fallbacks. */
111
+ function collectLeafConditions(children) {
112
+ return children.map(child => {
113
+ if (isLeaf(child)) {
114
+ return leafToCondition(child);
115
+ }
116
+ // Non-leaf in OR/AND children → raw fallback with Cedar text
117
+ return { field: RAW_CONDITION_FIELD, operator: 'eq', value: expressionToCedar(child) };
118
+ });
119
+ }
120
+ /** Check if an expression is a leaf (comparison, contains, like, has). */
121
+ function isLeaf(expr) {
122
+ return expr.kind === 'comparison' || expr.kind === 'contains'
123
+ || expr.kind === 'like' || expr.kind === 'has';
124
+ }
125
+ /** Convert a leaf expression to a PolicyCondition. */
126
+ function leafToCondition(expr) {
127
+ switch (expr.kind) {
128
+ case 'comparison':
129
+ return { field: expr.field, operator: expr.operator, value: expr.value };
130
+ case 'contains':
131
+ return { field: expr.field, operator: 'contains', value: expr.value };
132
+ case 'like':
133
+ return { field: expr.field, operator: 'like', value: expr.pattern };
134
+ case 'has':
135
+ return { field: expr.field, operator: 'eq', value: true };
136
+ default:
137
+ // Fallback — shouldn't reach here for true leaves
138
+ return { field: RAW_CONDITION_FIELD, operator: 'eq', value: expressionToCedar(expr) };
139
+ }
140
+ }
141
+ // ---------------------------------------------------------------------------
142
+ // groupsToExpression
143
+ // ---------------------------------------------------------------------------
144
+ /**
145
+ * Convert a flat array of ConditionGroups back into a ConditionExpression AST.
146
+ *
147
+ * Each group becomes an AND/OR node (or single leaf if only one condition).
148
+ * If `negated`, the group is wrapped in NOT. Multiple groups are combined
149
+ * with a top-level AND.
150
+ */
151
+ export function groupsToExpression(groups) {
152
+ if (groups.length === 0) {
153
+ return { kind: 'and', children: [] };
154
+ }
155
+ const expressions = groups.map(groupToExpression);
156
+ if (expressions.length === 1) {
157
+ return expressions[0];
158
+ }
159
+ return { kind: 'and', children: expressions };
160
+ }
161
+ /** Convert a single ConditionGroup to a ConditionExpression. */
162
+ function groupToExpression(group) {
163
+ const children = group.conditions.map(conditionToExpression);
164
+ let inner;
165
+ if (children.length === 1) {
166
+ inner = children[0];
167
+ }
168
+ else if (group.logic === 'or') {
169
+ inner = { kind: 'or', children };
170
+ }
171
+ else {
172
+ inner = { kind: 'and', children };
173
+ }
174
+ if (group.negated) {
175
+ return { kind: 'not', child: inner };
176
+ }
177
+ return inner;
178
+ }
179
+ /** Convert a PolicyCondition back to a leaf ConditionExpression. */
180
+ function conditionToExpression(cond) {
181
+ // Raw sentinel → raw expression
182
+ if (cond.field === RAW_CONDITION_FIELD) {
183
+ return { kind: 'raw', text: String(cond.value) };
184
+ }
185
+ switch (cond.operator) {
186
+ case 'contains': {
187
+ // contains() checks a single value against a set — value is never string[]
188
+ const containsVal = Array.isArray(cond.value) ? cond.value[0] ?? '' : cond.value;
189
+ return { kind: 'contains', field: cond.field, value: containsVal };
190
+ }
191
+ case 'like':
192
+ return { kind: 'like', field: cond.field, pattern: String(cond.value) };
193
+ default:
194
+ return {
195
+ kind: 'comparison',
196
+ field: cond.field,
197
+ operator: cond.operator,
198
+ value: cond.value,
199
+ };
200
+ }
201
+ }
202
+ // ---------------------------------------------------------------------------
203
+ // expressionToCedar
204
+ // ---------------------------------------------------------------------------
205
+ /**
206
+ * Render any ConditionExpression node to valid Cedar condition text.
207
+ *
208
+ * This handles the full AST including AND, OR, NOT, and raw nodes —
209
+ * unlike `conditionToCedar()` which only handles leaf PolicyConditions.
210
+ *
211
+ * @param expr - The expression tree to render
212
+ * @param optionalFields - Optional set of field names that need `context has` guards
213
+ * @returns Cedar condition text (without the `when { ... }` wrapper)
214
+ */
215
+ export function expressionToCedar(expr, optionalFields) {
216
+ switch (expr.kind) {
217
+ case 'comparison':
218
+ case 'contains':
219
+ case 'like':
220
+ return conditionToCedar(leafToCondition(expr), optionalFields);
221
+ case 'has': {
222
+ const field = sanitizeIdentifier(expr.field, 'field');
223
+ return `context has ${field}`;
224
+ }
225
+ case 'and': {
226
+ if (expr.children.length === 0)
227
+ return 'true';
228
+ if (expr.children.length === 1)
229
+ return expressionToCedar(expr.children[0], optionalFields);
230
+ return expr.children
231
+ .map(c => {
232
+ // Parenthesize OR children to preserve precedence
233
+ if (c.kind === 'or')
234
+ return `(${expressionToCedar(c, optionalFields)})`;
235
+ return expressionToCedar(c, optionalFields);
236
+ })
237
+ .join(' && ');
238
+ }
239
+ case 'or': {
240
+ if (expr.children.length === 0)
241
+ return 'false';
242
+ if (expr.children.length === 1)
243
+ return expressionToCedar(expr.children[0], optionalFields);
244
+ return expr.children
245
+ .map(c => {
246
+ // Parenthesize AND children to preserve precedence
247
+ if (c.kind === 'and')
248
+ return `(${expressionToCedar(c, optionalFields)})`;
249
+ return expressionToCedar(c, optionalFields);
250
+ })
251
+ .join(' || ');
252
+ }
253
+ case 'not': {
254
+ const inner = expressionToCedar(expr.child, optionalFields);
255
+ // If inner is a simple leaf, no parens needed after !
256
+ if (isLeaf(expr.child))
257
+ return `!${inner}`;
258
+ return `!(${inner})`;
259
+ }
260
+ case 'raw': {
261
+ if (isValidRawCondition(expr.text)) {
262
+ return expr.text;
263
+ }
264
+ return '/* invalid raw condition */';
265
+ }
266
+ }
267
+ }
268
+ // ---------------------------------------------------------------------------
269
+ // extractContextFields
270
+ // ---------------------------------------------------------------------------
271
+ /**
272
+ * Extract all unique context field names referenced in a ConditionExpression tree.
273
+ *
274
+ * Used by Shield to determine which detectors to run — only detectors that
275
+ * produce fields referenced in active policies need to execute.
276
+ *
277
+ * @returns Sorted array of unique field names
278
+ */
279
+ export function extractContextFields(expr) {
280
+ const fields = new Set();
281
+ collectFields(expr, fields);
282
+ return Array.from(fields).sort();
283
+ }
284
+ function collectFields(expr, fields) {
285
+ switch (expr.kind) {
286
+ case 'comparison':
287
+ case 'contains':
288
+ case 'like':
289
+ case 'has':
290
+ fields.add(expr.field);
291
+ break;
292
+ case 'and':
293
+ case 'or':
294
+ for (const child of expr.children) {
295
+ collectFields(child, fields);
296
+ }
297
+ break;
298
+ case 'not':
299
+ collectFields(expr.child, fields);
300
+ break;
301
+ case 'raw':
302
+ // Can't reliably extract fields from raw text
303
+ break;
304
+ }
305
+ }
package/dist/index.d.ts CHANGED
@@ -8,16 +8,21 @@ export * from './parser.js';
8
8
  export * from './errors.js';
9
9
  export * from './annotations.js';
10
10
  export * from './explain.js';
11
- export { GUARDRAILS_SCHEMA, GUARDRAILS_CONTEXT, OVERWATCH_SCHEMA, OVERWATCH_CONTEXT, PALISADE_SCHEMA, PALISADE_CONTEXT, } from './service-schemas.gen.js';
11
+ export * from './condition-groups.js';
12
+ export { GUARDRAILS_SCHEMA, GUARDRAILS_CONTEXT, OVERWATCH_SCHEMA, OVERWATCH_CONTEXT, PALISADE_SCHEMA, PALISADE_CONTEXT, SENTRY_SCHEMA, SENTRY_CONTEXT, } from './service-schemas.gen.js';
12
13
  export type { ContextAttribute, ActionContext, ServiceContext, } from './service-schemas.gen.js';
13
14
  export { GuardrailsContextKey } from './guardrails-context.gen.js';
14
15
  export { OverwatchContextKey } from './overwatch-context.gen.js';
15
16
  export { PalisadeContextKey } from './palisade-context.gen.js';
17
+ export { SentryContextKey } from './sentry-context.gen.js';
16
18
  export { GUARDRAILS_ENTITIES, GUARDRAILS_ACTION_ENTITIES, } from './guardrails-entities.gen.js';
17
19
  export { OVERWATCH_ENTITIES, OVERWATCH_ACTION_ENTITIES, } from './overwatch-entities.gen.js';
18
20
  export { PALISADE_ENTITIES, PALISADE_ACTION_ENTITIES, } from './palisade-entities.gen.js';
21
+ export { SENTRY_ENTITIES, SENTRY_ACTION_ENTITIES, } from './sentry-entities.gen.js';
19
22
  export type { ServiceEntityMetadata, ActionEntityMetadata } from './entity-metadata-types.gen.js';
20
23
  export { GUARDRAILS_DEFAULTS, GUARDRAILS_TEMPLATES, GUARDRAILS_CATEGORIES, GUARDRAILS_TEMPLATES_JSON, getGuardrailsDefaultsByCategory, getGuardrailsTemplatesByCategory, getGuardrailsTemplateById, } from './guardrails-defaults.gen.js';
21
24
  export type { GuardrailsCategory, GuardrailsCategoryInfo, GuardrailsDefaultPolicy, GuardrailsTemplate, } from './guardrails-defaults.gen.js';
22
25
  export { OVERWATCH_DEFAULTS, OVERWATCH_TEMPLATES, OVERWATCH_CATEGORIES, OVERWATCH_TEMPLATES_JSON, getOverwatchDefaultsByCategory, getOverwatchTemplatesByCategory, getOverwatchTemplateById, } from './overwatch-defaults.gen.js';
23
26
  export type { OverwatchCategory, OverwatchCategoryInfo, OverwatchDefaultPolicy, OverwatchTemplate, } from './overwatch-defaults.gen.js';
27
+ export { SENTRY_DEFAULTS, SENTRY_TEMPLATES, SENTRY_CATEGORIES, SENTRY_TEMPLATES_JSON, getSentryDefaultsByCategory, getSentryTemplatesByCategory, getSentryTemplateById, } from './sentry-defaults.gen.js';
28
+ export type { SentryCategory, SentryCategoryInfo, SentryDefaultPolicy, SentryTemplate, } from './sentry-defaults.gen.js';
package/dist/index.js CHANGED
@@ -15,16 +15,21 @@ export * from './errors.js';
15
15
  export * from './annotations.js';
16
16
  // Decision explanation
17
17
  export * from './explain.js';
18
+ // Condition groups (AST ↔ flat UI groups)
19
+ export * from './condition-groups.js';
18
20
  // Service-specific schemas and context (inlined)
19
- export { GUARDRAILS_SCHEMA, GUARDRAILS_CONTEXT, OVERWATCH_SCHEMA, OVERWATCH_CONTEXT, PALISADE_SCHEMA, PALISADE_CONTEXT, } from './service-schemas.gen.js';
21
+ export { GUARDRAILS_SCHEMA, GUARDRAILS_CONTEXT, OVERWATCH_SCHEMA, OVERWATCH_CONTEXT, PALISADE_SCHEMA, PALISADE_CONTEXT, SENTRY_SCHEMA, SENTRY_CONTEXT, } from './service-schemas.gen.js';
20
22
  // Service-specific context key enums
21
23
  export { GuardrailsContextKey } from './guardrails-context.gen.js';
22
24
  export { OverwatchContextKey } from './overwatch-context.gen.js';
23
25
  export { PalisadeContextKey } from './palisade-context.gen.js';
26
+ export { SentryContextKey } from './sentry-context.gen.js';
24
27
  // Service-specific entity metadata (for UI - principals, resources, actions)
25
28
  export { GUARDRAILS_ENTITIES, GUARDRAILS_ACTION_ENTITIES, } from './guardrails-entities.gen.js';
26
29
  export { OVERWATCH_ENTITIES, OVERWATCH_ACTION_ENTITIES, } from './overwatch-entities.gen.js';
27
30
  export { PALISADE_ENTITIES, PALISADE_ACTION_ENTITIES, } from './palisade-entities.gen.js';
31
+ export { SENTRY_ENTITIES, SENTRY_ACTION_ENTITIES, } from './sentry-entities.gen.js';
28
32
  // Service-specific default policies, templates, and categories
29
33
  export { GUARDRAILS_DEFAULTS, GUARDRAILS_TEMPLATES, GUARDRAILS_CATEGORIES, GUARDRAILS_TEMPLATES_JSON, getGuardrailsDefaultsByCategory, getGuardrailsTemplatesByCategory, getGuardrailsTemplateById, } from './guardrails-defaults.gen.js';
30
34
  export { OVERWATCH_DEFAULTS, OVERWATCH_TEMPLATES, OVERWATCH_CATEGORIES, OVERWATCH_TEMPLATES_JSON, getOverwatchDefaultsByCategory, getOverwatchTemplatesByCategory, getOverwatchTemplateById, } from './overwatch-defaults.gen.js';
35
+ export { SENTRY_DEFAULTS, SENTRY_TEMPLATES, SENTRY_CATEGORIES, SENTRY_TEMPLATES_JSON, getSentryDefaultsByCategory, getSentryTemplatesByCategory, getSentryTemplateById, } from './sentry-defaults.gen.js';
@@ -41,6 +41,13 @@ export declare const OverwatchContextKey: {
41
41
  readonly SecretCount: "secret_count";
42
42
  readonly SecretTypes: "secret_types";
43
43
  readonly SequenceRisk: "sequence_risk";
44
+ readonly SessionCommandInjection: "session_command_injection";
45
+ readonly SessionInjectionDetected: "session_injection_detected";
46
+ readonly SessionPiiDetected: "session_pii_detected";
47
+ readonly SessionPiiTypes: "session_pii_types";
48
+ readonly SessionSecretTypes: "session_secret_types";
49
+ readonly SessionSecretsDetected: "session_secrets_detected";
50
+ readonly SessionThreatTurns: "session_threat_turns";
44
51
  readonly SexualScore: "sexual_score";
45
52
  readonly Source: "source";
46
53
  readonly SuspiciousPattern: "suspicious_pattern";
@@ -43,6 +43,13 @@ export const OverwatchContextKey = {
43
43
  SecretCount: 'secret_count',
44
44
  SecretTypes: 'secret_types',
45
45
  SequenceRisk: 'sequence_risk',
46
+ SessionCommandInjection: 'session_command_injection',
47
+ SessionInjectionDetected: 'session_injection_detected',
48
+ SessionPiiDetected: 'session_pii_detected',
49
+ SessionPiiTypes: 'session_pii_types',
50
+ SessionSecretTypes: 'session_secret_types',
51
+ SessionSecretsDetected: 'session_secrets_detected',
52
+ SessionThreatTurns: 'session_threat_turns',
46
53
  SexualScore: 'sexual_score',
47
54
  Source: 'source',
48
55
  SuspiciousPattern: 'suspicious_pattern',