@highflame/policy 1.2.0 → 2.0.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.
Files changed (74) hide show
  1. package/README.md +219 -0
  2. package/_schemas/overwatch/context.json +463 -0
  3. package/_schemas/overwatch/schema.cedarschema +184 -0
  4. package/_schemas/palisade/context.json +325 -0
  5. package/_schemas/palisade/schema.cedarschema +168 -0
  6. package/dist/builder.d.ts +1 -2
  7. package/dist/builder.d.ts.map +1 -1
  8. package/dist/builder.js.map +1 -1
  9. package/dist/context.gen.d.ts +1 -94
  10. package/dist/context.gen.d.ts.map +1 -1
  11. package/dist/context.gen.js +1 -97
  12. package/dist/context.gen.js.map +1 -1
  13. package/dist/engine.d.ts +18 -18
  14. package/dist/engine.d.ts.map +1 -1
  15. package/dist/engine.js +44 -28
  16. package/dist/engine.js.map +1 -1
  17. package/dist/engine.test.js.map +1 -1
  18. package/dist/entities.gen.d.ts +1 -0
  19. package/dist/entities.gen.d.ts.map +1 -1
  20. package/dist/entities.gen.js +1 -0
  21. package/dist/entities.gen.js.map +1 -1
  22. package/dist/errors.d.ts +102 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +127 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/overwatch-context.gen.d.ts +31 -0
  31. package/dist/overwatch-context.gen.d.ts.map +1 -0
  32. package/dist/overwatch-context.gen.js +32 -0
  33. package/dist/overwatch-context.gen.js.map +1 -0
  34. package/dist/palisade-context.gen.d.ts +25 -0
  35. package/dist/palisade-context.gen.d.ts.map +1 -0
  36. package/dist/palisade-context.gen.js +26 -0
  37. package/dist/palisade-context.gen.js.map +1 -0
  38. package/dist/parser.d.ts.map +1 -1
  39. package/dist/parser.js +79 -34
  40. package/dist/parser.js.map +1 -1
  41. package/dist/parser.test.js +44 -0
  42. package/dist/parser.test.js.map +1 -1
  43. package/dist/schema.gen.d.ts +1 -1
  44. package/dist/schema.gen.d.ts.map +1 -1
  45. package/dist/schema.gen.js +60 -541
  46. package/dist/schema.gen.js.map +1 -1
  47. package/dist/schemas.d.ts +64 -0
  48. package/dist/schemas.d.ts.map +1 -0
  49. package/dist/schemas.js +70 -0
  50. package/dist/schemas.js.map +1 -0
  51. package/dist/schemas.test.d.ts +8 -0
  52. package/dist/schemas.test.d.ts.map +1 -0
  53. package/dist/schemas.test.js +381 -0
  54. package/dist/schemas.test.js.map +1 -0
  55. package/dist/types.d.ts +1 -0
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/types.js +2 -0
  58. package/dist/types.js.map +1 -1
  59. package/package.json +13 -6
  60. package/src/builder.ts +1 -2
  61. package/src/context.gen.ts +0 -97
  62. package/src/engine.test.ts +0 -1
  63. package/src/engine.ts +62 -33
  64. package/src/entities.gen.ts +1 -0
  65. package/src/errors.ts +195 -0
  66. package/src/index.ts +2 -0
  67. package/src/overwatch-context.gen.ts +34 -0
  68. package/src/palisade-context.gen.ts +28 -0
  69. package/src/parser.test.ts +53 -0
  70. package/src/parser.ts +83 -36
  71. package/src/schema.gen.ts +60 -541
  72. package/src/schemas.test.ts +449 -0
  73. package/src/schemas.ts +91 -0
  74. package/src/types.ts +3 -0
