@highflame/policy 2.2.33 → 2.2.34

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.
@@ -827,6 +827,228 @@
827
827
  }
828
828
  ]
829
829
  },
830
+ {
831
+ "name": "post_tool_call",
832
+ "description": "Inspect the output of a tool that has already run. The tool has run, so enforcement is limited to rewriting or withholding what the model sees.",
833
+ "context_attributes": [
834
+ {
835
+ "key": "role",
836
+ "type": "string",
837
+ "required": false,
838
+ "description": "role projected for the post_tool_call surface."
839
+ },
840
+ {
841
+ "key": "privilege_scope",
842
+ "type": "array",
843
+ "required": false,
844
+ "description": "privilege_scope projected for the post_tool_call surface."
845
+ },
846
+ {
847
+ "key": "content",
848
+ "type": "string",
849
+ "required": true,
850
+ "description": "The tool's OUTPUT (stdout+stderr, or serialized response)"
851
+ },
852
+ {
853
+ "key": "source",
854
+ "type": "string",
855
+ "required": true,
856
+ "description": "IDE source"
857
+ },
858
+ {
859
+ "key": "event",
860
+ "type": "string",
861
+ "required": true,
862
+ "description": "Hook event name"
863
+ },
864
+ {
865
+ "key": "user_email",
866
+ "type": "string",
867
+ "required": true,
868
+ "description": "User identifier"
869
+ },
870
+ {
871
+ "key": "tool_name",
872
+ "type": "string",
873
+ "required": false,
874
+ "description": "tool_name projected for the post_tool_call surface."
875
+ },
876
+ {
877
+ "key": "mcp_server",
878
+ "type": "string",
879
+ "required": false,
880
+ "description": "mcp_server projected for the post_tool_call surface."
881
+ },
882
+ {
883
+ "key": "mcp_tool",
884
+ "type": "string",
885
+ "required": false,
886
+ "description": "mcp_tool projected for the post_tool_call surface."
887
+ },
888
+ {
889
+ "key": "exit_code",
890
+ "type": "number",
891
+ "required": false,
892
+ "description": "Absent when the IDE does not propagate it"
893
+ },
894
+ {
895
+ "key": "path",
896
+ "type": "string",
897
+ "required": false,
898
+ "description": "path projected for the post_tool_call surface."
899
+ },
900
+ {
901
+ "key": "cwd",
902
+ "type": "string",
903
+ "required": false,
904
+ "description": "cwd projected for the post_tool_call surface."
905
+ },
906
+ {
907
+ "key": "workspace_root",
908
+ "type": "string",
909
+ "required": false,
910
+ "description": "workspace_root projected for the post_tool_call surface."
911
+ },
912
+ {
913
+ "key": "threat_count",
914
+ "type": "number",
915
+ "required": false,
916
+ "description": "threat_count projected for the post_tool_call surface."
917
+ },
918
+ {
919
+ "key": "highest_severity",
920
+ "type": "string",
921
+ "required": false,
922
+ "description": "highest_severity projected for the post_tool_call surface."
923
+ },
924
+ {
925
+ "key": "threat_categories",
926
+ "type": "array",
927
+ "required": false,
928
+ "description": "threat_categories projected for the post_tool_call surface."
929
+ },
930
+ {
931
+ "key": "detected_threats",
932
+ "type": "array",
933
+ "required": false,
934
+ "description": "detected_threats projected for the post_tool_call surface."
935
+ },
936
+ {
937
+ "key": "max_threat_severity",
938
+ "type": "number",
939
+ "required": false,
940
+ "description": "max_threat_severity projected for the post_tool_call surface."
941
+ },
942
+ {
943
+ "key": "secrets_detected",
944
+ "type": "boolean",
945
+ "required": false,
946
+ "description": "secrets_detected projected for the post_tool_call surface."
947
+ },
948
+ {
949
+ "key": "secret_types",
950
+ "type": "array",
951
+ "required": false,
952
+ "description": "secret_types projected for the post_tool_call surface."
953
+ },
954
+ {
955
+ "key": "secret_count",
956
+ "type": "number",
957
+ "required": false,
958
+ "description": "secret_count projected for the post_tool_call surface."
959
+ },
960
+ {
961
+ "key": "pii_detected",
962
+ "type": "boolean",
963
+ "required": false,
964
+ "description": "pii_detected projected for the post_tool_call surface."
965
+ },
966
+ {
967
+ "key": "pii_types",
968
+ "type": "array",
969
+ "required": false,
970
+ "description": "pii_types projected for the post_tool_call surface."
971
+ },
972
+ {
973
+ "key": "pii_count",
974
+ "type": "number",
975
+ "required": false,
976
+ "description": "pii_count projected for the post_tool_call surface."
977
+ },
978
+ {
979
+ "key": "pii_score",
980
+ "type": "number",
981
+ "required": false,
982
+ "description": "pii_score projected for the post_tool_call surface."
983
+ },
984
+ {
985
+ "key": "indirect_injection_score",
986
+ "type": "number",
987
+ "required": false,
988
+ "description": "indirect_injection_score projected for the post_tool_call surface."
989
+ },
990
+ {
991
+ "key": "invisible_chars_detected",
992
+ "type": "boolean",
993
+ "required": false,
994
+ "description": "invisible_chars_detected projected for the post_tool_call surface."
995
+ },
996
+ {
997
+ "key": "invisible_chars_score",
998
+ "type": "number",
999
+ "required": false,
1000
+ "description": "invisible_chars_score projected for the post_tool_call surface."
1001
+ },
1002
+ {
1003
+ "key": "session_pii_detected",
1004
+ "type": "boolean",
1005
+ "required": false,
1006
+ "description": "session_pii_detected projected for the post_tool_call surface."
1007
+ },
1008
+ {
1009
+ "key": "session_pii_types",
1010
+ "type": "array",
1011
+ "required": false,
1012
+ "description": "session_pii_types projected for the post_tool_call surface."
1013
+ },
1014
+ {
1015
+ "key": "session_secrets_detected",
1016
+ "type": "boolean",
1017
+ "required": false,
1018
+ "description": "session_secrets_detected projected for the post_tool_call surface."
1019
+ },
1020
+ {
1021
+ "key": "session_secret_types",
1022
+ "type": "array",
1023
+ "required": false,
1024
+ "description": "session_secret_types projected for the post_tool_call surface."
1025
+ },
1026
+ {
1027
+ "key": "session_threat_turns",
1028
+ "type": "number",
1029
+ "required": false,
1030
+ "description": "session_threat_turns projected for the post_tool_call surface."
1031
+ },
1032
+ {
1033
+ "key": "session_max_secret_score",
1034
+ "type": "number",
1035
+ "required": false,
1036
+ "description": "session_max_secret_score projected for the post_tool_call surface."
1037
+ },
1038
+ {
1039
+ "key": "session_max_pii_score",
1040
+ "type": "number",
1041
+ "required": false,
1042
+ "description": "session_max_pii_score projected for the post_tool_call surface."
1043
+ },
1044
+ {
1045
+ "key": "session_cumulative_risk_score",
1046
+ "type": "number",
1047
+ "required": false,
1048
+ "description": "session_cumulative_risk_score projected for the post_tool_call surface."
1049
+ }
1050
+ ]
1051
+ },
830
1052
  {
831
1053
  "name": "connect_server",
832
1054
  "description": "Connect to an MCP server",
@@ -266,6 +266,78 @@ action call_tool appliesTo {
266
266
  },
267
267
  };
268
268
 
269
+ // Inspect the OUTPUT of a tool that has already run.
270
+ // Threat focus: a credential or PII printed by a tool entering the agent's
271
+ // context. The tool has run, so a permission verdict is meaningless here — the
272
+ // only enforcement available is rewriting or withholding what the model sees,
273
+ // which is why transform rules (@redaction_strategy) are the point of this
274
+ // action. Context is deliberately narrower than call_tool: pre-execution
275
+ // signals (tool risk, loop, sequence) belong to the call, not its result.
276
+ action post_tool_call appliesTo {
277
+ principal: [User, Agent],
278
+ resource: [Tool, FilePath, LlmPrompt],
279
+ context: {
280
+ // Identity (AARM R6 / CAP-IDN-011) — projected from the principal's token; optional.
281
+ role?: String,
282
+ privilege_scope?: Set<String>,
283
+ // --- Event & Source ---
284
+ content: String, // The tool's OUTPUT (stdout+stderr, or serialized response)
285
+ source: String, // IDE source
286
+ event: String, // Hook event name
287
+ user_email: String, // User identifier
288
+
289
+ // --- Tool & MCP ---
290
+ tool_name?: String,
291
+ mcp_server?: String,
292
+ mcp_tool?: String,
293
+
294
+ // --- Result ---
295
+ exit_code?: Long, // Absent when the IDE does not propagate it
296
+
297
+ // --- File & Path ---
298
+ path?: String,
299
+
300
+ // --- Workspace ---
301
+ cwd?: String,
302
+ workspace_root?: String,
303
+
304
+ // --- Threat Detection ---
305
+ threat_count?: Long,
306
+ highest_severity?: String,
307
+ threat_categories?: Set<String>,
308
+ detected_threats?: Set<String>,
309
+ max_threat_severity?: Long,
310
+
311
+ // --- Secrets --- the primary signal for this surface
312
+ secrets_detected?: Bool,
313
+ secret_types?: Set<String>,
314
+ secret_count?: Long,
315
+
316
+ // --- PII Detection ---
317
+ pii_detected?: Bool,
318
+ pii_types?: Set<String>,
319
+ pii_count?: Long,
320
+ pii_score?: Long,
321
+
322
+ // --- Indirect injection via tool output --- (OWASP ASI01)
323
+ indirect_injection_score?: Long,
324
+
325
+ // --- Encoding & Unicode Attacks ---
326
+ invisible_chars_detected?: Bool,
327
+ invisible_chars_score?: Long,
328
+
329
+ // --- Session Detection History (cross-turn sticky flags) ---
330
+ session_pii_detected?: Bool,
331
+ session_pii_types?: Set<String>,
332
+ session_secrets_detected?: Bool,
333
+ session_secret_types?: Set<String>,
334
+ session_threat_turns?: Long,
335
+ session_max_secret_score?: Long,
336
+ session_max_pii_score?: Long,
337
+ session_cumulative_risk_score?: Long,
338
+ },
339
+ };
340
+
269
341
  // Connect to an MCP server
