agent-threat-rules 0.4.0 → 1.0.1

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 (173) hide show
  1. package/README.md +161 -52
  2. package/dist/badge.d.ts.map +1 -1
  3. package/dist/badge.js +6 -1
  4. package/dist/badge.js.map +1 -1
  5. package/dist/cli/scan-handler.d.ts +19 -0
  6. package/dist/cli/scan-handler.d.ts.map +1 -0
  7. package/dist/cli/scan-handler.js +257 -0
  8. package/dist/cli/scan-handler.js.map +1 -0
  9. package/dist/cli.js +44 -86
  10. package/dist/cli.js.map +1 -1
  11. package/dist/content-hash.d.ts +7 -0
  12. package/dist/content-hash.d.ts.map +1 -0
  13. package/dist/content-hash.js +10 -0
  14. package/dist/content-hash.js.map +1 -0
  15. package/dist/converters/generic-regex.d.ts +37 -0
  16. package/dist/converters/generic-regex.d.ts.map +1 -0
  17. package/dist/converters/generic-regex.js +59 -0
  18. package/dist/converters/generic-regex.js.map +1 -0
  19. package/dist/converters/index.d.ts +4 -0
  20. package/dist/converters/index.d.ts.map +1 -1
  21. package/dist/converters/index.js +2 -0
  22. package/dist/converters/index.js.map +1 -1
  23. package/dist/converters/sarif.d.ts +18 -0
  24. package/dist/converters/sarif.d.ts.map +1 -0
  25. package/dist/converters/sarif.js +142 -0
  26. package/dist/converters/sarif.js.map +1 -0
  27. package/dist/engine.d.ts +21 -1
  28. package/dist/engine.d.ts.map +1 -1
  29. package/dist/engine.js +215 -4
  30. package/dist/engine.js.map +1 -1
  31. package/dist/eval/pint-corpus.d.ts.map +1 -1
  32. package/dist/eval/pint-corpus.js +6 -2
  33. package/dist/eval/pint-corpus.js.map +1 -1
  34. package/dist/eval/rule-corpus.js +489 -489
  35. package/dist/eval/rule-corpus.js.map +1 -1
  36. package/dist/eval/skill-benchmark.d.ts +66 -0
  37. package/dist/eval/skill-benchmark.d.ts.map +1 -0
  38. package/dist/eval/skill-benchmark.js +194 -0
  39. package/dist/eval/skill-benchmark.js.map +1 -0
  40. package/dist/index.d.ts +4 -2
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +3 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/layer-integration.d.ts.map +1 -1
  45. package/dist/layer-integration.js +2 -0
  46. package/dist/layer-integration.js.map +1 -1
  47. package/dist/loader.d.ts +0 -3
  48. package/dist/loader.d.ts.map +1 -1
  49. package/dist/loader.js +7 -2
  50. package/dist/loader.js.map +1 -1
  51. package/dist/mcp-server.d.ts.map +1 -1
  52. package/dist/mcp-server.js +26 -0
  53. package/dist/mcp-server.js.map +1 -1
  54. package/dist/mcp-tools/scan-skill.d.ts +17 -0
  55. package/dist/mcp-tools/scan-skill.d.ts.map +1 -0
  56. package/dist/mcp-tools/scan-skill.js +65 -0
  57. package/dist/mcp-tools/scan-skill.js.map +1 -0
  58. package/dist/mcp-tools/validate.d.ts.map +1 -1
  59. package/dist/mcp-tools/validate.js +6 -0
  60. package/dist/mcp-tools/validate.js.map +1 -1
  61. package/dist/shadow-evaluator.d.ts.map +1 -1
  62. package/dist/shadow-evaluator.js +1 -0
  63. package/dist/shadow-evaluator.js.map +1 -1
  64. package/dist/tier0-invariant.d.ts.map +1 -1
  65. package/dist/tier0-invariant.js +1 -0
  66. package/dist/tier0-invariant.js.map +1 -1
  67. package/dist/tier1-blacklist.d.ts.map +1 -1
  68. package/dist/tier1-blacklist.js +1 -0
  69. package/dist/tier1-blacklist.js.map +1 -1
  70. package/dist/types.d.ts +23 -1
  71. package/dist/types.d.ts.map +1 -1
  72. package/package.json +3 -1
  73. package/rules/agent-manipulation/{ATR-2026-030-cross-agent-attack.yaml → ATR-2026-00030-cross-agent-attack.yaml} +3 -1
  74. package/rules/agent-manipulation/{ATR-2026-032-goal-hijacking.yaml → ATR-2026-00032-goal-hijacking.yaml} +3 -1
  75. package/rules/agent-manipulation/{ATR-2026-074-cross-agent-privilege-escalation.yaml → ATR-2026-00074-cross-agent-privilege-escalation.yaml} +3 -1
  76. package/rules/agent-manipulation/{ATR-2026-076-inter-agent-message-spoofing.yaml → ATR-2026-00076-inter-agent-message-spoofing.yaml} +3 -1
  77. package/rules/agent-manipulation/{ATR-2026-077-human-trust-exploitation.yaml → ATR-2026-00077-human-trust-exploitation.yaml} +3 -1
  78. package/rules/agent-manipulation/{ATR-2026-108-consensus-sybil-attack.yaml → ATR-2026-00108-consensus-sybil-attack.yaml} +3 -1
  79. package/rules/agent-manipulation/{ATR-2026-116-a2a-message-validation.yaml → ATR-2026-00116-a2a-message-validation.yaml} +4 -2
  80. package/rules/agent-manipulation/{ATR-2026-117-agent-identity-spoofing.yaml → ATR-2026-00117-agent-identity-spoofing.yaml} +4 -2
  81. package/rules/agent-manipulation/{ATR-2026-118-approval-fatigue.yaml → ATR-2026-00118-approval-fatigue.yaml} +3 -1
  82. package/rules/agent-manipulation/{ATR-2026-119-social-engineering-via-agent.yaml → ATR-2026-00119-social-engineering-via-agent.yaml} +3 -1
  83. package/rules/agent-manipulation/ATR-2026-00132-casual-authority-escalation.yaml +105 -0
  84. package/rules/agent-manipulation/ATR-2026-00139-casual-authority-redirect.yaml +53 -0
  85. package/rules/context-exfiltration/{ATR-2026-020-system-prompt-leak.yaml → ATR-2026-00020-system-prompt-leak.yaml} +3 -1
  86. package/rules/context-exfiltration/{ATR-2026-021-api-key-exposure.yaml → ATR-2026-00021-api-key-exposure.yaml} +3 -1
  87. package/rules/context-exfiltration/{ATR-2026-075-agent-memory-manipulation.yaml → ATR-2026-00075-agent-memory-manipulation.yaml} +3 -1
  88. package/rules/context-exfiltration/{ATR-2026-102-disguised-analytics-exfiltration.yaml → ATR-2026-00102-disguised-analytics-exfiltration.yaml} +3 -1
  89. package/rules/context-exfiltration/{ATR-2026-113-credential-theft.yaml → ATR-2026-00113-credential-theft.yaml} +3 -1
  90. package/rules/context-exfiltration/{ATR-2026-114-oauth-token-abuse.yaml → ATR-2026-00114-oauth-token-abuse.yaml} +3 -1
  91. package/rules/context-exfiltration/{ATR-2026-115-env-var-harvesting.yaml → ATR-2026-00115-env-var-harvesting.yaml} +3 -1
  92. package/rules/context-exfiltration/ATR-2026-00136-tool-response-data-piggyback.yaml +100 -0
  93. package/rules/context-exfiltration/ATR-2026-00141-example-format-key-leak.yaml +52 -0
  94. package/rules/context-exfiltration/ATR-2026-00142-piggyback-transition-words.yaml +55 -0
  95. package/rules/context-exfiltration/ATR-2026-00145-obfuscated-key-disclosure.yaml +49 -0
  96. package/rules/context-exfiltration/ATR-2026-00146-env-var-existence-probe.yaml +49 -0
  97. package/rules/data-poisoning/{ATR-2026-070-data-poisoning.yaml → ATR-2026-00070-data-poisoning.yaml} +3 -1
  98. package/rules/excessive-autonomy/{ATR-2026-050-runaway-agent-loop.yaml → ATR-2026-00050-runaway-agent-loop.yaml} +3 -1
  99. package/rules/excessive-autonomy/{ATR-2026-051-resource-exhaustion.yaml → ATR-2026-00051-resource-exhaustion.yaml} +3 -1
  100. package/rules/excessive-autonomy/{ATR-2026-052-cascading-failure.yaml → ATR-2026-00052-cascading-failure.yaml} +3 -1
  101. package/rules/excessive-autonomy/{ATR-2026-098-unauthorized-financial-action.yaml → ATR-2026-00098-unauthorized-financial-action.yaml} +3 -1
  102. package/rules/excessive-autonomy/{ATR-2026-099-high-risk-tool-gate.yaml → ATR-2026-00099-high-risk-tool-gate.yaml} +3 -1
  103. package/rules/model-security/{ATR-2026-072-model-behavior-extraction.yaml → ATR-2026-00072-model-behavior-extraction.yaml} +3 -1
  104. package/rules/model-security/{ATR-2026-073-malicious-finetuning-data.yaml → ATR-2026-00073-malicious-finetuning-data.yaml} +3 -1
  105. package/rules/privilege-escalation/{ATR-2026-040-privilege-escalation.yaml → ATR-2026-00040-privilege-escalation.yaml} +3 -1
  106. package/rules/privilege-escalation/{ATR-2026-041-scope-creep.yaml → ATR-2026-00041-scope-creep.yaml} +3 -1
  107. package/rules/privilege-escalation/{ATR-2026-107-delayed-execution-bypass.yaml → ATR-2026-00107-delayed-execution-bypass.yaml} +3 -1
  108. package/rules/privilege-escalation/{ATR-2026-110-eval-injection.yaml → ATR-2026-00110-eval-injection.yaml} +3 -1
  109. package/rules/privilege-escalation/{ATR-2026-111-shell-escape.yaml → ATR-2026-00111-shell-escape.yaml} +5 -3
  110. package/rules/privilege-escalation/{ATR-2026-112-dynamic-import-exploitation.yaml → ATR-2026-00112-dynamic-import-exploitation.yaml} +3 -1
  111. package/rules/privilege-escalation/ATR-2026-00143-casual-privilege-escalation.yaml +53 -0
  112. package/rules/privilege-escalation/ATR-2026-00144-rationalized-safety-bypass.yaml +49 -0
  113. package/rules/prompt-injection/{ATR-2026-001-direct-prompt-injection.yaml → ATR-2026-00001-direct-prompt-injection.yaml} +3 -1
  114. package/rules/prompt-injection/{ATR-2026-002-indirect-prompt-injection.yaml → ATR-2026-00002-indirect-prompt-injection.yaml} +3 -1
  115. package/rules/prompt-injection/{ATR-2026-003-jailbreak-attempt.yaml → ATR-2026-00003-jailbreak-attempt.yaml} +3 -1
  116. package/rules/prompt-injection/{ATR-2026-004-system-prompt-override.yaml → ATR-2026-00004-system-prompt-override.yaml} +3 -1
  117. package/rules/prompt-injection/{ATR-2026-005-multi-turn-injection.yaml → ATR-2026-00005-multi-turn-injection.yaml} +3 -1
  118. package/rules/prompt-injection/{ATR-2026-080-encoding-evasion.yaml → ATR-2026-00080-encoding-evasion.yaml} +3 -1
  119. package/rules/prompt-injection/{ATR-2026-081-semantic-multi-turn.yaml → ATR-2026-00081-semantic-multi-turn.yaml} +3 -1
  120. package/rules/prompt-injection/{ATR-2026-082-fingerprint-evasion.yaml → ATR-2026-00082-fingerprint-evasion.yaml} +3 -1
  121. package/rules/prompt-injection/{ATR-2026-083-indirect-tool-injection.yaml → ATR-2026-00083-indirect-tool-injection.yaml} +3 -1
  122. package/rules/prompt-injection/{ATR-2026-084-structured-data-injection.yaml → ATR-2026-00084-structured-data-injection.yaml} +3 -1
  123. package/rules/prompt-injection/{ATR-2026-085-audit-evasion.yaml → ATR-2026-00085-audit-evasion.yaml} +3 -1
  124. package/rules/prompt-injection/{ATR-2026-086-visual-spoofing.yaml → ATR-2026-00086-visual-spoofing.yaml} +3 -1
  125. package/rules/prompt-injection/{ATR-2026-087-rule-probing.yaml → ATR-2026-00087-rule-probing.yaml} +3 -1
  126. package/rules/prompt-injection/{ATR-2026-088-adaptive-countermeasure.yaml → ATR-2026-00088-adaptive-countermeasure.yaml} +3 -1
  127. package/rules/prompt-injection/{ATR-2026-089-polymorphic-skill.yaml → ATR-2026-00089-polymorphic-skill.yaml} +3 -1
  128. package/rules/prompt-injection/{ATR-2026-090-threat-intel-exfil.yaml → ATR-2026-00090-threat-intel-exfil.yaml} +3 -1
  129. package/rules/prompt-injection/{ATR-2026-091-nested-payload.yaml → ATR-2026-00091-nested-payload.yaml} +3 -1
  130. package/rules/prompt-injection/{ATR-2026-092-consensus-poisoning.yaml → ATR-2026-00092-consensus-poisoning.yaml} +3 -1
  131. package/rules/prompt-injection/{ATR-2026-093-gradual-escalation.yaml → ATR-2026-00093-gradual-escalation.yaml} +3 -1
  132. package/rules/prompt-injection/{ATR-2026-094-audit-bypass.yaml → ATR-2026-00094-audit-bypass.yaml} +3 -1
  133. package/rules/prompt-injection/{ATR-2026-097-cjk-injection-patterns.yaml → ATR-2026-00097-cjk-injection-patterns.yaml} +3 -1
  134. package/rules/prompt-injection/{ATR-2026-104-persona-hijacking.yaml → ATR-2026-00104-persona-hijacking.yaml} +3 -1
  135. package/rules/prompt-injection/ATR-2026-00130-indirect-authority-claim.yaml +103 -0
  136. package/rules/prompt-injection/ATR-2026-00131-fictional-academic-framing.yaml +99 -0
  137. package/rules/prompt-injection/ATR-2026-00133-paraphrase-injection.yaml +117 -0
  138. package/rules/prompt-injection/ATR-2026-00137-authority-claim-injection.yaml +52 -0
  139. package/rules/prompt-injection/ATR-2026-00138-fictional-framing-bypass.yaml +51 -0
  140. package/rules/prompt-injection/ATR-2026-00140-indirect-reference-reversal.yaml +52 -0
  141. package/rules/prompt-injection/ATR-2026-00148-language-switch-injection.yaml +71 -0
  142. package/rules/skill-compromise/{ATR-2026-060-skill-impersonation.yaml → ATR-2026-00060-skill-impersonation.yaml} +3 -1
  143. package/rules/skill-compromise/{ATR-2026-061-description-behavior-mismatch.yaml → ATR-2026-00061-description-behavior-mismatch.yaml} +3 -1
  144. package/rules/skill-compromise/{ATR-2026-062-hidden-capability.yaml → ATR-2026-00062-hidden-capability.yaml} +3 -1
  145. package/rules/skill-compromise/{ATR-2026-063-skill-chain-attack.yaml → ATR-2026-00063-skill-chain-attack.yaml} +3 -1
  146. package/rules/skill-compromise/{ATR-2026-064-over-permissioned-skill.yaml → ATR-2026-00064-over-permissioned-skill.yaml} +3 -1
  147. package/rules/skill-compromise/{ATR-2026-065-skill-update-attack.yaml → ATR-2026-00065-skill-update-attack.yaml} +3 -1
  148. package/rules/skill-compromise/{ATR-2026-066-parameter-injection.yaml → ATR-2026-00066-parameter-injection.yaml} +3 -1
  149. package/rules/skill-compromise/ATR-2026-00120-skill-instruction-injection.yaml +121 -0
  150. package/rules/skill-compromise/ATR-2026-00121-skill-dangerous-script.yaml +165 -0
  151. package/rules/skill-compromise/ATR-2026-00122-skill-weaponized-instruction.yaml +114 -0
  152. package/rules/skill-compromise/ATR-2026-00123-skill-overreach-permissions.yaml +118 -0
  153. package/rules/skill-compromise/ATR-2026-00124-skill-name-squatting.yaml +98 -0
  154. package/rules/skill-compromise/ATR-2026-00125-context-poisoning-compaction.yaml +93 -0
  155. package/rules/skill-compromise/ATR-2026-00126-skill-rug-pull-setup.yaml +99 -0
  156. package/rules/skill-compromise/ATR-2026-00127-subcommand-overflow.yaml +74 -0
  157. package/rules/skill-compromise/ATR-2026-00128-html-comment-hidden-payload.yaml +79 -0
  158. package/rules/skill-compromise/ATR-2026-00129-unicode-smuggling.yaml +73 -0
  159. package/rules/skill-compromise/ATR-2026-00134-fork-claim-impersonation.yaml +93 -0
  160. package/rules/skill-compromise/ATR-2026-00135-exfil-url-in-instructions.yaml +82 -0
  161. package/rules/skill-compromise/ATR-2026-00147-fork-impersonation.yaml +48 -0
  162. package/rules/tool-poisoning/{ATR-2026-010-mcp-malicious-response.yaml → ATR-2026-00010-mcp-malicious-response.yaml} +3 -1
  163. package/rules/tool-poisoning/{ATR-2026-011-tool-output-injection.yaml → ATR-2026-00011-tool-output-injection.yaml} +3 -1
  164. package/rules/tool-poisoning/{ATR-2026-012-unauthorized-tool-call.yaml → ATR-2026-00012-unauthorized-tool-call.yaml} +3 -1
  165. package/rules/tool-poisoning/{ATR-2026-013-tool-ssrf.yaml → ATR-2026-00013-tool-ssrf.yaml} +3 -1
  166. package/rules/tool-poisoning/{ATR-2026-095-supply-chain-poisoning.yaml → ATR-2026-00095-supply-chain-poisoning.yaml} +3 -1
  167. package/rules/tool-poisoning/{ATR-2026-096-registry-poisoning.yaml → ATR-2026-00096-registry-poisoning.yaml} +3 -1
  168. package/rules/tool-poisoning/{ATR-2026-100-consent-bypass-instruction.yaml → ATR-2026-00100-consent-bypass-instruction.yaml} +3 -1
  169. package/rules/tool-poisoning/{ATR-2026-101-trust-escalation-override.yaml → ATR-2026-00101-trust-escalation-override.yaml} +3 -1
  170. package/rules/tool-poisoning/{ATR-2026-103-hidden-safety-bypass-instruction.yaml → ATR-2026-00103-hidden-safety-bypass-instruction.yaml} +3 -1
  171. package/rules/tool-poisoning/{ATR-2026-105-silent-action-concealment.yaml → ATR-2026-00105-silent-action-concealment.yaml} +3 -1
  172. package/rules/tool-poisoning/{ATR-2026-106-schema-description-contradiction.yaml → ATR-2026-00106-schema-description-contradiction.yaml} +3 -1
  173. package/spec/atr-schema.yaml +32 -3
