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.
- package/README.md +161 -52
- package/dist/badge.d.ts.map +1 -1
- package/dist/badge.js +6 -1
- package/dist/badge.js.map +1 -1
- package/dist/cli/scan-handler.d.ts +19 -0
- package/dist/cli/scan-handler.d.ts.map +1 -0
- package/dist/cli/scan-handler.js +257 -0
- package/dist/cli/scan-handler.js.map +1 -0
- package/dist/cli.js +44 -86
- package/dist/cli.js.map +1 -1
- package/dist/content-hash.d.ts +7 -0
- package/dist/content-hash.d.ts.map +1 -0
- package/dist/content-hash.js +10 -0
- package/dist/content-hash.js.map +1 -0
- package/dist/converters/generic-regex.d.ts +37 -0
- package/dist/converters/generic-regex.d.ts.map +1 -0
- package/dist/converters/generic-regex.js +59 -0
- package/dist/converters/generic-regex.js.map +1 -0
- package/dist/converters/index.d.ts +4 -0
- package/dist/converters/index.d.ts.map +1 -1
- package/dist/converters/index.js +2 -0
- package/dist/converters/index.js.map +1 -1
- package/dist/converters/sarif.d.ts +18 -0
- package/dist/converters/sarif.d.ts.map +1 -0
- package/dist/converters/sarif.js +142 -0
- package/dist/converters/sarif.js.map +1 -0
- package/dist/engine.d.ts +21 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +215 -4
- package/dist/engine.js.map +1 -1
- package/dist/eval/pint-corpus.d.ts.map +1 -1
- package/dist/eval/pint-corpus.js +6 -2
- package/dist/eval/pint-corpus.js.map +1 -1
- package/dist/eval/rule-corpus.js +489 -489
- package/dist/eval/rule-corpus.js.map +1 -1
- package/dist/eval/skill-benchmark.d.ts +66 -0
- package/dist/eval/skill-benchmark.d.ts.map +1 -0
- package/dist/eval/skill-benchmark.js +194 -0
- package/dist/eval/skill-benchmark.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/layer-integration.d.ts.map +1 -1
- package/dist/layer-integration.js +2 -0
- package/dist/layer-integration.js.map +1 -1
- package/dist/loader.d.ts +0 -3
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +7 -2
- package/dist/loader.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +26 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools/scan-skill.d.ts +17 -0
- package/dist/mcp-tools/scan-skill.d.ts.map +1 -0
- package/dist/mcp-tools/scan-skill.js +65 -0
- package/dist/mcp-tools/scan-skill.js.map +1 -0
- package/dist/mcp-tools/validate.d.ts.map +1 -1
- package/dist/mcp-tools/validate.js +6 -0
- package/dist/mcp-tools/validate.js.map +1 -1
- package/dist/shadow-evaluator.d.ts.map +1 -1
- package/dist/shadow-evaluator.js +1 -0
- package/dist/shadow-evaluator.js.map +1 -1
- package/dist/tier0-invariant.d.ts.map +1 -1
- package/dist/tier0-invariant.js +1 -0
- package/dist/tier0-invariant.js.map +1 -1
- package/dist/tier1-blacklist.d.ts.map +1 -1
- package/dist/tier1-blacklist.js +1 -0
- package/dist/tier1-blacklist.js.map +1 -1
- package/dist/types.d.ts +23 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -1
- package/rules/agent-manipulation/{ATR-2026-030-cross-agent-attack.yaml → ATR-2026-00030-cross-agent-attack.yaml} +3 -1
- package/rules/agent-manipulation/{ATR-2026-032-goal-hijacking.yaml → ATR-2026-00032-goal-hijacking.yaml} +3 -1
- package/rules/agent-manipulation/{ATR-2026-074-cross-agent-privilege-escalation.yaml → ATR-2026-00074-cross-agent-privilege-escalation.yaml} +3 -1
- package/rules/agent-manipulation/{ATR-2026-076-inter-agent-message-spoofing.yaml → ATR-2026-00076-inter-agent-message-spoofing.yaml} +3 -1
- package/rules/agent-manipulation/{ATR-2026-077-human-trust-exploitation.yaml → ATR-2026-00077-human-trust-exploitation.yaml} +3 -1
- package/rules/agent-manipulation/{ATR-2026-108-consensus-sybil-attack.yaml → ATR-2026-00108-consensus-sybil-attack.yaml} +3 -1
- package/rules/agent-manipulation/{ATR-2026-116-a2a-message-validation.yaml → ATR-2026-00116-a2a-message-validation.yaml} +4 -2
- package/rules/agent-manipulation/{ATR-2026-117-agent-identity-spoofing.yaml → ATR-2026-00117-agent-identity-spoofing.yaml} +4 -2
- package/rules/agent-manipulation/{ATR-2026-118-approval-fatigue.yaml → ATR-2026-00118-approval-fatigue.yaml} +3 -1
- package/rules/agent-manipulation/{ATR-2026-119-social-engineering-via-agent.yaml → ATR-2026-00119-social-engineering-via-agent.yaml} +3 -1
- package/rules/agent-manipulation/ATR-2026-00132-casual-authority-escalation.yaml +105 -0
- package/rules/agent-manipulation/ATR-2026-00139-casual-authority-redirect.yaml +53 -0
- package/rules/context-exfiltration/{ATR-2026-020-system-prompt-leak.yaml → ATR-2026-00020-system-prompt-leak.yaml} +3 -1
- package/rules/context-exfiltration/{ATR-2026-021-api-key-exposure.yaml → ATR-2026-00021-api-key-exposure.yaml} +3 -1
- package/rules/context-exfiltration/{ATR-2026-075-agent-memory-manipulation.yaml → ATR-2026-00075-agent-memory-manipulation.yaml} +3 -1
- package/rules/context-exfiltration/{ATR-2026-102-disguised-analytics-exfiltration.yaml → ATR-2026-00102-disguised-analytics-exfiltration.yaml} +3 -1
- package/rules/context-exfiltration/{ATR-2026-113-credential-theft.yaml → ATR-2026-00113-credential-theft.yaml} +3 -1
- package/rules/context-exfiltration/{ATR-2026-114-oauth-token-abuse.yaml → ATR-2026-00114-oauth-token-abuse.yaml} +3 -1
- package/rules/context-exfiltration/{ATR-2026-115-env-var-harvesting.yaml → ATR-2026-00115-env-var-harvesting.yaml} +3 -1
- package/rules/context-exfiltration/ATR-2026-00136-tool-response-data-piggyback.yaml +100 -0
- package/rules/context-exfiltration/ATR-2026-00141-example-format-key-leak.yaml +52 -0
- package/rules/context-exfiltration/ATR-2026-00142-piggyback-transition-words.yaml +55 -0
- package/rules/context-exfiltration/ATR-2026-00145-obfuscated-key-disclosure.yaml +49 -0
- package/rules/context-exfiltration/ATR-2026-00146-env-var-existence-probe.yaml +49 -0
- package/rules/data-poisoning/{ATR-2026-070-data-poisoning.yaml → ATR-2026-00070-data-poisoning.yaml} +3 -1
- package/rules/excessive-autonomy/{ATR-2026-050-runaway-agent-loop.yaml → ATR-2026-00050-runaway-agent-loop.yaml} +3 -1
- package/rules/excessive-autonomy/{ATR-2026-051-resource-exhaustion.yaml → ATR-2026-00051-resource-exhaustion.yaml} +3 -1
- package/rules/excessive-autonomy/{ATR-2026-052-cascading-failure.yaml → ATR-2026-00052-cascading-failure.yaml} +3 -1
- package/rules/excessive-autonomy/{ATR-2026-098-unauthorized-financial-action.yaml → ATR-2026-00098-unauthorized-financial-action.yaml} +3 -1
- package/rules/excessive-autonomy/{ATR-2026-099-high-risk-tool-gate.yaml → ATR-2026-00099-high-risk-tool-gate.yaml} +3 -1
- package/rules/model-security/{ATR-2026-072-model-behavior-extraction.yaml → ATR-2026-00072-model-behavior-extraction.yaml} +3 -1
- package/rules/model-security/{ATR-2026-073-malicious-finetuning-data.yaml → ATR-2026-00073-malicious-finetuning-data.yaml} +3 -1
- package/rules/privilege-escalation/{ATR-2026-040-privilege-escalation.yaml → ATR-2026-00040-privilege-escalation.yaml} +3 -1
- package/rules/privilege-escalation/{ATR-2026-041-scope-creep.yaml → ATR-2026-00041-scope-creep.yaml} +3 -1
- package/rules/privilege-escalation/{ATR-2026-107-delayed-execution-bypass.yaml → ATR-2026-00107-delayed-execution-bypass.yaml} +3 -1
- package/rules/privilege-escalation/{ATR-2026-110-eval-injection.yaml → ATR-2026-00110-eval-injection.yaml} +3 -1
- package/rules/privilege-escalation/{ATR-2026-111-shell-escape.yaml → ATR-2026-00111-shell-escape.yaml} +5 -3
- package/rules/privilege-escalation/{ATR-2026-112-dynamic-import-exploitation.yaml → ATR-2026-00112-dynamic-import-exploitation.yaml} +3 -1
- package/rules/privilege-escalation/ATR-2026-00143-casual-privilege-escalation.yaml +53 -0
- package/rules/privilege-escalation/ATR-2026-00144-rationalized-safety-bypass.yaml +49 -0
- package/rules/prompt-injection/{ATR-2026-001-direct-prompt-injection.yaml → ATR-2026-00001-direct-prompt-injection.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-002-indirect-prompt-injection.yaml → ATR-2026-00002-indirect-prompt-injection.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-003-jailbreak-attempt.yaml → ATR-2026-00003-jailbreak-attempt.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-004-system-prompt-override.yaml → ATR-2026-00004-system-prompt-override.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-005-multi-turn-injection.yaml → ATR-2026-00005-multi-turn-injection.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-080-encoding-evasion.yaml → ATR-2026-00080-encoding-evasion.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-081-semantic-multi-turn.yaml → ATR-2026-00081-semantic-multi-turn.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-082-fingerprint-evasion.yaml → ATR-2026-00082-fingerprint-evasion.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-083-indirect-tool-injection.yaml → ATR-2026-00083-indirect-tool-injection.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-084-structured-data-injection.yaml → ATR-2026-00084-structured-data-injection.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-085-audit-evasion.yaml → ATR-2026-00085-audit-evasion.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-086-visual-spoofing.yaml → ATR-2026-00086-visual-spoofing.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-087-rule-probing.yaml → ATR-2026-00087-rule-probing.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-088-adaptive-countermeasure.yaml → ATR-2026-00088-adaptive-countermeasure.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-089-polymorphic-skill.yaml → ATR-2026-00089-polymorphic-skill.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-090-threat-intel-exfil.yaml → ATR-2026-00090-threat-intel-exfil.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-091-nested-payload.yaml → ATR-2026-00091-nested-payload.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-092-consensus-poisoning.yaml → ATR-2026-00092-consensus-poisoning.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-093-gradual-escalation.yaml → ATR-2026-00093-gradual-escalation.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-094-audit-bypass.yaml → ATR-2026-00094-audit-bypass.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-097-cjk-injection-patterns.yaml → ATR-2026-00097-cjk-injection-patterns.yaml} +3 -1
- package/rules/prompt-injection/{ATR-2026-104-persona-hijacking.yaml → ATR-2026-00104-persona-hijacking.yaml} +3 -1
- package/rules/prompt-injection/ATR-2026-00130-indirect-authority-claim.yaml +103 -0
- package/rules/prompt-injection/ATR-2026-00131-fictional-academic-framing.yaml +99 -0
- package/rules/prompt-injection/ATR-2026-00133-paraphrase-injection.yaml +117 -0
- package/rules/prompt-injection/ATR-2026-00137-authority-claim-injection.yaml +52 -0
- package/rules/prompt-injection/ATR-2026-00138-fictional-framing-bypass.yaml +51 -0
- package/rules/prompt-injection/ATR-2026-00140-indirect-reference-reversal.yaml +52 -0
- package/rules/prompt-injection/ATR-2026-00148-language-switch-injection.yaml +71 -0
- package/rules/skill-compromise/{ATR-2026-060-skill-impersonation.yaml → ATR-2026-00060-skill-impersonation.yaml} +3 -1
- package/rules/skill-compromise/{ATR-2026-061-description-behavior-mismatch.yaml → ATR-2026-00061-description-behavior-mismatch.yaml} +3 -1
- package/rules/skill-compromise/{ATR-2026-062-hidden-capability.yaml → ATR-2026-00062-hidden-capability.yaml} +3 -1
- package/rules/skill-compromise/{ATR-2026-063-skill-chain-attack.yaml → ATR-2026-00063-skill-chain-attack.yaml} +3 -1
- package/rules/skill-compromise/{ATR-2026-064-over-permissioned-skill.yaml → ATR-2026-00064-over-permissioned-skill.yaml} +3 -1
- package/rules/skill-compromise/{ATR-2026-065-skill-update-attack.yaml → ATR-2026-00065-skill-update-attack.yaml} +3 -1
- package/rules/skill-compromise/{ATR-2026-066-parameter-injection.yaml → ATR-2026-00066-parameter-injection.yaml} +3 -1
- package/rules/skill-compromise/ATR-2026-00120-skill-instruction-injection.yaml +121 -0
- package/rules/skill-compromise/ATR-2026-00121-skill-dangerous-script.yaml +165 -0
- package/rules/skill-compromise/ATR-2026-00122-skill-weaponized-instruction.yaml +114 -0
- package/rules/skill-compromise/ATR-2026-00123-skill-overreach-permissions.yaml +118 -0
- package/rules/skill-compromise/ATR-2026-00124-skill-name-squatting.yaml +98 -0
- package/rules/skill-compromise/ATR-2026-00125-context-poisoning-compaction.yaml +93 -0
- package/rules/skill-compromise/ATR-2026-00126-skill-rug-pull-setup.yaml +99 -0
- package/rules/skill-compromise/ATR-2026-00127-subcommand-overflow.yaml +74 -0
- package/rules/skill-compromise/ATR-2026-00128-html-comment-hidden-payload.yaml +79 -0
- package/rules/skill-compromise/ATR-2026-00129-unicode-smuggling.yaml +73 -0
- package/rules/skill-compromise/ATR-2026-00134-fork-claim-impersonation.yaml +93 -0
- package/rules/skill-compromise/ATR-2026-00135-exfil-url-in-instructions.yaml +82 -0
- package/rules/skill-compromise/ATR-2026-00147-fork-impersonation.yaml +48 -0
- package/rules/tool-poisoning/{ATR-2026-010-mcp-malicious-response.yaml → ATR-2026-00010-mcp-malicious-response.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-011-tool-output-injection.yaml → ATR-2026-00011-tool-output-injection.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-012-unauthorized-tool-call.yaml → ATR-2026-00012-unauthorized-tool-call.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-013-tool-ssrf.yaml → ATR-2026-00013-tool-ssrf.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-095-supply-chain-poisoning.yaml → ATR-2026-00095-supply-chain-poisoning.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-096-registry-poisoning.yaml → ATR-2026-00096-registry-poisoning.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-100-consent-bypass-instruction.yaml → ATR-2026-00100-consent-bypass-instruction.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-101-trust-escalation-override.yaml → ATR-2026-00101-trust-escalation-override.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-103-hidden-safety-bypass-instruction.yaml → ATR-2026-00103-hidden-safety-bypass-instruction.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-105-silent-action-concealment.yaml → ATR-2026-00105-silent-action-concealment.yaml} +3 -1
- package/rules/tool-poisoning/{ATR-2026-106-schema-description-contradiction.yaml → ATR-2026-00106-schema-description-contradiction.yaml} +3 -1
- 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
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|