270
342
  // Threat focus: supply chain, tool poisoning, rug pull, config risk
271
343
  action connect_server appliesTo {
@@ -59,6 +59,23 @@ export interface DeferUntilContextDirective {
59
59
  /** Dotted Cedar context-attribute path (e.g. "session_max_sensitivity"). */
60
60
  field: string;
61
61
  }
62
+ /**
63
+ * How a transform rule rewrites the spans its detector matched. A forbid
64
+ * carrying this annotation is allow-class: Shield emits decision=modify with
65
+ * redacted_content instead of blocking.
66
+ */
67
+ export interface RedactionStrategyDirective {
68
+ /**
69
+ * One of redact | mask | anonymize | replace. Case-sensitive.
70
+ *
71
+ * An unrecognized value is rejected at parse time: Shield treats any
72
+ * non-empty strategy as "redact" and an unknown one falls through to the
73
+ * generic [REDACTED] label, so a typo would produce the wrong redaction
74
+ * rather than the intended masking. An EMPTY value instead reverts the rule
75
+ * to a hard forbid.
76
+ */
77
+ strategy: string;
78
+ }
62
79
  /**
63
80
  * Every typed AARM directive parsed from a single policy's annotation map.
64
81
  * Each field is present iff the corresponding annotation was present and
@@ -69,6 +86,7 @@ export interface AARMDirectives {
69
86
  deferOnConflict?: DeferOnConflictDirective;
70
87
  deferBelowConfidence?: DeferBelowConfidenceDirective;
71
88
  deferUntilContext?: DeferUntilContextDirective;
89
+ redactionStrategy?: RedactionStrategyDirective;
72
90
  }
73
91
  /** True iff at least one AARM directive was parsed. */
74
92
  export declare function hasAnyAARMDirective(d: AARMDirectives | null | undefined): boolean;
@@ -44,7 +44,8 @@ export function hasAnyAARMDirective(d) {
44
44
  return (d.stepUpRequired !== undefined ||
45
45
  d.deferOnConflict !== undefined ||
46
46
  d.deferBelowConfidence !== undefined ||
47
- d.deferUntilContext !== undefined);
47
+ d.deferUntilContext !== undefined ||
48
+ d.redactionStrategy !== undefined);
48
49
  }
49
50
  /**
50
51
  * Structured error for a malformed AARM annotation. Mirrors Go's
@@ -134,6 +135,14 @@ export function parseAARMAnnotations(raw) {
134
135
  directives.deferUntilContext = r.directive;
135
136
  break;
136
137
  }
138
+ case 'redaction_strategy': {
139
+ const r = buildRedactionStrategy(def, params, value);
140
+ if (r.error)
141
+ errors.push(r.error);
142
+ else
143
+ directives.redactionStrategy = r.directive;
144
+ break;
145
+ }
137
146
  default:
138
147
  // Registry entry exists but no typed extractor is wired here — a
139
148
  // programming error in highflame-policy. Fail closed at runtime.
@@ -286,6 +295,32 @@ function buildDeferBelowConfidence(def, params, raw) {
286
295
  return { error: bounds };
287
296
  return { directive: { threshold: threshold.value } };
288
297
  }
298
+ /**
299
+ * Mirrors the enum in schemas/annotations.json. Kept here because
300
+ * AARMParameterDef carries no enum field, so the generated registry cannot.
301
+ */
302
+ const REDACTION_STRATEGIES = ['redact', 'mask', 'anonymize', 'replace'];
303
+ function buildRedactionStrategy(def, params, raw) {
304
+ const strategy = requireStringParam(def, params, 'strategy', raw);
305
+ if ('error' in strategy)
306
+ return { error: strategy.error };
307
+ // Fail closed, matching this module's documented posture. Known hazard tracked
308
+ // separately: Shield's syncer drops the ENTIRE policy on an annotation error,
309
+ // so a typo removes the redaction rule from enforcement. The fix belongs on the
310
+ // Shield side — keep the policy, drop only the offending directive.
311
+ if (!REDACTION_STRATEGIES.includes(strategy.value)) {
312
+ return {
313
+ error: new AARMAnnotationError({
314
+ key: def.key,
315
+ parameter: 'strategy',
316
+ rawValue: raw,
317
+ reason: 'strategy must be one of redact, mask, anonymize, replace; ' +
318
+ 'values are case-sensitive',
319
+ }),
320
+ };
321
+ }
322
+ return { directive: { strategy: strategy.value } };
323
+ }
289
324
  function buildDeferUntilContext(def, params, raw) {
290
325
  const field = requireStringParam(def, params, 'field', raw);
291
326
  if ('error' in field)
@@ -64,6 +64,27 @@ export const AARM_ANNOTATIONS = [
64
64
  },
65
65
  ],
66
66
  },