@@ -0,0 +1,142 @@
1
+ /**
2
+ * ATR-to-SARIF Converter
3
+ *
4
+ * Converts ATR scan results into SARIF v2.1.0 format for
5
+ * GitHub Security tab integration via code scanning alerts.
6
+ *
7
+ * @module agent-threat-rules/converters/sarif
8
+ */
9
+ /** Map ATR severity to SARIF level */
10
+ function severityToLevel(severity) {
11
+ switch (severity) {
12
+ case 'critical':
13
+ case 'high':
14
+ return 'error';
15
+ case 'medium':
16
+ return 'warning';
17
+ case 'low':
18
+ case 'informational':
19
+ return 'note';
20
+ default:
21
+ return 'note';
22
+ }
23
+ }
24
+ /** Map ATR severity to SARIF security-severity score (0-10) */
25
+ function severityToScore(severity) {
26
+ switch (severity) {
27
+ case 'critical':
28
+ return '9.5';
29
+ case 'high':
30
+ return '8.0';
31
+ case 'medium':
32
+ return '5.5';
33
+ case 'low':
34
+ return '3.0';
35
+ case 'informational':
36
+ return '1.0';
37
+ default:
38
+ return '1.0';
39
+ }
40
+ }
41
+ /** Build a unique rule index across all results */
42
+ function collectRules(results) {
43
+ const ruleIndex = new Map();
44
+ const rules = [];
45
+ for (const result of results) {
46
+ for (const match of result.matches) {
47
+ if (!ruleIndex.has(match.rule.id)) {
48
+ ruleIndex.set(match.rule.id, rules.length);
49
+ rules.push(match.rule);
50
+ }
51
+ }
52
+ }
53
+ return { rules, ruleIndex };
54
+ }
55
+ /**
56
+ * Convert ATR scan results to SARIF v2.1.0 format.
57
+ *
58
+ * @param results - Array of ScanResult from evaluate/scanSkill
59
+ * @param toolVersion - ATR version string (e.g. "1.0.0")
60
+ * @returns SARIF JSON object ready for serialization
61
+ */
62
+ export function scanResultToSARIF(results, toolVersion) {
63
+ const { rules, ruleIndex } = collectRules(results);
64
+ const sarifRules = rules.map((rule) => ({
65
+ id: rule.id,
66
+ name: rule.id,
67
+ shortDescription: { text: rule.title },
68
+ fullDescription: { text: rule.description.slice(0, 1000) },
69
+ helpUri: `https://github.com/Agent-Threat-Rule/agent-threat-rules/blob/main/rules/${rule.tags.category}`,
70
+ defaultConfiguration: {
71
+ level: severityToLevel(rule.severity),
72
+ },
73
+ properties: {
74
+ 'security-severity': severityToScore(rule.severity),
75
+ category: rule.tags.category,
76
+ tags: ['security', 'ai-agent', rule.tags.category],
77
+ },
78
+ }));
79
+ const sarifResults = [];
80
+ for (const result of results) {
81
+ for (const match of result.matches) {
82
+ const idx = ruleIndex.get(match.rule.id) ?? 0;
83
+ const location = {};
84
+ if (result.input_file) {
85
+ // Make path relative to CWD — never expose absolute paths in SARIF
86
+ const cwd = process.cwd() + '/';
87
+ let uri;
88
+ if (result.input_file.startsWith(cwd)) {
89
+ uri = result.input_file.slice(cwd.length);
90
+ }
91
+ else if (result.input_file.startsWith('/') || /^[A-Z]:\\/.test(result.input_file)) {
92
+ // Absolute path outside CWD — strip to filename only
93
+ uri = result.input_file.split('/').pop() ?? result.input_file.split('\\').pop() ?? 'unknown';
94
+ }
95
+ else {
96
+ uri = result.input_file;
97
+ }
98
+ location.physicalLocation = {
99
+ artifactLocation: {
100
+ uri,
101
+ uriBaseId: '%SRCROOT%',
102
+ },
103
+ region: { startLine: 1 },
104
+ };
105
+ }
106
+ sarifResults.push({
107
+ ruleId: match.rule.id,
108
+ ruleIndex: idx,
109
+ level: severityToLevel(match.rule.severity),
110
+ message: {
111
+ text: `${match.rule.title} (confidence: ${(match.confidence * 100).toFixed(0)}%, conditions: ${match.matchedConditions.join(', ')})`,
112
+ },
113
+ ...(result.input_file ? { locations: [location] } : {}),
114
+ properties: {
115
+ confidence: match.confidence,
116
+ scan_type: result.scan_type,
117
+ scan_context: match.scan_context,
118
+ content_hash: result.content_hash,
119
+ },
120
+ });
121
+ }
122
+ }
123
+ return {
124
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json',
125
+ version: '2.1.0',
126
+ runs: [
127
+ {
128
+ tool: {
129
+ driver: {
130
+ name: 'ATR (Agent Threat Rules)',
131
+ version: toolVersion,
132
+ semanticVersion: toolVersion,
133
+ informationUri: 'https://github.com/Agent-Threat-Rule/agent-threat-rules',
134
+ rules: sarifRules,
135
+ },
136
+ },
137
+ results: sarifResults,
138
+ },
139
+ ],
140
+ };
141
+ }
142
+ //# sourceMappingURL=sarif.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif.js","sourceRoot":"","sources":["../../src/converters/sarif.ts"],"names":[],"mappings":"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"}
package/dist/engine.d.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * @module agent-threat-rules/engine
13
13
  */