package/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # @highflame/policy - TypeScript Package
2
+
3
+ [![npm version](https://badge.fury.io/js/%40highflame%2Fpolicy.svg)](https://www.npmjs.com/package/@highflame/policy)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-4.5%2B-blue)](https://www.typescriptlang.org/)
5
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
6
+
7
+ Cedar policy engine and typed constants for the Highflame security platform. Ensures entity/action consistency across all Highflame services.
8
+
9
+ ## Features
10
+
11
+ - 🔒 **Cedar Policy Evaluation** - Wraps `@cedar-policy/cedar-wasm` with Highflame types
12
+ - 📝 **Typed Constants** - Auto-generated entity types, actions, and context keys
13
+ - 🎨 **PolicyBuilder** - Type-safe policy builder for UI (works in browser!)
14
+ - 🔄 **Cedar Parser** - Convert Cedar text to structured PolicyRule JSON
15
+ - ✅ **Schema Validation** - Full schema validation with embedded schemas
16
+ - 🌐 **Namespace Support** - Generic support for namespaced actions
17
+ - 🌍 **Browser + Node.js** - Separate entry points for each environment
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @highflame/policy
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ### Node.js - Policy Evaluation (Palisade Example)
28
+
29
+ ```typescript
30
+ import { PolicyEngine, EntityType, ActionType, schemas } from '@highflame/policy';
31
+ import { PalisadeContextKey } from '@highflame/policy/schemas';
32
+
33
+ const engine = new PolicyEngine();
34
+ engine.loadSchema(schemas.PALISADE_SCHEMA);
35
+ engine.loadPolicies(policyContent);
36
+
37
+ const decision = engine.evaluateSimple(
38
+ EntityType.Scanner,
39
+ "palisade",
40
+ ActionType.ScanArtifact,
41
+ EntityType.Artifact,
42
+ "/model.safetensors",
43
+ {
44
+ [PalisadeContextKey.ArtifactFormat]: "safetensors",
45
+ [PalisadeContextKey.Severity]: "HIGH",
46
+ [PalisadeContextKey.Environment]: "production",
47
+ }
48
+ );
49
+
50
+ if (decision.effect === "Deny") {
51
+ console.log(`Blocked by: ${decision.determining_policies}`);
52
+ }
53
+ ```
54
+
55
+ ### Browser - PolicyBuilder (No WASM!)
56
+
57
+ ```typescript
58
+ import { PolicyBuilder, EntityType, ActionType } from '@highflame/policy/types';
59
+ import { PalisadeContextKey } from '@highflame/policy/schemas';
60
+
61
+ // Works in browser - no Cedar WASM dependency!
62
+ const policy = PolicyBuilder.permit()
63
+ .name("Allow artifact scanning in production")
64
+ .principalType(EntityType.Scanner)
65
+ .action(ActionType.ScanArtifact)
66
+ .resourceType(EntityType.Artifact)
67
+ .when(PalisadeContextKey.Environment, 'eq', 'production')
68
+ .build();
69
+
70
+ const cedarText = policy.toCedar();
71
+ // Send to backend for validation + storage
72
+ ```
73
+
74
+ ### Service-Specific Schemas
75
+
76
+ ```typescript
77
+ import { PolicyEngine, schemas } from '@highflame/policy';
78
+
79
+ // Use Overwatch schema for IDE security (Guardian)
80
+ const engine = new PolicyEngine();
81
+ engine.loadSchema(schemas.OVERWATCH_SCHEMA);
82
+
83
+ const decision = engine.evaluateSimple(
84
+ "Overwatch::User",
85
+ "mcp_client",
86
+ 'Overwatch::Action::"call_tool"',
87
+ "Overwatch::Tool",
88
+ "shell",
89
+ { threat_count: 3 }
90
+ );
91
+ ```
92
+
93
+ ## Entry Points
94
+
95
+ ### `@highflame/policy` (Node.js)
96
+ Full package including PolicyEngine, validation, and schemas.
97
+
98
+ ```typescript
99
+ import {
100
+ PolicyEngine, // Cedar evaluation
101
+ validatePolicy, // Schema validation
102
+ parseCedarToRules, // Parser
103
+ EntityType, // Constants
104
+ ActionType,
105
+ schemas // Service schemas
106
+ } from '@highflame/policy';
107
+
108
+ // Import service-specific context keys
109
+ import { OverwatchContextKey, PalisadeContextKey } from '@highflame/policy/schemas';
110
+ ```
111
+
112
+ ### `@highflame/policy/types` (Browser + Node.js)
113
+ Types, constants, and PolicyBuilder only - no WASM dependency.
114
+
115
+ ```typescript
116
+ import {
117
+ EntityType,
118
+ ActionType,
119
+ PolicyBuilder
120
+ } from '@highflame/policy/types';
121
+
122
+ // Import service-specific context keys
123
+ import { OverwatchContextKey, PalisadeContextKey } from '@highflame/policy/schemas';
124
+ ```
125
+
126
+ ## PolicyBuilder API
127
+
128
+ Perfect for building policies in UI:
129
+
130
+ ```typescript
131
+ import { PolicyBuilder, EntityType, ActionType } from '@highflame/policy/types';
132
+ import { PalisadeContextKey } from '@highflame/policy/schemas';
133
+
134
+ const policy = PolicyBuilder.permit()
135
+ .name("Allow scanners to scan artifacts in production")
136
+ .principalType(EntityType.Scanner)
137
+ .action(ActionType.ScanArtifact)
138
+ .resourceType(EntityType.Artifact)
139
+ .when(PalisadeContextKey.Environment, 'eq', 'production')
140
+ .when(PalisadeContextKey.Severity, 'eq', 'HIGH')
141
+ .build();
142
+
143
+ // Get Cedar text
144
+ const cedarText = policy.toCedar();
145
+
146
+ // Get JSON for editing
147
+ const policyJson = policy.toJSON();
148
+ ```
149
+
150
+ ## Cedar Parser
151
+
152
+ Parse existing policies for UI editing:
153
+
154
+ ```typescript
155
+ import { parseCedarToRules } from '@highflame/policy';
156
+
157
+ const result = parseCedarToRules(cedarText);
158
+
159
+ // Successfully parsed policies
160
+ result.rules.forEach(rule => {
161
+ console.log(rule.id, rule.effect);
162
+ // Edit in UI, then rebuild with PolicyBuilder
163
+ });
164
+
165
+ // Policies that can't be represented (unless clauses, templates)
166
+ result.unstructured.forEach(policy => {
167
+ // Show as raw Cedar text
168
+ });
169
+ ```
170
+
171
+ ## Available Constants
172
+
173
+ - **17 Entity Types**: `EntityType.User`, `Scanner`, `Artifact`, `Tool`, etc.
174
+ - **38 Actions**: `ActionType.ScanArtifact`, `CallTool`, `LoadModel`, etc.
175
+ - **Service-Specific Context Keys**:
176
+ - **Overwatch**: `OverwatchContextKey.ThreatCount`, `ToolName`, `UserEmail`, etc.
177
+ - **Palisade**: `PalisadeContextKey.Environment`, `Severity`, `ArtifactFormat`, etc.
178
+
179
+ ## Service Schemas
180
+
181
+ ### Overwatch (Guardian)
182
+ ```typescript
183
+ import { schemas } from '@highflame/policy';
184
+
185
+ // schemas.OVERWATCH_SCHEMA - Cedar schema
186
+ // schemas.OVERWATCH_CONTEXT - Context metadata (JSON)
187
+ ```
188
+
189
+ ### Palisade
190
+ ```typescript
191
+ // schemas.PALISADE_SCHEMA - Cedar schema
192
+ // schemas.PALISADE_CONTEXT - Context metadata (JSON)
193
+ ```
194
+
195
+ ## Why Typed Constants?
196
+
197
+ **Without types (error-prone):**
198
+ ```typescript
199
+ context: {
200
+ "enviroment": "production", // Typo! Policy won't match
201
+ }
202
+ ```
203
+
204
+ **With types (autocomplete + type checking):**
205
+ ```typescript
206
+ import { PalisadeContextKey } from '@highflame/policy/schemas';
207
+
208
+ context: {
209
+ [PalisadeContextKey.Environment]: "production", // ✓ Autocomplete works!
210
+ }
211
+ ```
212
+
213
+ ## Documentation
214
+
215
+ Full documentation: [CLAUDE.md](https://github.com/highflame-ai/highflame-policy/blob/main/CLAUDE.md)
216
+
217
+ ## License
218
+
219
+ Apache 2.0
@@ -0,0 +1,463 @@
1
+ {
2
+ "service": "overwatch",
3
+ "version": "1.0.0",
4
+ "description": "Overwatch (Guardian) IDE security & policy enforcement",
5
+ "actions": [
6
+ {
7
+ "name": "process_prompt",
8
+ "description": "User submits a prompt or receives AI response",
9
+ "context_attributes": [
10
+ {
11
+ "key": "content",
12
+ "type": "string",
13
+ "required": true,
14
+ "description": "Raw content being scanned (prompt, command, etc.)"
15
+ },
16
+ {
17
+ "key": "source",
18
+ "type": "string",
19
+ "required": true,
20
+ "description": "IDE source: cursor, claudecode, github_copilot"
21
+ },
22
+ {
23
+ "key": "event",
24
+ "type": "string",
25
+ "required": true,
26
+ "description": "Hook event name (e.g., beforeSubmitPrompt, UserPromptSubmit)"
27
+ },
28
+ {
29
+ "key": "user_email",
30
+ "type": "string",
31
+ "required": true,
32
+ "description": "User identifier (OAuth verified or fallback)"
33
+ },
34
+ {
35
+ "key": "cwd",
36
+ "type": "string",
37
+ "required": false,
38
+ "description": "Current working directory"
39
+ },
40
+ {
41
+ "key": "workspace_root",
42
+ "type": "string",
43
+ "required": false,
44
+ "description": "Workspace/repository root path"
45
+ },
46
+ {
47
+ "key": "threat_count",
48
+ "type": "number",
49
+ "required": true,
50
+ "description": "Total number of threats detected by YARA/Javelin"
51
+ },
52
+ {
53
+ "key": "highest_severity",
54
+ "type": "string",
55
+ "required": true,
56
+ "description": "Highest severity level: critical, high, medium, low"
57
+ },
58
+ {
59
+ "key": "threat_categories",
60
+ "type": "array",
61
+ "required": true,
62
+ "description": "Threat category names from aggregator"
63
+ },
64
+ {
65
+ "key": "threat_types",
66
+ "type": "array",
67
+ "required": true,
68
+ "description": "YARA threat category names"
69
+ },
70
+ {
71
+ "key": "yara_threats",
72
+ "type": "array",
73
+ "required": true,
74
+ "description": "YARA rule names that matched"
75
+ },
76
+ {
77
+ "key": "max_threat_severity",
78
+ "type": "number",
79
+ "required": true,
80
+ "description": "Numeric severity (0-4, where 4=CRITICAL)"
81
+ },
82
+ {
83
+ "key": "contains_secrets",
84
+ "type": "boolean",
85
+ "required": true,
86
+ "description": "Whether secrets or credentials were detected"
87
+ },
88
+ {
89
+ "key": "prompt_text",
90
+ "type": "string",
91
+ "required": false,
92
+ "description": "Same as content (legacy field)"
93
+ },
94
+ {
95
+ "key": "response_content",
96
+ "type": "string",
97
+ "required": false,
98
+ "description": "Response content from AI (if available)"
99
+ }
100
+ ]
101
+ },
102
+ {
103
+ "name": "call_tool",
104
+ "description": "User calls a tool (native IDE tool or MCP tool)",
105
+ "context_attributes": [
106
+ {
107
+ "key": "content",
108
+ "type": "string",
109
+ "required": true,
110
+ "description": "Raw content being scanned (e.g., shell command)"
111
+ },
112
+ {
113
+ "key": "source",
114
+ "type": "string",
115
+ "required": true,
116
+ "description": "IDE source: cursor, claudecode, github_copilot"
117
+ },
118
+ {
119
+ "key": "event",
120
+ "type": "string",
121
+ "required": true,
122
+ "description": "Hook event name (e.g., beforeShellExecution, PreToolUse)"
123
+ },
124
+ {
125
+ "key": "user_email",
126
+ "type": "string",
127
+ "required": true,
128
+ "description": "User identifier"
129
+ },
130
+ {
131
+ "key": "tool_name",
132
+ "type": "string",
133
+ "required": false,
134
+ "description": "Normalized tool name: shell, read_file, write_file, edit_file, etc."
135
+ },
136
+ {
137
+ "key": "mcp_server",
138
+ "type": "string",
139
+ "required": false,
140
+ "description": "MCP server name (e.g., filesystem, playwright)"
141
+ },
142
+ {
143
+ "key": "mcp_tool",
144
+ "type": "string",
145
+ "required": false,
146
+ "description": "MCP tool name (e.g., list_directory, navigate)"
147
+ },
148
+ {
149
+ "key": "server_name",
150
+ "type": "string",
151
+ "required": false,
152
+ "description": "Alias for mcp_server (duplicate field)"
153
+ },
154
+ {
155
+ "key": "path",
156
+ "type": "string",
157
+ "required": false,
158
+ "description": "File path (if file operation)"
159
+ },
160
+ {
161
+ "key": "file_path",
162
+ "type": "string",
163
+ "required": false,
164
+ "description": "Duplicate of path field"
165
+ },
166
+ {
167
+ "key": "cwd",
168
+ "type": "string",
169
+ "required": false,
170
+ "description": "Current working directory"
171
+ },
172
+ {
173
+ "key": "workspace_root",
174
+ "type": "string",
175
+ "required": false,
176
+ "description": "Workspace/repository root path"
177
+ },
178
+ {
179
+ "key": "threat_count",
180
+ "type": "number",
181
+ "required": true,
182
+ "description": "Total threats detected"
183
+ },
184
+ {
185
+ "key": "highest_severity",
186
+ "type": "string",
187
+ "required": true,
188
+ "description": "Highest severity: critical, high, medium, low"
189
+ },
190
+ {
191
+ "key": "threat_categories",
192
+ "type": "array",
193
+ "required": true,
194
+ "description": "Threat category names"
195
+ },
196
+ {
197
+ "key": "threat_types",
198
+ "type": "array",
199
+ "required": true,
200
+ "description": "YARA threat categories"
201
+ },
202
+ {
203
+ "key": "yara_threats",
204
+ "type": "array",
205
+ "required": true,
206
+ "description": "YARA rule names"
207
+ },
208
+ {
209
+ "key": "max_threat_severity",
210
+ "type": "number",
211
+ "required": true,
212
+ "description": "Numeric severity (0-4)"
213
+ },
214
+ {
215
+ "key": "contains_secrets",
216
+ "type": "boolean",
217
+ "required": true,
218
+ "description": "Whether secrets detected"
219
+ },
220
+ {
221
+ "key": "response_content",
222
+ "type": "string",
223
+ "required": false,
224
+ "description": "Response content (if available)"
225
+ }
226
+ ]
227
+ },
228
+ {
229
+ "name": "connect_server",
230
+ "description": "Connect to an MCP server",
231
+ "context_attributes": [
232
+ {
233
+ "key": "content",
234
+ "type": "string",
235
+ "required": true,
236
+ "description": "Raw content being scanned"
237
+ },
238
+ {
239
+ "key": "source",
240
+ "type": "string",
241
+ "required": true,
242
+ "description": "IDE source"
243
+ },
244
+ {
245
+ "key": "event",
246
+ "type": "string",
247
+ "required": true,
248
+ "description": "Hook event name"
249
+ },
250
+ {
251
+ "key": "user_email",
252
+ "type": "string",
253
+ "required": true,
254
+ "description": "User identifier"
255
+ },
256
+ {
257
+ "key": "mcp_server",
258
+ "type": "string",
259
+ "required": false,
260
+ "description": "MCP server name"
261
+ },
262
+ {
263
+ "key": "server_name",
264
+ "type": "string",
265
+ "required": false,
266
+ "description": "Alias for mcp_server"
267
+ },
268
+ {
269
+ "key": "threat_count",
270
+ "type": "number",
271
+ "required": true,
272
+ "description": "Total threats detected"
273
+ },
274
+ {
275
+ "key": "highest_severity",
276
+ "type": "string",
277
+ "required": true,
278
+ "description": "Highest severity level"
279
+ },
280
+ {
281
+ "key": "threat_categories",
282
+ "type": "array",
283
+ "required": true,
284
+ "description": "Threat category names"
285
+ },
286
+ {
287
+ "key": "max_threat_severity",
288
+ "type": "number",
289
+ "required": true,
290
+ "description": "Numeric severity (0-4)"
291
+ }
292
+ ]
293
+ },
294
+ {
295
+ "name": "read_file",
296
+ "description": "Read a file from disk",
297
+ "context_attributes": [
298
+ {
299
+ "key": "content",
300
+ "type": "string",
301
+ "required": true,
302
+ "description": "File content or operation details"
303
+ },
304
+ {
305
+ "key": "source",
306
+ "type": "string",
307
+ "required": true,
308
+ "description": "IDE source"
309
+ },
310
+ {
311
+ "key": "event",
312
+ "type": "string",
313
+ "required": true,
314
+ "description": "Hook event name (e.g., beforeReadFile)"
315
+ },
316
+ {
317
+ "key": "user_email",
318
+ "type": "string",
319
+ "required": true,
320
+ "description": "User identifier"
321
+ },
322
+ {
323
+ "key": "path",
324
+ "type": "string",
325
+ "required": false,
326
+ "description": "File path being read"
327
+ },
328
+ {
329
+ "key": "file_path",
330
+ "type": "string",
331
+ "required": false,
332
+ "description": "Duplicate of path field"
333
+ },
334
+ {
335
+ "key": "cwd",
336
+ "type": "string",
337
+ "required": false,
338
+ "description": "Current working directory"
339
+ },
340
+ {
341
+ "key": "workspace_root",
342
+ "type": "string",
343
+ "required": false,
344
+ "description": "Workspace root path"
345
+ },
346
+ {
347
+ "key": "threat_count",
348
+ "type": "number",
349
+ "required": true,
350
+ "description": "Total threats detected"
351
+ },
352
+ {
353
+ "key": "highest_severity",
354
+ "type": "string",
355
+ "required": true,
356
+ "description": "Highest severity level"
357
+ },
358
+ {
359
+ "key": "threat_categories",
360
+ "type": "array",
361
+ "required": true,
362
+ "description": "Threat categories"
363
+ },
364
+ {
365
+ "key": "max_threat_severity",
366
+ "type": "number",
367
+ "required": true,
368
+ "description": "Numeric severity (0-4)"
369
+ },
370
+ {
371
+ "key": "contains_secrets",
372
+ "type": "boolean",
373
+ "required": true,
374
+ "description": "Whether secrets detected"
375
+ }
376
+ ]
377
+ },
378
+ {
379
+ "name": "write_file",
380
+ "description": "Write a file to disk",
381
+ "context_attributes": [
382
+ {
383
+ "key": "content",
384
+ "type": "string",
385
+ "required": true,
386
+ "description": "File content being written"
387
+ },
388
+ {
389
+ "key": "source",
390
+ "type": "string",
391
+ "required": true,
392
+ "description": "IDE source"
393
+ },
394
+ {
395
+ "key": "event",
396
+ "type": "string",
397
+ "required": true,
398
+ "description": "Hook event name"
399
+ },
400
+ {
401
+ "key": "user_email",
402
+ "type": "string",
403
+ "required": true,
404
+ "description": "User identifier"
405
+ },
406
+ {
407
+ "key": "path",
408
+ "type": "string",
409
+ "required": false,
410
+ "description": "File path being written"
411
+ },
412
+ {
413
+ "key": "file_path",
414
+ "type": "string",
415
+ "required": false,
416
+ "description": "Duplicate of path field"
417
+ },
418
+ {
419
+ "key": "cwd",
420
+ "type": "string",
421
+ "required": false,
422
+ "description": "Current working directory"
423
+ },
424
+ {
425
+ "key": "workspace_root",
426
+ "type": "string",
427
+ "required": false,
428
+ "description": "Workspace root path"
429
+ },
430
+ {
431
+ "key": "threat_count",
432
+ "type": "number",
433
+ "required": true,
434
+ "description": "Total threats detected"
435
+ },
436
+ {
437
+ "key": "highest_severity",
438
+ "type": "string",
439
+ "required": true,
440
+ "description": "Highest severity level"
441
+ },
442
+ {
443
+ "key": "threat_categories",
444
+ "type": "array",
445
+ "required": true,
446
+ "description": "Threat categories"
447
+ },
448
+ {
449
+ "key": "max_threat_severity",
450
+ "type": "number",
451
+ "required": true,
452
+ "description": "Numeric severity (0-4)"
453
+ },
454
+ {
455
+ "key": "contains_secrets",
456
+ "type": "boolean",
457
+ "required": true,
458
+ "description": "Whether secrets detected"
459
+ }
460
+ ]
461
+ }
462
+ ]
463
+ }