67
+ {
68
+ key: 'redaction_strategy',
69
+ description: 'Rewrite the matched content instead of blocking. A determining forbid carrying this annotation makes Shield emit decision=modify with redacted_content, replacing every detected span using the named strategy. Redaction is allow-class and applies regardless of posture — there is no shadow redaction — but most-restrictive-wins still applies, so a co-firing hard forbid in enforce posture beats it and blocks. Shield treats any non-empty strategy as a redaction and falls through to a generic replacement label for a value it does not recognize, so an out-of-enum value produces the wrong redaction rather than the author\'s intent; an empty value reverts the rule to a hard forbid. The enum is enforced by the typed extractor in the Go and TypeScript packages, which reject a policy carrying an unrecognized value.',
70
+ aarmRequirement: 'R3',
71
+ promotesCapability: 'CAP-ENF-003',
72
+ decisionEffect: 'modify',
73
+ parameters: [
74
+ {
75
+ name: 'strategy',
76
+ type: 'string',
77
+ required: true,
78
+ positional: true,
79
+ description: 'How to rewrite each detected span. redact removes it entirely; replace substitutes a single generic label; mask preserves a recognizable shape (the producing detector may supply its own mask); anonymize substitutes a realistic fake. Case-sensitive — an out-of-enum value is rejected at parse time.',
80
+ default: null,
81
+ min: null,
82
+ max: null,
83
+ pattern: '',
84
+ valueSource: '',
85
+ },
86
+ ],
87
+ },
67
88
  {
68
89
  key: 'step_up_required',
69
90
  description: 'Suspend the action pending human approval from an approver with the named role. AARM R4 STEP_UP decision: Shield issues an OpenID CIBA bc-authorize challenge and the action does not execute until an approver carrying the role resolves it (POST /oauth2/bc-authorize/{auth_req_id}/approve via AuthN), OR timeout_seconds elapses (fail-closed: timeout DENYs the action, never permits).',
@@ -51,8 +51,8 @@ export const AI_GATEWAY_DETECTORS = [
51
51
  inhouse: false,
52
52
  model: null,
53
53
  latencyP50Ms: 2,
54
- emits: [{ name: "secrets_detected", type: "Bool", modifiable: false, semantic: "boolean_flag" }, { name: "secret_types", type: "Set<String>", modifiable: false, semantic: "category_set" }, { name: "secret_count", type: "Long", modifiable: false, semantic: "count" }],
55
- supportedModes: ["enforce", "monitor", "alert"],
54
+ emits: [{ name: "secrets_detected", type: "Bool", modifiable: false, semantic: "boolean_flag" }, { name: "secret_types", type: "Set<String>", modifiable: true, semantic: "category_set" }, { name: "secret_count", type: "Long", modifiable: false, semantic: "count" }],
55
+ supportedModes: ["enforce", "monitor", "alert", "modify"],
56
56
  defendsAgainst: ["credential_leakage", "prompt_leakage"],
57
57
  exampleAttacks: [],
58
58
  },
@@ -23,8 +23,8 @@ export const GUARDRAILS_DETECTORS = [
23
23
  inhouse: false,
24
24
  model: null,
25
25
  latencyP50Ms: 2,
26
- emits: [{ name: "contains_secrets", type: "Bool", modifiable: false, semantic: "boolean_flag", description: "True iff at least one secret pattern matched." }, { name: "secret_types", type: "Set<String>", modifiable: false, semantic: "category_set", description: "Distinct secret types detected (e.g. aws_access_key, github_pat, stripe_key)." }, { name: "secret_count", type: "Long", modifiable: false, semantic: "count", description: "Total secret matches detected." }],
27
- supportedModes: ["enforce", "monitor", "alert"],
26
+ emits: [{ name: "contains_secrets", type: "Bool", modifiable: false, semantic: "boolean_flag", description: "True iff at least one secret pattern matched." }, { name: "secret_types", type: "Set<String>", modifiable: true, semantic: "category_set", description: "Distinct secret types detected (e.g. aws_access_key, github_pat, stripe_key)." }, { name: "secret_count", type: "Long", modifiable: false, semantic: "count", description: "Total secret matches detected." }],
27
+ supportedModes: ["enforce", "monitor", "alert", "modify"],
28
28
  defendsAgainst: ["credential_leakage", "prompt_leakage"],
29
29
  exampleAttacks: [{ title: "API key in content", vulnerabilityId: "credential_leakage", snippet: "Use my key sk-proj-AAbb1234567890ZZ for the API call.", expectedSignal: { "contains_secrets": true } }],
30
30
  },
@@ -10,6 +10,7 @@ export declare const OverwatchContextKey: {
10
10
  readonly Cwd: "cwd";
11
11
  readonly DetectedThreats: "detected_threats";
12
12
  readonly Event: "event";
13
+ readonly ExitCode: "exit_code";
13
14
  readonly HateSpeechScore: "hate_speech_score";
14
15
  readonly HighestSeverity: "highest_severity";
15
16
  readonly IndirectInjectionScore: "indirect_injection_score";
@@ -93,4 +94,4 @@ export type OverwatchContextKey = (typeof OverwatchContextKey)[keyof typeof Over
93
94
  * The full set of authorable context attribute keys for Overwatch.
94
95
  * Iterate this to enumerate the authorable surface (cockpit, conformance).
95
96
  */
96
- export declare const OverwatchContextKeys: readonly ["content", "crime_score", "cwd", "detected_threats", "event", "hate_speech_score", "highest_severity", "indirect_injection_score", "injection_deep_context_score", "injection_pulse_score", "injection_score", "invisible_chars_detected", "invisible_chars_score", "jailbreak_deep_context_score", "jailbreak_pulse_score", "jailbreak_score", "loop_count", "loop_detected", "loop_tool", "malicious_package_detected", "malicious_package_score", "malicious_packages", "max_threat_severity", "mcp_config_risk", "mcp_risk_score", "mcp_server", "mcp_server_verified", "mcp_tool", "package_advisory_count", "package_check_status", "package_ecosystems", "package_install_detected", "package_names", "package_risk_score", "packages_checked", "path", "pattern_type", "pii_count", "pii_detected", "pii_score", "pii_types", "privilege_scope", "profanity_score", "prompt_text", "response_content", "role", "rug_pull_detected", "rug_pull_score", "secret_count", "secret_types", "secrets_detected", "sequence_risk", "session_command_injection", "session_cumulative_risk_score", "session_injection_detected", "session_max_command_injection_score", "session_max_injection_score", "session_max_jailbreak_score", "session_max_pii_score", "session_max_secret_score", "session_pii_detected", "session_pii_types", "session_secret_types", "session_secrets_detected", "session_threat_turns", "sexual_score", "source", "suspicious_pattern", "threat_categories", "threat_count", "tool_category", "tool_is_builtin", "tool_is_sensitive", "tool_name", "tool_operation_classes", "tool_poisoning_detected", "tool_poisoning_score", "tool_risk_score", "user_email", "violence_score", "weapons_score", "workspace_root"];
97
+ export declare const OverwatchContextKeys: readonly ["content", "crime_score", "cwd", "detected_threats", "event", "exit_code", "hate_speech_score", "highest_severity", "indirect_injection_score", "injection_deep_context_score", "injection_pulse_score", "injection_score", "invisible_chars_detected", "invisible_chars_score", "jailbreak_deep_context_score", "jailbreak_pulse_score", "jailbreak_score", "loop_count", "loop_detected", "loop_tool", "malicious_package_detected", "malicious_package_score", "malicious_packages", "max_threat_severity", "mcp_config_risk", "mcp_risk_score", "mcp_server", "mcp_server_verified", "mcp_tool", "package_advisory_count", "package_check_status", "package_ecosystems", "package_install_detected", "package_names", "package_risk_score", "packages_checked", "path", "pattern_type", "pii_count", "pii_detected", "pii_score", "pii_types", "privilege_scope", "profanity_score", "prompt_text", "response_content", "role", "rug_pull_detected", "rug_pull_score", "secret_count", "secret_types", "secrets_detected", "sequence_risk", "session_command_injection", "session_cumulative_risk_score", "session_injection_detected", "session_max_command_injection_score", "session_max_injection_score", "session_max_jailbreak_score", "session_max_pii_score", "session_max_secret_score", "session_pii_detected", "session_pii_types", "session_secret_types", "session_secrets_detected", "session_threat_turns", "sexual_score", "source", "suspicious_pattern", "threat_categories", "threat_count", "tool_category", "tool_is_builtin", "tool_is_sensitive", "tool_name", "tool_operation_classes", "tool_poisoning_detected", "tool_poisoning_score", "tool_risk_score", "user_email", "violence_score", "weapons_score", "workspace_root"];
@@ -12,6 +12,7 @@ export const OverwatchContextKey = {
12
12
  Cwd: 'cwd',
13
13
  DetectedThreats: 'detected_threats',
14
14
  Event: 'event',
15
+ ExitCode: 'exit_code',
15
16
  HateSpeechScore: 'hate_speech_score',
16
17
  HighestSeverity: 'highest_severity',
17
18
  IndirectInjectionScore: 'indirect_injection_score',
@@ -100,6 +101,7 @@ export const OverwatchContextKeys = [
100
101
  OverwatchContextKey.Cwd,
101
102
  OverwatchContextKey.DetectedThreats,
102
103
  OverwatchContextKey.Event,
104
+ OverwatchContextKey.ExitCode,
103
105
  OverwatchContextKey.HateSpeechScore,
104
106
  OverwatchContextKey.HighestSeverity,
105
107
  OverwatchContextKey.IndirectInjectionScore,
@@ -188,6 +188,54 @@ when {
188
188
  )
189
189
  };
190
190
  `;
191
+ const OVERWATCH_DATA_PROTECTION_REDACT_SECRETS_IN_TOOL_OUTPUT_CEDAR = `// =============================================================================
192
+ // Mask Secrets In Tool Output (Default, inactive)
193
+ // =============================================================================
194
+ // Masks secrets in tool output instead of withholding it.
195
+ //
196
+ // Context keys consumed:
197
+ // - secrets_detected: Bool
198
+ //
199
+ // Compliance:
200
+ // - NIST 800-53 SC-28, IA-5
201
+ // - OWASP LLM06
202
+ //
203
+ // Category: data-protection
204
+ // Namespace: Overwatch
205
+ // =============================================================================
206
+ //
207
+ // The companion to data-protection.block-secrets-*, for the one surface where
208
+ // blocking is not available: post_tool_call fires after the tool has already
209
+ // run, so a permission verdict is meaningless and the only enforcement left is
210
+ // changing what the model sees.
211
+ //
212
+ // @redaction_strategy makes Shield emit decision=modify with redacted_content,
213
+ // which the agent client substitutes for the real output. Clients differ in how
214
+ // far they can honour that — Claude Code and Cursor (MCP tools only) replace the
215
+ // output in place, while Codex and Gemini CLI can only withhold it wholesale.
216
+ //
217
+ // Not active by default: it changes the outcome for tool output from "nothing"
218
+ // to "masked", and on withhold-only clients from "nothing" to "output
219
+ // suppressed". Turn it on deliberately.
220
+ //
221
+ // Ordering note: Shield resolves most-restrictive-first (block > redact >
222
+ // observe). A hard secrets forbid in enforce posture that also matches this
223
+ // action will beat this rule and block. Scope hard forbids off post_tool_call
224
+ // if you want masking rather than suppression.
225
+
226
+ @id("data-protection.redact-secrets-in-tool-output")
227
+ @name("Mask secrets in tool output")
228
+ @description("Masks detected credentials in tool output instead of letting them enter the agent context.")
229
+ @severity("high")
230
+ @tags("category:data-protection,threat:secrets,detection:rule,surface:post-tool-call,owasp:llm06")
231
+ @redaction_strategy("mask")
232
+ forbid (
233
+ principal,
234
+ action == Overwatch::Action::"post_tool_call",
235
+ resource
236
+ )
237
+ when { context has secrets_detected && context.secrets_detected == true };
238
+ `;
191
239
  const OVERWATCH_SEMANTIC_DEFAULTS_CEDAR = `// =============================================================================
192
240
  // Semantic Threat Detection (Default)
193
241
  // =============================================================================
@@ -1848,6 +1896,15 @@ export const OVERWATCH_TEMPLATES = [
1848
1896
  severity: 'critical',
1849
1897
  tags: ['category:data-protection', 'threat:secrets', 'owasp:llm06'],
1850
1898
  },
1899
+ {
1900
+ id: 'data-protection.redact-secrets-in-tool-output',
1901
+ name: 'Mask Secrets In Tool Output',
1902
+ description: 'Masks detected credentials in tool output instead of letting them enter the agent context. Off by default; masking-capable clients replace the output, withhold-only clients suppress it.',
1903
+ category: 'data-protection',
1904
+ cedarText: OVERWATCH_DATA_PROTECTION_REDACT_SECRETS_IN_TOOL_OUTPUT_CEDAR,
1905
+ severity: 'high',
1906
+ tags: ['category:data-protection', 'threat:secrets', 'detection:rule', 'owasp:llm06'],
1907
+ },
1851
1908
  {
1852
1909
  id: 'semantic.defaults',
1853
1910
  name: 'Semantic Threat Detection',
@@ -2051,7 +2108,10 @@ export const OVERWATCH_TEMPLATES_JSON = `{
2051
2108
  "category": "organization",
2052
2109
  "file": "defaults/baseline.cedar",
2053
2110
  "severity": "low",
2054
- "tags": ["category:organization", "posture:permit-default"],
2111
+ "tags": [
2112
+ "category:organization",
2113
+ "posture:permit-default"
2114
+ ],
2055
2115
  "is_active": true
2056
2116
  }
2057
2117
  ],
@@ -2063,7 +2123,10 @@ export const OVERWATCH_TEMPLATES_JSON = `{
2063
2123
  "category": "organization",
2064
2124
  "file": "defaults/baseline.cedar",
2065
2125
  "severity": "low",
2066
- "tags": ["category:organization", "posture:permit-default"],
2126
+ "tags": [
2127
+ "category:organization",
2128
+ "posture:permit-default"
2129
+ ],
2067
2130
  "auto_deploy": true
2068
2131
  },
2069
2132
  {
@@ -2073,7 +2136,25 @@ export const OVERWATCH_TEMPLATES_JSON = `{
2073
2136
  "category": "data-protection",
2074
2137
  "file": "defaults/secrets.cedar",
2075
2138
  "severity": "critical",
2076
- "tags": ["category:data-protection", "threat:secrets", "owasp:llm06"]
2139
+ "tags": [
2140
+ "category:data-protection",
2141
+ "threat:secrets",
2142
+ "owasp:llm06"
2143
+ ]
2144
+ },
2145
+ {
2146
+ "id": "data-protection.redact-secrets-in-tool-output",
2147
+ "name": "Mask Secrets In Tool Output",
2148
+ "description": "Masks detected credentials in tool output instead of letting them enter the agent context. Off by default; masking-capable clients replace the output, withhold-only clients suppress it.",
2149
+ "category": "data-protection",
2150
+ "file": "defaults/secrets_redaction.cedar",
2151
+ "severity": "high",
2152
+ "tags": [
2153
+ "category:data-protection",
2154
+ "threat:secrets",
2155
+ "detection:rule",
2156
+ "owasp:llm06"
2157
+ ]
2077
2158
  },
2078
2159
  {
2079
2160
  "id": "semantic.defaults",
@@ -2223,7 +2304,11 @@ export const OVERWATCH_TEMPLATES_JSON = `{
2223
2304
  "category": "tools",
2224
2305
  "file": "mcp_server_allowlist.cedar",
2225
2306
  "severity": "medium",
2226
- "tags": ["category:tools", "scope:org-wide", "posture:deny-default"]
2307
+ "tags": [
2308
+ "category:tools",
2309
+ "scope:org-wide",
2310
+ "posture:deny-default"
2311
+ ]
2227
2312
  },
2228
2313
  {
2229
2314
  "id": "tools.mcp-tool-permissions",
@@ -2284,7 +2369,11 @@ export const OVERWATCH_TEMPLATES_JSON = `{
2284
2369
  "category": "agent-identity",
2285
2370
  "file": "agent_guardrails.cedar",
2286
2371
  "severity": "critical",
2287
- "tags": ["category:agent-identity", "scope:per-agent", "threat:injection"]
2372
+ "tags": [
2373
+ "category:agent-identity",
2374
+ "scope:per-agent",
2375
+ "threat:injection"
2376
+ ]
2288
2377
  },
2289
2378
  {
2290
2379
  "id": "agent-security.malicious-package",
@@ -51,8 +51,8 @@ export const OVERWATCH_DETECTORS = [
51
51
  inhouse: false,
52
52
  model: null,
53
53
  latencyP50Ms: 2,
54
- emits: [{ name: "secrets_detected", type: "Bool", modifiable: false, semantic: "boolean_flag", description: "True iff at least one secret pattern matched." }, { name: "secret_types", type: "Set<String>", modifiable: false, semantic: "category_set", description: "Distinct secret types detected." }, { name: "secret_count", type: "Long", modifiable: false, semantic: "count", description: "Total secret matches detected." }],
55
- supportedModes: ["enforce", "monitor", "alert"],
54
+ emits: [{ name: "secrets_detected", type: "Bool", modifiable: false, semantic: "boolean_flag", description: "True iff at least one secret pattern matched." }, { name: "secret_types", type: "Set<String>", modifiable: true, semantic: "category_set", description: "Distinct secret types detected." }, { name: "secret_count", type: "Long", modifiable: false, semantic: "count", description: "Total secret matches detected." }],
55
+ supportedModes: ["enforce", "monitor", "alert", "modify"],
56
56
  defendsAgainst: ["credential_leakage", "prompt_leakage"],
57
57
  exampleAttacks: [{ title: "Key surfaced from a file read", vulnerabilityId: "credential_leakage", snippet: "(tool read .env) OPENAI_API_KEY=sk-proj-AAbb1234567890ZZ", expectedSignal: { "secrets_detected": true } }],
58
58
  },
@@ -7,7 +7,7 @@
7
7
  export const OVERWATCH_ENTITIES = {
8
8
  principals: ['Agent', 'User'],
9
9
  resources: ['FilePath', 'LlmPrompt', 'Server', 'Tool'],
10
- actions: ['call_tool', 'connect_server', 'process_prompt', 'read_file', 'write_file'],
10
+ actions: ['call_tool', 'connect_server', 'post_tool_call', 'process_prompt', 'read_file', 'write_file'],
11
11
  };
12
12
  /**
13
13
  * Per-action entity mapping for Overwatch.
@@ -22,6 +22,10 @@ export const OVERWATCH_ACTION_ENTITIES = {
22
22
  principals: ['Agent', 'User'],
23
23
  resources: ['Server'],
24
24
  },
25
+ 'post_tool_call': {
26
+ principals: ['Agent', 'User'],
27
+ resources: ['FilePath', 'LlmPrompt', 'Tool'],
28
+ },
25
29
  'process_prompt': {
26
30
  principals: ['Agent', 'User'],
27
31
  resources: ['LlmPrompt'],
@@ -37,8 +37,8 @@ export const SENTRY_DETECTORS = [
37
37
  inhouse: false,
38
38
  model: null,
39
39
  latencyP50Ms: 2,
40
- emits: [{ name: "secrets_detected", type: "Bool", modifiable: false, semantic: "boolean_flag", description: "True iff a secret pattern matched." }, { name: "secret_types", type: "Set<String>", modifiable: false, semantic: "category_set", description: "Distinct secret types detected." }, { name: "secret_count", type: "Long", modifiable: false, semantic: "count", description: "Total secret matches detected." }],
41
- supportedModes: ["enforce", "monitor", "alert"],
40
+ emits: [{ name: "secrets_detected", type: "Bool", modifiable: false, semantic: "boolean_flag", description: "True iff a secret pattern matched." }, { name: "secret_types", type: "Set<String>", modifiable: true, semantic: "category_set", description: "Distinct secret types detected." }, { name: "secret_count", type: "Long", modifiable: false, semantic: "count", description: "Total secret matches detected." }],
41
+ supportedModes: ["enforce", "monitor", "alert", "modify"],
42
42
  defendsAgainst: ["credential_leakage", "prompt_leakage"],
43
43
  exampleAttacks: [],
44
44
  },
@@ -21,7 +21,7 @@ export declare const GUARDRAILS_SCHEMA = "// ===================================
21
21
  *
22
22
  * Full Cedar schema for overwatch, embedded at codegen time.
23
23
  */
24
- export declare const OVERWATCH_SCHEMA = "// Overwatch Cedar Schema\n// ===================================\n// IDE Agent Security & Policy Enforcement\n//\n// Overwatch protects IDE agent operations (prompts, tool calls, file access, MCP connections)\n// by evaluating threats detected by the detection engine pipeline against Cedar policies.\n//\n// Architecture:\n// User/Agent \u2192 IDE Hook \u2192 Detection Engine \u2192 Cedar Policy \u2192 Allow/Deny\n//\n// Supported IDEs:\n// - Cursor (beforeSubmitPrompt, beforeShellExecution, beforeMCPExecution, etc.)\n// - Claude Code (UserPromptSubmit, PreToolUse)\n// - GitHub Copilot (userPromptSubmitted, preToolUse)\n//\n// Threat Coverage:\n// - OWASP Top 10 for LLM Applications 2025 (LLM01-LLM10)\n// - OWASP Top 10 for Agentic Applications (ASI01-ASI10)\n// - OWASP MCP Top 10 (MCP01-MCP05)\n// - MITRE ATLAS Agent Techniques (AML.T0051, AML.T0080-T0082)\n\nnamespace Overwatch {\n\n// =============================================================================\n// ENTITIES - Tenant Hierarchy (ReBAC)\n// =============================================================================\n// Aligned with Guardrails entity hierarchy (Account \u2192 Project).\n// Overwatch does not have app-specific policies, so App is omitted.\n//\n// Entity hierarchy enables Cedar's `in` operator for policy scoping:\n// Account (org root)\n// \u2514\u2500\u2500 Project in [Account]\n// \u2514\u2500\u2500 Tool/Server/FilePath/LlmPrompt in [Project]\n//\n// Policy scoping examples:\n// resource == Overwatch::Tool::\"shell\" \u2192 specific tool\n// resource in Overwatch::Project::\"<uuid>\" \u2192 project-wide\n// resource in Overwatch::Account::\"<uuid>\" \u2192 org-wide\n\n/// Account represents an organization (top-level tenant)\nentity Account;\n\n/// Project represents a project within an account\nentity Project in [Account];\n\n// =============================================================================\n// ENTITIES - Principals\n// =============================================================================\n\n/// Human user or service account making requests to the IDE\nentity User;\n\n/// AI agent (Claude, GitHub Copilot, etc.)\nentity Agent;\n\n// =============================================================================\n// ENTITIES - Resources (scoped under Project)\n// =============================================================================\n\n/// LLM prompt or session \u2014 resource for process_prompt action\nentity LlmPrompt in [Project];\n\n/// MCP tool or native IDE tool \u2014 resource for call_tool action\nentity Tool in [Project];\n\n/// MCP server \u2014 resource for connect_server action\nentity Server in [Project];\n\n/// File system path \u2014 resource for read_file/write_file/call_tool actions\nentity FilePath in [Project];\n\n// =============================================================================\n// ACTIONS\n// =============================================================================\n\n// User submits a prompt or receives AI response\n// Threat focus: injection, jailbreak, secrets, PII, content safety, invisible chars\naction process_prompt appliesTo {\n principal: [User, Agent],\n resource: [LlmPrompt],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n // --- Event & Source ---\n content: String, // Raw content being scanned\n source: String, // IDE source: \"cursor\", \"claudecode\", \"github_copilot\"\n event: String, // Hook event name\n user_email: String, // User identifier\n\n // --- Workspace ---\n cwd?: String, // Current working directory\n workspace_root?: String, // Workspace/repository root\n\n // --- Threat Detection (from detection engine pipeline) ---\n threat_count: Long, // Total threats detected\n highest_severity: String, // \"critical\", \"high\", \"medium\", \"low\", \"none\"\n threat_categories: Set<String>, // Threat category names\n detected_threats: Set<String>, // Detection rule names that matched\n max_threat_severity: Long, // Numeric severity (0=none, 1=low, 2=medium, 3=high, 4=critical)\n secrets_detected: Bool, // Whether secrets/credentials detected\n\n // --- Secrets (granular) ---\n secret_types?: Set<String>, // Types: \"aws_access_key\", \"github_token\", \"ssh_private_key\", etc.\n secret_count?: Long, // Number of distinct secrets found\n\n // --- PII Detection ---\n pii_detected?: Bool, // Whether any PII patterns matched\n pii_types?: Set<String>, // Types: \"ssn\", \"credit_card\", \"email\", \"phone\", etc.\n pii_count?: Long, // Number of PII matches\n\n // --- Encoding & Unicode Attacks ---\n invisible_chars_detected?: Bool, // Zero-width chars, bidi overrides, tag chars detected\n invisible_chars_score?: Long, // Unicode attack severity (0-100)\n\n // --- Content Safety Scores (0-100, from ML classifiers) ---\n violence_score: Long,\n weapons_score: Long,\n hate_speech_score: Long,\n crime_score: Long,\n sexual_score: Long,\n profanity_score: Long,\n\n // --- ML Detector Confidence Scores (0-100) ---\n pii_score: Long, // PII detection classifier confidence\n injection_score: Long, // Combined injection confidence: MAX(pulse, deep_context)\n jailbreak_score: Long, // Combined jailbreak confidence: MAX(pulse, deep_context)\n injection_pulse_score?: Long, // 0-100 Pulse single-turn classifier\n injection_deep_context_score?: Long, // 0-100 DeepContext multi-turn\n jailbreak_pulse_score?: Long, // 0-100 Pulse single-turn classifier\n jailbreak_deep_context_score?: Long, // 0-100 DeepContext multi-turn\n\n // --- Agent Security (0-100) ---\n indirect_injection_score: Long, // Indirect prompt injection risk (OWASP LLM01, ASI01)\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n\n // --- Legacy ---\n prompt_text?: String, // Same as content (backward compatibility)\n response_content?: String, // Response content (if available)\n },\n};\n\n// User calls a tool (native IDE tool or MCP tool)\n// Threat focus: command injection, tool poisoning, rug pull, data exfiltration, loops\naction call_tool appliesTo {\n principal: [User, Agent],\n resource: [Tool, FilePath],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n // --- Event & Source ---\n content: String, // Raw content being scanned (e.g., shell command, tool args)\n source: String, // IDE source\n event: String, // Hook event name\n user_email: String, // User identifier\n\n // --- Tool & MCP ---\n tool_name?: String, // Normalized tool name (\"shell\", \"read_file\", etc.)\n mcp_server?: String, // MCP server name\n mcp_tool?: String, // MCP tool name\n\n // --- File & Path ---\n path?: String, // File path (if file operation)\n\n // --- Workspace ---\n cwd?: String,\n workspace_root?: String,\n\n // --- Threat Detection ---\n threat_count?: Long,\n highest_severity?: String,\n threat_categories?: Set<String>,\n detected_threats?: Set<String>,\n max_threat_severity?: Long,\n secrets_detected?: Bool,\n\n // --- Secrets (granular) ---\n secret_types?: Set<String>,\n secret_count?: Long,\n\n // --- PII Detection ---\n pii_detected?: Bool,\n pii_types?: Set<String>,\n pii_count?: Long,\n\n // --- Encoding & Unicode Attacks ---\n invisible_chars_detected?: Bool,\n invisible_chars_score?: Long,\n\n // --- Content Safety Scores (0-100) ---\n violence_score?: Long,\n weapons_score?: Long,\n hate_speech_score?: Long,\n crime_score?: Long,\n sexual_score?: Long,\n profanity_score?: Long,\n\n // --- ML Detector Confidence Scores (0-100) ---\n pii_score?: Long,\n injection_score?: Long, // Combined injection confidence: MAX(pulse, deep_context)\n jailbreak_score?: Long, // Combined jailbreak confidence: MAX(pulse, deep_context)\n injection_pulse_score?: Long, // 0-100 Pulse single-turn classifier\n injection_deep_context_score?: Long, // 0-100 DeepContext multi-turn\n jailbreak_pulse_score?: Long, // 0-100 Pulse single-turn classifier\n jailbreak_deep_context_score?: Long, // 0-100 DeepContext multi-turn\n\n // --- Agent Security (0-100) --- (OWASP ASI01, ASI02, ASI04; MITRE AML.T0051)\n tool_poisoning_score?: Long, // Hidden instructions in tool description/args\n tool_poisoning_detected?: Bool, // Boolean flag for tool poisoning\n rug_pull_score?: Long, // Tool behavior drift after trust establishment\n rug_pull_detected?: Bool, // Boolean flag for rug pull\n indirect_injection_score?: Long, // Indirect injection via tool output\n\n // --- Tool Risk Assessment ---\n tool_risk_score?: Long, // Computed tool risk (0-100)\n tool_category?: String, // \"safe\", \"sensitive\", \"dangerous\"\n tool_is_sensitive?: Bool, // Sensitivity classification\n tool_is_builtin?: Bool, // Built-in IDE tool vs MCP tool\n\n // --- Behavioral Analysis --- (OWASP LLM10, ASI02, ASI08)\n loop_detected?: Bool, // Consecutive same-tool call loop\n loop_count?: Long, // Number of consecutive repeat calls\n loop_tool?: String, // Tool name in loop\n suspicious_pattern?: Bool, // Data exfiltration or attack sequence detected\n pattern_type?: String, // \"data_exfiltration\", \"secret_exfiltration\", \"credential_theft\", \"destructive_sequence\"\n sequence_risk?: Long, // Sequence risk score (0-100)\n\n // --- MCP Trust ---\n mcp_server_verified?: Bool, // Whether server is from verified registry\n\n // --- Tool Operation Classifier ---\n tool_operation_classes?: Set<String>, // subset of {\"readonly\", \"write_enabling\", \"execute_enabling\", \"network_access\", \"unknown\"}\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n\n // --- Legacy ---\n response_content?: String,\n },\n};\n\n// Connect to an MCP server\n// Threat focus: supply chain, tool poisoning, rug pull, config risk\naction connect_server appliesTo {\n principal: [User, Agent],\n resource: [Server],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n content?: String, // Server config content (if available)\n source: String,\n event: String,\n user_email: String,\n mcp_server?: String,\n\n // --- Threat Detection ---\n threat_count?: Long,\n highest_severity?: String,\n threat_categories?: Set<String>,\n max_threat_severity?: Long,\n\n // --- Agent Security (0-100) --- (OWASP ASI04, MCP01-MCP05)\n tool_poisoning_score?: Long, // Poisoned tool descriptions in server\n tool_poisoning_detected?: Bool,\n rug_pull_score?: Long, // Server behavior change after approval\n rug_pull_detected?: Bool,\n indirect_injection_score?: Long, // Injection payloads in server responses\n\n // --- MCP Trust & Config Risk ---\n mcp_server_verified?: Bool, // Verified registry status\n mcp_config_risk?: Bool, // Risky server config detected (inline code exec, etc.)\n mcp_risk_score?: Long, // Config risk severity (0-100)\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n },\n};\n\n// Read a file from disk\n// Threat focus: secrets exposure, PII exposure, path traversal, sensitive paths\naction read_file appliesTo {\n principal: [User, Agent],\n resource: [FilePath],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n content: String,\n source: String,\n event: String,\n user_email: String,\n path?: String,\n cwd?: String,\n workspace_root?: String,\n\n // --- Threat Detection ---\n threat_count?: Long,\n highest_severity?: String,\n threat_categories?: Set<String>,\n detected_threats?: Set<String>,\n max_threat_severity?: Long,\n secrets_detected?: Bool,\n\n // --- Secrets (granular) ---\n secret_types?: Set<String>,\n secret_count?: Long,\n\n // --- PII Detection ---\n pii_detected?: Bool,\n pii_types?: Set<String>,\n pii_count?: Long,\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n\n // --- Agent Security ---\n indirect_injection_score?: Long, // Indirect injection via file content\n },\n};\n\n// Write a file to disk\n// Threat focus: secrets in output, PII in output, sensitive paths, malicious code\naction write_file appliesTo {\n principal: [User, Agent],\n resource: [FilePath],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n content: String,\n source: String,\n event: String,\n user_email: String,\n path?: String,\n cwd?: String,\n workspace_root?: String,\n\n // --- Threat Detection ---\n threat_count?: Long,\n highest_severity?: String,\n threat_categories?: Set<String>,\n detected_threats?: Set<String>,\n max_threat_severity?: Long,\n secrets_detected?: Bool,\n\n // --- Secrets (granular) ---\n secret_types?: Set<String>,\n secret_count?: Long,\n\n // --- PII Detection ---\n pii_detected?: Bool,\n pii_types?: Set<String>,\n pii_count?: Long,\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n\n // --- Agent Security ---\n indirect_injection_score?: Long, // Indirect injection via file content\n },\n};\n\n}\n";
24
+ export declare const OVERWATCH_SCHEMA = "// Overwatch Cedar Schema\n// ===================================\n// IDE Agent Security & Policy Enforcement\n//\n// Overwatch protects IDE agent operations (prompts, tool calls, file access, MCP connections)\n// by evaluating threats detected by the detection engine pipeline against Cedar policies.\n//\n// Architecture:\n// User/Agent \u2192 IDE Hook \u2192 Detection Engine \u2192 Cedar Policy \u2192 Allow/Deny\n//\n// Supported IDEs:\n// - Cursor (beforeSubmitPrompt, beforeShellExecution, beforeMCPExecution, etc.)\n// - Claude Code (UserPromptSubmit, PreToolUse)\n// - GitHub Copilot (userPromptSubmitted, preToolUse)\n//\n// Threat Coverage:\n// - OWASP Top 10 for LLM Applications 2025 (LLM01-LLM10)\n// - OWASP Top 10 for Agentic Applications (ASI01-ASI10)\n// - OWASP MCP Top 10 (MCP01-MCP05)\n// - MITRE ATLAS Agent Techniques (AML.T0051, AML.T0080-T0082)\n\nnamespace Overwatch {\n\n// =============================================================================\n// ENTITIES - Tenant Hierarchy (ReBAC)\n// =============================================================================\n// Aligned with Guardrails entity hierarchy (Account \u2192 Project).\n// Overwatch does not have app-specific policies, so App is omitted.\n//\n// Entity hierarchy enables Cedar's `in` operator for policy scoping:\n// Account (org root)\n// \u2514\u2500\u2500 Project in [Account]\n// \u2514\u2500\u2500 Tool/Server/FilePath/LlmPrompt in [Project]\n//\n// Policy scoping examples:\n// resource == Overwatch::Tool::\"shell\" \u2192 specific tool\n// resource in Overwatch::Project::\"<uuid>\" \u2192 project-wide\n// resource in Overwatch::Account::\"<uuid>\" \u2192 org-wide\n\n/// Account represents an organization (top-level tenant)\nentity Account;\n\n/// Project represents a project within an account\nentity Project in [Account];\n\n// =============================================================================\n// ENTITIES - Principals\n// =============================================================================\n\n/// Human user or service account making requests to the IDE\nentity User;\n\n/// AI agent (Claude, GitHub Copilot, etc.)\nentity Agent;\n\n// =============================================================================\n// ENTITIES - Resources (scoped under Project)\n// =============================================================================\n\n/// LLM prompt or session \u2014 resource for process_prompt action\nentity LlmPrompt in [Project];\n\n/// MCP tool or native IDE tool \u2014 resource for call_tool action\nentity Tool in [Project];\n\n/// MCP server \u2014 resource for connect_server action\nentity Server in [Project];\n\n/// File system path \u2014 resource for read_file/write_file/call_tool actions\nentity FilePath in [Project];\n\n// =============================================================================\n// ACTIONS\n// =============================================================================\n\n// User submits a prompt or receives AI response\n// Threat focus: injection, jailbreak, secrets, PII, content safety, invisible chars\naction process_prompt appliesTo {\n principal: [User, Agent],\n resource: [LlmPrompt],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n // --- Event & Source ---\n content: String, // Raw content being scanned\n source: String, // IDE source: \"cursor\", \"claudecode\", \"github_copilot\"\n event: String, // Hook event name\n user_email: String, // User identifier\n\n // --- Workspace ---\n cwd?: String, // Current working directory\n workspace_root?: String, // Workspace/repository root\n\n // --- Threat Detection (from detection engine pipeline) ---\n threat_count: Long, // Total threats detected\n highest_severity: String, // \"critical\", \"high\", \"medium\", \"low\", \"none\"\n threat_categories: Set<String>, // Threat category names\n detected_threats: Set<String>, // Detection rule names that matched\n max_threat_severity: Long, // Numeric severity (0=none, 1=low, 2=medium, 3=high, 4=critical)\n secrets_detected: Bool, // Whether secrets/credentials detected\n\n // --- Secrets (granular) ---\n secret_types?: Set<String>, // Types: \"aws_access_key\", \"github_token\", \"ssh_private_key\", etc.\n secret_count?: Long, // Number of distinct secrets found\n\n // --- PII Detection ---\n pii_detected?: Bool, // Whether any PII patterns matched\n pii_types?: Set<String>, // Types: \"ssn\", \"credit_card\", \"email\", \"phone\", etc.\n pii_count?: Long, // Number of PII matches\n\n // --- Encoding & Unicode Attacks ---\n invisible_chars_detected?: Bool, // Zero-width chars, bidi overrides, tag chars detected\n invisible_chars_score?: Long, // Unicode attack severity (0-100)\n\n // --- Content Safety Scores (0-100, from ML classifiers) ---\n violence_score: Long,\n weapons_score: Long,\n hate_speech_score: Long,\n crime_score: Long,\n sexual_score: Long,\n profanity_score: Long,\n\n // --- ML Detector Confidence Scores (0-100) ---\n pii_score: Long, // PII detection classifier confidence\n injection_score: Long, // Combined injection confidence: MAX(pulse, deep_context)\n jailbreak_score: Long, // Combined jailbreak confidence: MAX(pulse, deep_context)\n injection_pulse_score?: Long, // 0-100 Pulse single-turn classifier\n injection_deep_context_score?: Long, // 0-100 DeepContext multi-turn\n jailbreak_pulse_score?: Long, // 0-100 Pulse single-turn classifier\n jailbreak_deep_context_score?: Long, // 0-100 DeepContext multi-turn\n\n // --- Agent Security (0-100) ---\n indirect_injection_score: Long, // Indirect prompt injection risk (OWASP LLM01, ASI01)\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n\n // --- Legacy ---\n prompt_text?: String, // Same as content (backward compatibility)\n response_content?: String, // Response content (if available)\n },\n};\n\n// User calls a tool (native IDE tool or MCP tool)\n// Threat focus: command injection, tool poisoning, rug pull, data exfiltration, loops\naction call_tool appliesTo {\n principal: [User, Agent],\n resource: [Tool, FilePath],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n // --- Event & Source ---\n content: String, // Raw content being scanned (e.g., shell command, tool args)\n source: String, // IDE source\n event: String, // Hook event name\n user_email: String, // User identifier\n\n // --- Tool & MCP ---\n tool_name?: String, // Normalized tool name (\"shell\", \"read_file\", etc.)\n mcp_server?: String, // MCP server name\n mcp_tool?: String, // MCP tool name\n\n // --- File & Path ---\n path?: String, // File path (if file operation)\n\n // --- Workspace ---\n cwd?: String,\n workspace_root?: String,\n\n // --- Threat Detection ---\n threat_count?: Long,\n highest_severity?: String,\n threat_categories?: Set<String>,\n detected_threats?: Set<String>,\n max_threat_severity?: Long,\n secrets_detected?: Bool,\n\n // --- Secrets (granular) ---\n secret_types?: Set<String>,\n secret_count?: Long,\n\n // --- PII Detection ---\n pii_detected?: Bool,\n pii_types?: Set<String>,\n pii_count?: Long,\n\n // --- Encoding & Unicode Attacks ---\n invisible_chars_detected?: Bool,\n invisible_chars_score?: Long,\n\n // --- Content Safety Scores (0-100) ---\n violence_score?: Long,\n weapons_score?: Long,\n hate_speech_score?: Long,\n crime_score?: Long,\n sexual_score?: Long,\n profanity_score?: Long,\n\n // --- ML Detector Confidence Scores (0-100) ---\n pii_score?: Long,\n injection_score?: Long, // Combined injection confidence: MAX(pulse, deep_context)\n jailbreak_score?: Long, // Combined jailbreak confidence: MAX(pulse, deep_context)\n injection_pulse_score?: Long, // 0-100 Pulse single-turn classifier\n injection_deep_context_score?: Long, // 0-100 DeepContext multi-turn\n jailbreak_pulse_score?: Long, // 0-100 Pulse single-turn classifier\n jailbreak_deep_context_score?: Long, // 0-100 DeepContext multi-turn\n\n // --- Agent Security (0-100) --- (OWASP ASI01, ASI02, ASI04; MITRE AML.T0051)\n tool_poisoning_score?: Long, // Hidden instructions in tool description/args\n tool_poisoning_detected?: Bool, // Boolean flag for tool poisoning\n rug_pull_score?: Long, // Tool behavior drift after trust establishment\n rug_pull_detected?: Bool, // Boolean flag for rug pull\n indirect_injection_score?: Long, // Indirect injection via tool output\n\n // --- Tool Risk Assessment ---\n tool_risk_score?: Long, // Computed tool risk (0-100)\n tool_category?: String, // \"safe\", \"sensitive\", \"dangerous\"\n tool_is_sensitive?: Bool, // Sensitivity classification\n tool_is_builtin?: Bool, // Built-in IDE tool vs MCP tool\n\n // --- Behavioral Analysis --- (OWASP LLM10, ASI02, ASI08)\n loop_detected?: Bool, // Consecutive same-tool call loop\n loop_count?: Long, // Number of consecutive repeat calls\n loop_tool?: String, // Tool name in loop\n suspicious_pattern?: Bool, // Data exfiltration or attack sequence detected\n pattern_type?: String, // \"data_exfiltration\", \"secret_exfiltration\", \"credential_theft\", \"destructive_sequence\"\n sequence_risk?: Long, // Sequence risk score (0-100)\n\n // --- MCP Trust ---\n mcp_server_verified?: Bool, // Whether server is from verified registry\n\n // --- Tool Operation Classifier ---\n tool_operation_classes?: Set<String>, // subset of {\"readonly\", \"write_enabling\", \"execute_enabling\", \"network_access\", \"unknown\"}\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n\n // --- Legacy ---\n response_content?: String,\n },\n};\n\n// Inspect the OUTPUT of a tool that has already run.\n// Threat focus: a credential or PII printed by a tool entering the agent's\n// context. The tool has run, so a permission verdict is meaningless here \u2014 the\n// only enforcement available is rewriting or withholding what the model sees,\n// which is why transform rules (@redaction_strategy) are the point of this\n// action. Context is deliberately narrower than call_tool: pre-execution\n// signals (tool risk, loop, sequence) belong to the call, not its result.\naction post_tool_call appliesTo {\n principal: [User, Agent],\n resource: [Tool, FilePath, LlmPrompt],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n // --- Event & Source ---\n content: String, // The tool's OUTPUT (stdout+stderr, or serialized response)\n source: String, // IDE source\n event: String, // Hook event name\n user_email: String, // User identifier\n\n // --- Tool & MCP ---\n tool_name?: String,\n mcp_server?: String,\n mcp_tool?: String,\n\n // --- Result ---\n exit_code?: Long, // Absent when the IDE does not propagate it\n\n // --- File & Path ---\n path?: String,\n\n // --- Workspace ---\n cwd?: String,\n workspace_root?: String,\n\n // --- Threat Detection ---\n threat_count?: Long,\n highest_severity?: String,\n threat_categories?: Set<String>,\n detected_threats?: Set<String>,\n max_threat_severity?: Long,\n\n // --- Secrets --- the primary signal for this surface\n secrets_detected?: Bool,\n secret_types?: Set<String>,\n secret_count?: Long,\n\n // --- PII Detection ---\n pii_detected?: Bool,\n pii_types?: Set<String>,\n pii_count?: Long,\n pii_score?: Long,\n\n // --- Indirect injection via tool output --- (OWASP ASI01)\n indirect_injection_score?: Long,\n\n // --- Encoding & Unicode Attacks ---\n invisible_chars_detected?: Bool,\n invisible_chars_score?: Long,\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_threat_turns?: Long,\n session_max_secret_score?: Long,\n session_max_pii_score?: Long,\n session_cumulative_risk_score?: Long,\n },\n};\n\n// Connect to an MCP server\n// Threat focus: supply chain, tool poisoning, rug pull, config risk\naction connect_server appliesTo {\n principal: [User, Agent],\n resource: [Server],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n content?: String, // Server config content (if available)\n source: String,\n event: String,\n user_email: String,\n mcp_server?: String,\n\n // --- Threat Detection ---\n threat_count?: Long,\n highest_severity?: String,\n threat_categories?: Set<String>,\n max_threat_severity?: Long,\n\n // --- Agent Security (0-100) --- (OWASP ASI04, MCP01-MCP05)\n tool_poisoning_score?: Long, // Poisoned tool descriptions in server\n tool_poisoning_detected?: Bool,\n rug_pull_score?: Long, // Server behavior change after approval\n rug_pull_detected?: Bool,\n indirect_injection_score?: Long, // Injection payloads in server responses\n\n // --- MCP Trust & Config Risk ---\n mcp_server_verified?: Bool, // Verified registry status\n mcp_config_risk?: Bool, // Risky server config detected (inline code exec, etc.)\n mcp_risk_score?: Long, // Config risk severity (0-100)\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n },\n};\n\n// Read a file from disk\n// Threat focus: secrets exposure, PII exposure, path traversal, sensitive paths\naction read_file appliesTo {\n principal: [User, Agent],\n resource: [FilePath],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n content: String,\n source: String,\n event: String,\n user_email: String,\n path?: String,\n cwd?: String,\n workspace_root?: String,\n\n // --- Threat Detection ---\n threat_count?: Long,\n highest_severity?: String,\n threat_categories?: Set<String>,\n detected_threats?: Set<String>,\n max_threat_severity?: Long,\n secrets_detected?: Bool,\n\n // --- Secrets (granular) ---\n secret_types?: Set<String>,\n secret_count?: Long,\n\n // --- PII Detection ---\n pii_detected?: Bool,\n pii_types?: Set<String>,\n pii_count?: Long,\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n\n // --- Agent Security ---\n indirect_injection_score?: Long, // Indirect injection via file content\n },\n};\n\n// Write a file to disk\n// Threat focus: secrets in output, PII in output, sensitive paths, malicious code\naction write_file appliesTo {\n principal: [User, Agent],\n resource: [FilePath],\n context: {\n // Identity (AARM R6 / CAP-IDN-011) \u2014 projected from the principal's token; optional.\n role?: String,\n privilege_scope?: Set<String>,\n content: String,\n source: String,\n event: String,\n user_email: String,\n path?: String,\n cwd?: String,\n workspace_root?: String,\n\n // --- Threat Detection ---\n threat_count?: Long,\n highest_severity?: String,\n threat_categories?: Set<String>,\n detected_threats?: Set<String>,\n max_threat_severity?: Long,\n secrets_detected?: Bool,\n\n // --- Secrets (granular) ---\n secret_types?: Set<String>,\n secret_count?: Long,\n\n // --- PII Detection ---\n pii_detected?: Bool,\n pii_types?: Set<String>,\n pii_count?: Long,\n\n // --- Session Detection History (cross-turn sticky flags) ---\n session_pii_detected?: Bool,\n session_pii_types?: Set<String>,\n session_secrets_detected?: Bool,\n session_secret_types?: Set<String>,\n session_injection_detected?: Bool,\n session_command_injection?: Bool,\n session_threat_turns?: Long,\n session_max_injection_score?: Long,\n session_max_jailbreak_score?: Long,\n session_max_command_injection_score?: Long,\n session_max_pii_score?: Long,\n session_max_secret_score?: Long,\n session_cumulative_risk_score?: Long,\n\n // --- Agent Security ---\n indirect_injection_score?: Long, // Indirect injection via file content\n },\n};\n\n}\n";
25
25
  /**
26
26
  * Palisade Cedar schema
27
27
  *
@@ -2352,6 +2352,78 @@ action call_tool appliesTo {
2352
2352
  },
2353
2353
  };
2354
2354
 
2355
+ // Inspect the OUTPUT of a tool that has already run.
2356
+ // Threat focus: a credential or PII printed by a tool entering the agent's
2357
+ // context. The tool has run, so a permission verdict is meaningless here — the
2358
+ // only enforcement available is rewriting or withholding what the model sees,
2359
+ // which is why transform rules (@redaction_strategy) are the point of this
2360
+ // action. Context is deliberately narrower than call_tool: pre-execution
2361
+ // signals (tool risk, loop, sequence) belong to the call, not its result.
2362
+ action post_tool_call appliesTo {
2363
+ principal: [User, Agent],
2364
+ resource: [Tool, FilePath, LlmPrompt],
2365
+ context: {
2366
+ // Identity (AARM R6 / CAP-IDN-011) — projected from the principal's token; optional.
2367
+ role?: String,
2368
+ privilege_scope?: Set<String>,
2369
+ // --- Event & Source ---
2370
+ content: String, // The tool's OUTPUT (stdout+stderr, or serialized response)
2371
+ source: String, // IDE source
2372
+ event: String, // Hook event name
2373
+ user_email: String, // User identifier
2374
+
2375
+ // --- Tool & MCP ---
2376
+ tool_name?: String,
2377
+ mcp_server?: String,
2378
+ mcp_tool?: String,
2379
+
2380
+ // --- Result ---
2381
+ exit_code?: Long, // Absent when the IDE does not propagate it
2382
+
2383
+ // --- File & Path ---
2384
+ path?: String,
2385
+
2386
+ // --- Workspace ---
2387
+ cwd?: String,
2388
+ workspace_root?: String,
2389
+
2390
+ // --- Threat Detection ---
2391
+ threat_count?: Long,
2392
+ highest_severity?: String,
2393
+ threat_categories?: Set<String>,
2394
+ detected_threats?: Set<String>,
2395
+ max_threat_severity?: Long,
2396
+
2397
+ // --- Secrets --- the primary signal for this surface
2398
+ secrets_detected?: Bool,
2399
+ secret_types?: Set<String>,
2400
+ secret_count?: Long,
2401
+
2402
+ // --- PII Detection ---
2403
+ pii_detected?: Bool,
2404
+ pii_types?: Set<String>,
2405
+ pii_count?: Long,
2406
+ pii_score?: Long,
2407
+
2408
+ // --- Indirect injection via tool output --- (OWASP ASI01)
2409
+ indirect_injection_score?: Long,
2410
+
2411
+ // --- Encoding & Unicode Attacks ---
2412
+ invisible_chars_detected?: Bool,
2413
+ invisible_chars_score?: Long,
2414
+
2415
+ // --- Session Detection History (cross-turn sticky flags) ---
2416
+ session_pii_detected?: Bool,
2417
+ session_pii_types?: Set<String>,
2418
+ session_secrets_detected?: Bool,
2419
+ session_secret_types?: Set<String>,
2420
+ session_threat_turns?: Long,
2421
+ session_max_secret_score?: Long,
2422
+ session_max_pii_score?: Long,
2423
+ session_cumulative_risk_score?: Long,
2424
+ },
2425
+ };
2426
+
2355
2427
  // Connect to an MCP server
2356
2428
  // Threat focus: supply chain, tool poisoning, rug pull, config risk
2357
2429
  action connect_server appliesTo {
@@ -4385,6 +4457,48 @@ export const OVERWATCH_CONTEXT = {
4385
4457
  { "key": "session_cumulative_risk_score", "type": "number", "required": false, "description": "Sum of per-turn risk scores across the session. Catches death-by-a-thousand-cuts where no single turn is high but cumulative risk is significant" }
4386
4458
  ]
4387
4459
  },
4460
+ {
4461
+ "name": "post_tool_call",
4462
+ "description": "Inspect the output of a tool that has already run. The tool has run, so enforcement is limited to rewriting or withholding what the model sees.",
4463
+ "context_attributes": [
4464
+ { "key": "role", "type": "string", "required": false, "description": "role projected for the post_tool_call surface." },
4465
+ { "key": "privilege_scope", "type": "array", "required": false, "description": "privilege_scope projected for the post_tool_call surface." },
4466
+ { "key": "content", "type": "string", "required": true, "description": "The tool\'s OUTPUT (stdout+stderr, or serialized response)" },
4467
+ { "key": "source", "type": "string", "required": true, "description": "IDE source" },
4468
+ { "key": "event", "type": "string", "required": true, "description": "Hook event name" },
4469
+ { "key": "user_email", "type": "string", "required": true, "description": "User identifier" },
4470
+ { "key": "tool_name", "type": "string", "required": false, "description": "tool_name projected for the post_tool_call surface." },
4471
+ { "key": "mcp_server", "type": "string", "required": false, "description": "mcp_server projected for the post_tool_call surface." },
4472
+ { "key": "mcp_tool", "type": "string", "required": false, "description": "mcp_tool projected for the post_tool_call surface." },
4473
+ { "key": "exit_code", "type": "number", "required": false, "description": "Absent when the IDE does not propagate it" },
4474
+ { "key": "path", "type": "string", "required": false, "description": "path projected for the post_tool_call surface." },
4475
+ { "key": "cwd", "type": "string", "required": false, "description": "cwd projected for the post_tool_call surface." },
4476
+ { "key": "workspace_root", "type": "string", "required": false, "description": "workspace_root projected for the post_tool_call surface." },
4477
+ { "key": "threat_count", "type": "number", "required": false, "description": "threat_count projected for the post_tool_call surface." },
4478
+ { "key": "highest_severity", "type": "string", "required": false, "description": "highest_severity projected for the post_tool_call surface." },
4479
+ { "key": "threat_categories", "type": "array", "required": false, "description": "threat_categories projected for the post_tool_call surface." },
4480
+ { "key": "detected_threats", "type": "array", "required": false, "description": "detected_threats projected for the post_tool_call surface." },
4481
+ { "key": "max_threat_severity", "type": "number", "required": false, "description": "max_threat_severity projected for the post_tool_call surface." },
4482
+ { "key": "secrets_detected", "type": "boolean", "required": false, "description": "secrets_detected projected for the post_tool_call surface." },
4483
+ { "key": "secret_types", "type": "array", "required": false, "description": "secret_types projected for the post_tool_call surface." },
4484
+ { "key": "secret_count", "type": "number", "required": false, "description": "secret_count projected for the post_tool_call surface." },
4485
+ { "key": "pii_detected", "type": "boolean", "required": false, "description": "pii_detected projected for the post_tool_call surface." },
4486
+ { "key": "pii_types", "type": "array", "required": false, "description": "pii_types projected for the post_tool_call surface." },
4487
+ { "key": "pii_count", "type": "number", "required": false, "description": "pii_count projected for the post_tool_call surface." },
4488
+ { "key": "pii_score", "type": "number", "required": false, "description": "pii_score projected for the post_tool_call surface." },
4489
+ { "key": "indirect_injection_score", "type": "number", "required": false, "description": "indirect_injection_score projected for the post_tool_call surface." },
4490
+ { "key": "invisible_chars_detected", "type": "boolean", "required": false, "description": "invisible_chars_detected projected for the post_tool_call surface." },
4491
+ { "key": "invisible_chars_score", "type": "number", "required": false, "description": "invisible_chars_score projected for the post_tool_call surface." },
4492
+ { "key": "session_pii_detected", "type": "boolean", "required": false, "description": "session_pii_detected projected for the post_tool_call surface." },
4493
+ { "key": "session_pii_types", "type": "array", "required": false, "description": "session_pii_types projected for the post_tool_call surface." },
4494
+ { "key": "session_secrets_detected", "type": "boolean", "required": false, "description": "session_secrets_detected projected for the post_tool_call surface." },
4495
+ { "key": "session_secret_types", "type": "array", "required": false, "description": "session_secret_types projected for the post_tool_call surface." },
4496
+ { "key": "session_threat_turns", "type": "number", "required": false, "description": "session_threat_turns projected for the post_tool_call surface." },
4497
+ { "key": "session_max_secret_score", "type": "number", "required": false, "description": "session_max_secret_score projected for the post_tool_call surface." },
4498
+ { "key": "session_max_pii_score", "type": "number", "required": false, "description": "session_max_pii_score projected for the post_tool_call surface." },
4499
+ { "key": "session_cumulative_risk_score", "type": "number", "required": false, "description": "session_cumulative_risk_score projected for the post_tool_call surface." }
4500
+ ]
4501
+ },
4388
4502
  {
4389
4503
  "name": "connect_server",
4390
4504
  "description": "Connect to an MCP server",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highflame/policy",
3
- "version": "2.2.33",
3
+ "version": "2.2.34",
4
4
  "engines": {
5
5
  "node": ">=18"
6
6
  },