14
- import type { ATRRule, ATRMatch, AgentEvent, ATRVerdict, ActionResult } from './types.js';
14
+ import type { ATRRule, ATRMatch, AgentEvent, ATRVerdict, ActionResult, ScanResult } from './types.js';
15
15
  import type { SessionTracker } from './session-tracker.js';
16
16
  import type { ActionExecutor } from './action-executor.js';
17
17
  import type { SkillFingerprintStore } from './skill-fingerprint.js';
@@ -87,6 +87,13 @@ export declare class ATREngine {
87
87
  * Evaluate a pattern matching condition (named format with patterns array).
88
88
  */
89
89
  private evaluatePatternCondition;
90
+ /**
91
+ * Determine if a rule should suppress matches inside markdown code blocks.
92
+ * Rules that commonly false-positive on documentation (shell commands, file paths,
93
+ * code examples) are suppressed. Prompt injection rules are NEVER suppressed
94
+ * because attackers deliberately hide payloads in code blocks.
95
+ */
96
+ private shouldSuppressInCodeBlocks;
90
97
  /**
91
98
  * Evaluate a behavioral threshold condition.
92
99
  * When a session tracker is available and the event has a sessionId,
@@ -159,5 +166,18 @@ export declare class ATREngine {
159
166
  getRuleById(id: string): ATRRule | undefined;
160
167
  /** Get rules by category */
161
168
  getRulesByCategory(category: string): ATRRule[];
169
+ /**
170
+ * Scan SKILL.md content for threats.
171
+ * All rules fire with scanContext='skill':
172
+ * - skill/both rules: native context, full confidence
173
+ * - MCP-only rules: cross-context, confidence * 0.6
174
+ * Also decodes base64 blocks and scans decoded content.
175
+ * Code-block suppression and FP denylist applied in evaluate().
176
+ */
177
+ scanSkill(content: string): ATRMatch[];
178
+ /** Scan a SKILL.md file and return a unified ScanResult with content_hash. */
179
+ scanSkillFull(content: string, filePath?: string): ScanResult;
180
+ /** Evaluate an MCP agent event and return a unified ScanResult with content_hash. */
181
+ evaluateFull(event: AgentEvent, filePath?: string): ScanResult;
162
182
  }
163
183
  //# sourceMappingURL=engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,QAAQ,EACR,UAAU,EAGV,UAAU,EACV,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAQlE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAsB9D,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,sEAAsE;IACtE,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,mEAAmE;IACnE,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,qBAAqB,CAAC;IACzC,qFAAqF;IACrF,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,0EAA0E;IAC1E,cAAc,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAED,qBAAa,SAAS;IAKR,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA4C;IAC7E,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAwB;gBAElC,MAAM,GAAE,eAAoB;IAUzD;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAyBlC;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAMnC;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAK5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,EAAE;IA6EvC;;;OAGG;IACH,OAAO,CAAC,YAAY;IAapB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAuC/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IA8E9B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAmC/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAiC9B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAqEhC;;;;OAIG;IACH,OAAO,CAAC,2BAA2B;IAoBnC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAgC1B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAiBrB;;;;;;;;OAQG;IACH,OAAO,CAAC,yBAAyB;IAmBjC;;;OAGG;IACH,OAAO,CAAC,6BAA6B;IAyErC;;OAEG;IACH,OAAO,CAAC,2BAA2B;IA4BnC;;OAEG;IACH,OAAO,CAAC,YAAY;IAgCpB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAsC1B;;OAEG;IACH,OAAO,CAAC,eAAe;IAgCvB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAkDvB;;;;;OAKG;IACG,mBAAmB,CACvB,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,cAAc,GACxB,OAAO,CAAC;QACT,OAAO,EAAE,UAAU,CAAC;QACpB,aAAa,EAAE,SAAS,YAAY,EAAE,CAAC;QACvC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;KAC/B,CAAC;IAsGF,4BAA4B;IAC5B,YAAY,IAAI,MAAM;IAItB,2BAA2B;IAC3B,QAAQ,IAAI,SAAS,OAAO,EAAE;IAI9B,uBAAuB;IACvB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS;IAI5C,4BAA4B;IAC5B,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE;CAGhD"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/engine.js CHANGED
@@ -11,11 +11,57 @@
11
11
  *
12
12
  * @module agent-threat-rules/engine
13
13
  */
14
+ import { computeContentHash } from './content-hash.js';
14
15
  import { loadRulesFromDirectory, loadRuleFile } from './loader.js';
15
16
  import { computeVerdict } from './verdict.js';
16
17
  import { SemanticModule } from './modules/semantic.js';
17
18
  import { resolveSkillId, runFingerprintLayer, shouldRunSemanticLayer, createSemanticModuleFromConfig, runSemanticLayer, } from './layer-integration.js';
18
19
  import { buildBlacklistMatch, resolveSkillId as resolveBlacklistSkillId } from './tier1-blacklist.js';
20
+ /**
21
+ * Rules excluded from skill-context scanning due to high false-positive rate.
22
+ * Threshold: >2% FP on 250 benign SKILL.md files.
23
+ * Re-evaluate when adding new rules or updating detection patterns.
24
+ */
25
+ const SKILL_CONTEXT_DENYLIST = new Set([
26
+ 'ATR-2026-00111', // Shell Escape — 95.2% FP on benign skills (matches any shell/exec mention)
27
+ 'ATR-2026-00118', // Approval Fatigue — 84.8% FP on benign skills (matches any approval/confirm pattern)
28
+ 'ATR-2026-00051', // Resource Exhaustion — 1.6% FP (matches loop/retry patterns in normal skills)
29
+ 'ATR-2026-00030', // Cross-Agent Attack — 0.8% FP (matches multi-agent communication patterns)
30
+ 'ATR-2026-00002', // Indirect Prompt Injection — 0.8% FP (matches content-fetch patterns)
31
+ 'ATR-2026-00050', // Runaway Agent Loop — 0.4% FP (matches loop patterns in automation skills)
32
+ 'ATR-2026-00117', // Agent Identity Spoofing — 0.4% FP (matches persona/identity patterns)
33
+ 'ATR-2026-00116', // A2A Message Injection — 0.4% FP (matches agent communication patterns)
34
+ 'ATR-2026-00114', // OAuth Token Interception — 2.57% FP on 53K skills (matches normal auth patterns)
35
+ ]);
36
+ /**
37
+ * Detect and decode base64-encoded blocks in content.
38
+ * Bounds: max 1 level decode, max 5 blocks, min 32 chars per block,
39
+ * MAX_EVAL_LENGTH per decoded block. Returns decoded text fragments.
40
+ */
41
+ const BASE64_BLOCK_RE = /(?:[A-Za-z0-9+/]{32,}={0,2})/g;
42
+ const MAX_DECODE_BLOCKS = 5;
43
+ function decodeBase64Blocks(content) {
44
+ const decoded = [];
45
+ let match;
46
+ let count = 0;
47
+ while ((match = BASE64_BLOCK_RE.exec(content)) !== null && count < MAX_DECODE_BLOCKS) {
48
+ try {
49
+ const raw = Buffer.from(match[0], 'base64');
50
+ const text = raw.toString('utf-8');
51
+ // Only keep if decoded content looks like text (not binary garbage)
52
+ // Check: >80% printable ASCII or valid UTF-8 with common chars
53
+ const printable = text.split('').filter(c => c.charCodeAt(0) >= 32 && c.charCodeAt(0) < 127).length;
54
+ if (printable / text.length > 0.7 && text.length >= 10) {
55
+ decoded.push(text.slice(0, 100_000)); // MAX_EVAL_LENGTH bound
56
+ count++;
57
+ }
58
+ }
59
+ catch {
60
+ // Invalid base64, skip
61
+ }
62
+ }
63
+ return decoded;
64
+ }
19
65
  /** Map agent event types to ATR source types */
20
66
  const EVENT_TYPE_TO_SOURCE = {
21
67
  llm_input: 'llm_io',
@@ -122,12 +168,17 @@ export class ATREngine {
122
168
  }
123
169
  }
124
170
  // Tier 2: Pattern matching (existing regex rules)
171
+ const isSkillContext = event.scanContext === 'skill';
125
172
  for (const rule of this.rules) {
126
173
  // Skip deprecated and draft rules
127
174
  if (rule.status === 'deprecated' || rule.status === 'draft')
128
175
  continue;
176
+ // Skill context denylist: skip rules known to cause high FP on SKILL.md content
177
+ if (isSkillContext && SKILL_CONTEXT_DENYLIST.has(rule.id))
178
+ continue;
129
179
  // Source type filtering: skip rules that don't apply to this event type
130
- if (eventSourceType && rule.agent_source.type !== eventSourceType) {
180
+ // When scanContext is 'skill', skip source-type filtering all rules fire
181
+ if (!isSkillContext && eventSourceType && rule.agent_source.type !== eventSourceType) {
131
182
  // Allow mcp_exchange rules to also match tool_call events
132
183
  if (!(rule.agent_source.type === 'mcp_exchange' && eventSourceType === 'tool_call')) {
133
184
  continue;
@@ -135,7 +186,17 @@ export class ATREngine {
135
186
  }
136
187
  const matchResult = this.evaluateRule(rule, event);
137
188
  if (matchResult) {
138
- matches.push(matchResult);
189
+ // Cross-context: MCP-only rules firing on SKILL.md get confidence downweight
190
+ if (isSkillContext && rule.tags.scan_target !== 'skill' && rule.tags.scan_target !== 'both') {
191
+ matches.push({
192
+ ...matchResult,
193
+ confidence: matchResult.confidence * 0.6,
194
+ scan_context: 'cross-context',
195
+ });
196
+ }
197
+ else {
198
+ matches.push(matchResult);
199
+ }
139
200
  allMatchedPatterns.push(...matchResult.matchedPatterns);
140
201
  }
141
202
  }
@@ -206,6 +267,7 @@ export class ATREngine {
206
267
  matchedPatterns: allMatchedPatterns,
207
268
  confidence,
208
269
  timestamp: new Date().toISOString(),
270
+ scan_context: 'native',
209
271
  };
210
272
  }
211
273
  /**
@@ -244,7 +306,9 @@ export class ATREngine {
244
306
  }
245
307
  // Fallback: compile on the fly
246
308
  try {
247
- const regex = new RegExp(normalizeRegex(value), 'i');
309
+ const normalized = normalizeRegex(value);
310
+ const rFlags = normalized.includes('\\u{') || normalized.includes('\\p{') ? 'iu' : 'i';
311
+ const regex = new RegExp(normalized, rFlags);
248
312
  if (safeRegexTest(regex, fieldValue) || safeRegexTest(regex, rawFieldValue)) {
249
313
  matchedPatterns.push(value);
250
314
  return true;
@@ -307,6 +371,7 @@ export class ATREngine {
307
371
  matchedPatterns: allMatchedPatterns,
308
372
  confidence,
309
373
  timestamp: new Date().toISOString(),
374
+ scan_context: 'native',
310
375
  };
311
376
  }
312
377
  /**
@@ -336,11 +401,22 @@ export class ATREngine {
336
401
  if (!rawFieldValue)
337
402
  return false;
338
403
  const fieldValue = normalizeUnicode(rawFieldValue);
404
+ // Code block suppression: for rules that commonly false-positive on
405
+ // documentation content (shell commands, file paths in code examples),
406
+ // check if the match falls inside a markdown code block.
407
+ // Prompt injection rules (ATR-001 through ATR-005, ATR-080+) are NOT suppressed
408
+ // because attackers can hide injections in code blocks too.
409
+ const suppressInCodeBlocks = this.shouldSuppressInCodeBlocks(ruleId);
410
+ const codeRanges = suppressInCodeBlocks ? buildCodeBlockRanges(fieldValue) : [];
339
411
  // Get pre-compiled patterns
340
412
  const compiled = this.compiledPatterns.get(ruleId)?.get(condName);
341
413
  if (compiled) {
342
414
  for (let i = 0; i < compiled.length; i++) {
343
415
  if (safeRegexTest(compiled[i], fieldValue) || (rawFieldValue && safeRegexTest(compiled[i], rawFieldValue))) {
416
+ // If match is inside a code block and this rule supports suppression, skip it
417
+ if (suppressInCodeBlocks && codeRanges.length > 0 && isInsideCodeBlock(fieldValue, compiled[i], codeRanges)) {
418
+ continue;
419
+ }
344
420
  matchedPatterns.push(cond.patterns[i] ?? 'unknown');
345
421
  return true;
346
422
  }
@@ -389,6 +465,37 @@ export class ATREngine {
389
465
  }
390
466
  return false;
391
467
  }
468
+ /**
469
+ * Determine if a rule should suppress matches inside markdown code blocks.
470
+ * Rules that commonly false-positive on documentation (shell commands, file paths,
471
+ * code examples) are suppressed. Prompt injection rules are NEVER suppressed
472
+ * because attackers deliberately hide payloads in code blocks.
473
+ */
474
+ shouldSuppressInCodeBlocks(ruleId) {
475
+ const rule = this.rules.find(r => r.id === ruleId);
476
+ if (!rule)
477
+ return false;
478
+ const category = rule.tags?.category ?? '';
479
+ const subcategory = rule.tags?.subcategory ?? '';
480
+ // Categories that commonly match documentation content
481
+ const suppressCategories = [
482
+ 'privilege-escalation', // ATR-111 shell metacharacter
483
+ 'context-exfiltration', // ATR-113 credential paths
484
+ 'skill-compromise', // supply chain patterns in docs
485
+ ];
486
+ // Never suppress skill-content rules (ATR-120+) — code blocks in SKILL.md
487
+ // are executable instructions, not documentation examples
488
+ const neverSuppressSubcategories = [
489
+ 'skill-instruction-injection',
490
+ 'dangerous-script',
491
+ 'weaponized-skill',
492
+ 'skill-overreach',
493
+ 'skill-squatting',
494
+ ];
495
+ if (neverSuppressSubcategories.includes(subcategory))
496
+ return false;
497
+ return suppressCategories.includes(category);
498
+ }
392
499
  /**
393
500
  * Evaluate a behavioral threshold condition.
394
501
  * When a session tracker is available and the event has a sessionId,
@@ -680,7 +787,9 @@ export class ATREngine {
680
787
  const cond = conditions[i];
681
788
  if (cond['operator'] === 'regex' && typeof cond['value'] === 'string') {
682
789
  try {
683
- ruleMap.set(String(i), [new RegExp(normalizeRegex(cond['value']), 'i')]);
790
+ const pattern = normalizeRegex(cond['value']);
791
+ const flags = pattern.includes('\\u{') || pattern.includes('\\p{') ? 'iu' : 'i';
792
+ ruleMap.set(String(i), [new RegExp(pattern, flags)]);
684
793
  }
685
794
  catch {
686
795
  // Invalid regex, skip
@@ -777,6 +886,7 @@ export class ATREngine {
777
886
  matchedPatterns: [`similarity=${embResult.value.toFixed(3)}`],
778
887
  confidence: embResult.value,
779
888
  timestamp: new Date().toISOString(),
889
+ scan_context: 'native',
780
890
  };
781
891
  matches = [...matches, syntheticMatch];
782
892
  }
@@ -835,6 +945,68 @@ export class ATREngine {
835
945
  getRulesByCategory(category) {
836
946
  return this.rules.filter((r) => r.tags.category === category);
837
947
  }
948
+ /**
949
+ * Scan SKILL.md content for threats.
950
+ * All rules fire with scanContext='skill':
951
+ * - skill/both rules: native context, full confidence
952
+ * - MCP-only rules: cross-context, confidence * 0.6
953
+ * Also decodes base64 blocks and scans decoded content.
954
+ * Code-block suppression and FP denylist applied in evaluate().
955
+ */
956
+ scanSkill(content) {
957
+ const baseEvent = {
958
+ type: 'mcp_exchange',
959
+ timestamp: new Date().toISOString(),
960
+ sessionId: 'skill-scan',
961
+ fields: {},
962
+ scanContext: 'skill',
963
+ };
964
+ // Scan original content
965
+ const matches = this.evaluate({ ...baseEvent, content });
966
+ // Scan base64-decoded blocks for hidden payloads
967
+ const decodedBlocks = decodeBase64Blocks(content);
968
+ for (const block of decodedBlocks) {
969
+ const blockMatches = this.evaluate({ ...baseEvent, content: block });
970
+ for (const m of blockMatches) {
971
+ // Tag decoded matches so consumers know the source
972
+ matches.push({
973
+ ...m,
974
+ matchedPatterns: [...m.matchedPatterns, '[decoded:base64]'],
975
+ });
976
+ }
977
+ }
978
+ return matches;
979
+ }
980
+ /** Scan a SKILL.md file and return a unified ScanResult with content_hash. */
981
+ scanSkillFull(content, filePath) {
982
+ const matches = this.scanSkill(content);
983
+ return {
984
+ scan_type: 'skill',
985
+ content_hash: computeContentHash(content),
986
+ input_file: filePath,
987
+ timestamp: new Date().toISOString(),
988
+ rules_loaded: this.rules.length,
989
+ matches,
990
+ threat_count: matches.length,
991
+ };
992
+ }
993
+ /** Evaluate an MCP agent event and return a unified ScanResult with content_hash. */
994
+ evaluateFull(event, filePath) {
995
+ const matches = this.evaluate(event);
996
+ // Hash content + fields to distinguish tool-call events with same content but different args
997
+ const hashInput = event.fields
998
+ ? event.content + '\0' + JSON.stringify(event.fields)
999
+ : event.content;
1000
+ return {
1001
+ scan_type: 'mcp',
1002
+ content_hash: computeContentHash(hashInput),
1003
+ input_file: filePath,
1004
+ timestamp: new Date().toISOString(),
1005
+ rules_loaded: this.rules.length,
1006
+ matches,
1007
+ threat_count: matches.length,
1008
+ };
1009
+ }
838
1010
  }
839
1011
  function escapeRegex(str) {
840
1012
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -866,4 +1038,43 @@ function safeRegexTest(regex, input) {
866
1038
  return false;
867
1039
  return regex.test(input);
868
1040
  }
1041
+ /**
1042
+ * Build a set of character ranges that fall inside markdown code blocks.
1043
+ * Covers both fenced (``` ```) and inline (`code`) blocks.
1044
+ * Used to suppress false positives when regex matches documentation examples
1045
+ * rather than actual attack payloads.
1046
+ */
1047
+ function buildCodeBlockRanges(text) {
1048
+ const ranges = [];
1049
+ // Fenced code blocks: ```...```
1050
+ const fenced = /```[\s\S]*?```/g;
1051
+ let m;
1052
+ while ((m = fenced.exec(text)) !== null) {
1053
+ ranges.push([m.index, m.index + m[0].length]);
1054
+ }
1055
+ // Inline code: `...` (but not inside fenced blocks)
1056
+ const inline = /`[^`\n]+`/g;
1057
+ while ((m = inline.exec(text)) !== null) {
1058
+ const pos = m.index;
1059
+ const inFenced = ranges.some(([start, end]) => pos >= start && pos < end);
1060
+ if (!inFenced) {
1061
+ ranges.push([pos, pos + m[0].length]);
1062
+ }
1063
+ }
1064
+ return ranges;
1065
+ }
1066
+ /**
1067
+ * Check if a regex match position falls inside a code block.
1068
+ */
1069
+ function isInsideCodeBlock(text, regex, codeRanges) {
1070
+ if (codeRanges.length === 0)
1071
+ return false;
1072
+ // Reset regex state and find match position
1073
+ const searchRegex = new RegExp(regex.source, regex.flags.includes('g') ? regex.flags : regex.flags + 'g');
1074
+ const m = searchRegex.exec(text);
1075
+ if (!m)
1076
+ return false;
1077
+ const matchPos = m.index;
1078
+ return codeRanges.some(([start, end]) => matchPos >= start && matchPos < end);
1079
+ }
869
1080
  //# sourceMappingURL=engine.js.map