agent-threat-rules 4.2.3 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +277 -0
- package/README.md +2 -0
- package/dist/cli/scan-handler.d.ts.map +1 -1
- package/dist/cli/scan-handler.js +164 -1
- package/dist/cli/scan-handler.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +123 -3
- package/dist/cli.js.map +1 -1
- package/dist/converters/numbat.d.ts +73 -0
- package/dist/converters/numbat.d.ts.map +1 -0
- package/dist/converters/numbat.js +281 -0
- package/dist/converters/numbat.js.map +1 -0
- package/dist/converters/quarry.d.ts +122 -0
- package/dist/converters/quarry.d.ts.map +1 -0
- package/dist/converters/quarry.js +431 -0
- package/dist/converters/quarry.js.map +1 -0
- package/dist/converters/sarif.d.ts +7 -0
- package/dist/converters/sarif.d.ts.map +1 -1
- package/dist/converters/sarif.js +24 -0
- package/dist/converters/sarif.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +7 -1
- package/dist/engine.js.map +1 -1
- package/dist/re2.d.ts +22 -0
- package/dist/re2.d.ts.map +1 -0
- package/dist/re2.js +213 -0
- package/dist/re2.js.map +1 -0
- package/package.json +1 -1
- package/rules/agent-manipulation/ATR-2026-00030-cross-agent-attack.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00301-tap-tree-of-attacks-jailbreak.yaml +3 -3
- package/rules/agent-manipulation/ATR-2026-00352-trainergpt-fix-my-ai-harmful-output-extraction.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00432-superagi-output-handler-eval-rce.yaml +3 -3
- package/rules/agent-manipulation/ATR-2026-00440-semantic-kernel-vector-store-eval-rce.yaml +3 -3
- package/rules/context-exfiltration/ATR-2026-00020-system-prompt-leak.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00471-garak-sysprompt-extraction-mixedunassigned.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00501-data-exfiltration-via-markdown-image-and-link-url-injection.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00504-tool-and-function-capability-enumeration.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00505-system-prompt-extraction-instruction-dump-request.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00514-system-prompt-extraction.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00516-output-xss-via-llm.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01462-sysprompt-override-pseudo-admin.yaml +2 -2
- package/rules/context-exfiltration/ATR-2026-01770-semantic-pii-credential-extraction.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01862-llmail-email-tool-exfil-confirmation.yaml +2 -2
- package/rules/context-exfiltration/ATR-2026-01863-llmail-post-summary-exfil-directive.yaml +2 -2
- package/rules/context-exfiltration/ATR-2026-01864-llmail-obfuscated-exfil-address.yaml +2 -2
- package/rules/context-exfiltration/ATR-2026-01924-system-instruction-extraction-reformat.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-02017-secret-key-exfiltration-request.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-02026-oblique-sysprompt-extraction-transform.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-02847-policy-justified-exfil-to-email.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-02850-fake-documentation-forward-request.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-02855-cross-email-exfil-relay.yaml +1 -1
- package/rules/data-poisoning/ATR-2026-00070-data-poisoning.yaml +2 -2
- package/rules/excessive-autonomy/ATR-2026-00050-runaway-agent-loop.yaml +2 -2
- package/rules/excessive-autonomy/ATR-2026-00052-cascading-failure.yaml +1 -1
- package/rules/excessive-autonomy/ATR-2026-00491-garak-agent-breaker-markdown-just-raw-json.yaml +1 -1
- package/rules/excessive-autonomy/ATR-2026-00500-ssrf-via-agent-url-fetch-instruction.yaml +1 -1
- package/rules/model-abuse/ATR-2026-00502-training-data-extraction-via-divergent-repetition-attack.yaml +1 -1
- package/rules/model-abuse/ATR-2026-00517-model-extraction-distillation.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00001-direct-prompt-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00002-indirect-prompt-injection.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-00003-jailbreak-attempt.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00140-indirect-reference-reversal.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00211-system-prompt-override.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00257-cipher-transposition-jailbreak.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00312-unicode-variant-selector-ascii-smuggling.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00452-direct-pwned-payload-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00453-secret-key-reveal-demand.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00454-backslash-per-character-encoding.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00455-no-period-output-override.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00456-conditional-empty-input-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00457-garak-dan-to-every-prompt.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00458-garak-dra-step-by-step.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00459-garak-dan-an-ai-language.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00460-garak-dan-no-matter-how.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00461-garak-goodside-mixedunassigned.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00462-garak-gcg-mixedunassigned.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00463-garak-lmrc-mixedunassigned.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00464-garak-dan-i-would-like.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00465-garak-dan-your-prompt-here.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00466-garak-latentinjection-following-text-from.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00467-garak-dan-act-as-a.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00468-garak-goat-the-target-llm.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00469-garak-dan-at-the-end.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00470-garak-dan-as-an-ai.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00472-garak-dan-are-going-to.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00473-garak-lmrc-how-can-i.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00474-garak-dan-that-chatgpt-has.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00475-garak-web-injection-alert-1-script.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00476-garak-web-injection-javascript-alert-1.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00477-garak-latentinjection-translate-the-following-text.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00478-garak-latentinjection-abuse-contact-email.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00479-garak-latentinjection-mixedunassigned.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00480-garak-dan-the-role-of.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00481-garak-dan-mixedunassigned.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00482-garak-lmrc-what-s-an.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00483-garak-dan-the-user-s.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00484-garak-dan-and-you-will.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00485-garak-dan-and-does-not.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00486-garak-dan-t-care-about.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00487-garak-dan-and-nothing-else.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00488-garak-encoding-alert-1-script.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00489-garak-encoding-javascript-alert-1.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00490-garak-dan-in-order-to.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00492-garak-dan-want-you-to.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00493-garak-latentinjection-registrar-iana-id.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00495-garak-dan-insert-prompt-here.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00496-garak-dan-no-matter-what.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00497-garak-encoding-mixedunassigned.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00498-garak-web-injection-mixedunassigned.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00499-garak-dan-from-now-on.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00503-fake-error-state-takeover-unrestricted-replacement-bot.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00506-nevermind-override-goal-hijacking-in-user-input-promptinject.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00507-screaming-stop-goal-hijacking-in-user-input-promptinject.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00508-escape-delimiter-wrapped-goal-hijacking-in-user-input-prompt.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00509-prompt-leaking-via-ignore-previous-instructions-in-user-inpu.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00510-delayed-tool-invocation-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00511-mcp-web-context-poisoning.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00512-rules-file-backdoor-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00515-hidden-text-prompt-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00518-ignore-previous-and-following-instructions-output-command-promptinject.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00519-tautology-logic-noise-injection-promptbench.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00520-nlp-task-random-token-suffix-injection-promptbench.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01860-llmail-chat-template-boundary-spoof.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01861-llmail-pseudo-xml-role-injection.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01865-llmail-fake-email-boundary-marker.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01920-access-grant-coercion.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01921-access-denial-inversion.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01922-authorization-state-spoofing.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01923-forged-input-boundary-markers.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01925-encoded-payload-decoding-coercion.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01926-cross-lingual-authorization-laundering.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02001-ignore-below-instructions-mirror-of-ignore-previ.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02002-instruction-set-probe-or-supplant-direct-meta-qu.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02003-task-pivot-override-abandon-the-cover-task-trans.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02004-regional-indicator-flag-emoji-alphabet-letter-sp.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02005-actor-never-breaks-character-roleplay-dan-style-.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02006-fuzzy-cross-lingual-i-have-been-pwned-near-miss-.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02007-bare-key-elicitation-token.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02008-translation-chain-output-laundering-state-secret.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02009-bare-key-elicitation-trailing-colon.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02010-emoji-only-payload.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02011-malformed-grammar-pwned-success-claim.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02012-movie-title-generator-instruction-wrapper-for-pw.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02013-delimiter-fence-ignore-instructions-override.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02014-spliced-target-string-letters-of-a-filtered-cana.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02015-dense-emoji-only-payload-flood.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02016-reversed-payload-string.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02018-regional-indicator-flag-emoji-letter-spelling-sp.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02019-python-interpreter-role-assignment-exec-framing.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02020-obfuscated-token-reconstruction-via-character-su.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02851-fake-user-turn-send-email-instruction.yaml +1 -1
- package/rules/skill-compromise/ATR-2026-00060-skill-impersonation.yaml +1 -1
- package/rules/skill-compromise/ATR-2026-00121-skill-dangerous-script.yaml +5 -5
- package/rules/skill-compromise/ATR-2026-00225-c2-communication.yaml +3 -3
- package/rules/tool-poisoning/ATR-2026-00277-echo-template-command-injection.yaml +3 -3
- package/rules/tool-poisoning/ATR-2026-00494-garak-exploitation-mixedunassigned.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-00513-package-hallucination-exploitation.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-00521-shell-command-injection-agent-tool-context.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-00522-sql-injection-natural-language-agent-interface.yaml +1 -1
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ATR-to-numbat converter
|
|
3
|
+
*
|
|
4
|
+
* Exports ATR rules as numbat operator rules: one YAML rule file per ATR rule,
|
|
5
|
+
* plus a companion `_tests.yaml` built from the rule's own test cases, so
|
|
6
|
+
* `numbat rules check --rules-dir <dir>` runs ATR's true positives and true
|
|
7
|
+
* negatives through numbat's RE2 matcher.
|
|
8
|
+
*
|
|
9
|
+
* Field mapping (numbat event model, schema 0.5.0):
|
|
10
|
+
* user_input -> prompt.user content, and tool_result of a tool result
|
|
11
|
+
* agent_output -> message.assistant content, and tool_result of a tool
|
|
12
|
+
* result (the ATR engine reads both on tool output, so
|
|
13
|
+
* indirect injection riding in on a tool result is seen)
|
|
14
|
+
* tool_args -> tool_input of a tool request (tool.call, command.exec,
|
|
15
|
+
* file.*, network.indicator)
|
|
16
|
+
* tool_response -> tool_result of a tool result (tool.result, command.result)
|
|
17
|
+
* tool_name -> tool_name of a tool request or result
|
|
18
|
+
* content -> the event's own text: content on prompt.user and
|
|
19
|
+
* message.assistant, tool_input on a request, tool_result
|
|
20
|
+
* on a result
|
|
21
|
+
*
|
|
22
|
+
* What does not carry over:
|
|
23
|
+
* - Conditions on other fields, and patterns RE2 cannot express, are
|
|
24
|
+
* dropped. A rule whose detection is "all" loses its meaning if any
|
|
25
|
+
* condition is dropped, so it is skipped; an "any" rule keeps the
|
|
26
|
+
* conditions that convert.
|
|
27
|
+
* - The ATR engine folds Unicode confusables and normalizes NFKC/NFC before
|
|
28
|
+
* matching; numbat matches the raw text, so obfuscated input can match in
|
|
29
|
+
* ATR and not here.
|
|
30
|
+
* - JavaScript and RE2 differ in a few classes: JS `\s` also matches NBSP,
|
|
31
|
+
* U+2028 and the BOM, JS `.` excludes `\r`, and a JS pattern without the
|
|
32
|
+
* `u` flag counts an astral character as two. The companion tests catch
|
|
33
|
+
* a difference only where a test case exercises it.
|
|
34
|
+
* - Rules are monitor-only (`enforce: false`); blocking is an operator
|
|
35
|
+
* decision in numbat.
|
|
36
|
+
*
|
|
37
|
+
* @module agent-threat-rules/converters/numbat
|
|
38
|
+
*/
|
|
39
|
+
import yaml from 'js-yaml';
|
|
40
|
+
import { regexConditionSpec } from '../regex-dialect.js';
|
|
41
|
+
import { toRe2 } from '../re2.js';
|
|
42
|
+
import { laneAllows, requiresConfirm } from '../quality/rule-contract.js';
|
|
43
|
+
const REQUEST_TYPES = ['tool.call', 'command.exec', 'file.read', 'file.write', 'file.delete', 'network.indicator'];
|
|
44
|
+
const RESULT_TYPES = ['tool.result', 'command.result'];
|
|
45
|
+
/** numbat field name and the event types it is read on, per ATR field. */
|
|
46
|
+
const FIELD_TARGETS = {
|
|
47
|
+
user_input: [
|
|
48
|
+
['content', ['prompt.user']],
|
|
49
|
+
['tool_result', RESULT_TYPES],
|
|
50
|
+
],
|
|
51
|
+
agent_output: [
|
|
52
|
+
['content', ['message.assistant']],
|
|
53
|
+
['tool_result', RESULT_TYPES],
|
|
54
|
+
],
|
|
55
|
+
tool_args: [['tool_input', REQUEST_TYPES]],
|
|
56
|
+
tool_response: [['tool_result', RESULT_TYPES]],
|
|
57
|
+
tool_name: [['tool_name', [...REQUEST_TYPES, ...RESULT_TYPES]]],
|
|
58
|
+
content: [
|
|
59
|
+
['content', ['prompt.user', 'message.assistant']],
|
|
60
|
+
['tool_input', REQUEST_TYPES],
|
|
61
|
+
['tool_result', RESULT_TYPES],
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
const SEVERITY = {
|
|
65
|
+
critical: 'critical',
|
|
66
|
+
high: 'high',
|
|
67
|
+
medium: 'medium',
|
|
68
|
+
low: 'low',
|
|
69
|
+
informational: 'info',
|
|
70
|
+
info: 'info',
|
|
71
|
+
};
|
|
72
|
+
/** numbat rule id for an ATR id: `atr.atr-2026-00432`. */
|
|
73
|
+
export function numbatRuleId(atrId) {
|
|
74
|
+
return `atr.${atrId.toLowerCase()}`;
|
|
75
|
+
}
|
|
76
|
+
/** A CEL double-quoted string literal. */
|
|
77
|
+
function celString(s) {
|
|
78
|
+
const escaped = s
|
|
79
|
+
.replace(/\\/g, '\\\\')
|
|
80
|
+
.replace(/"/g, '\\"')
|
|
81
|
+
.replace(/\n/g, '\\n')
|
|
82
|
+
.replace(/\r/g, '\\r')
|
|
83
|
+
.replace(/\t/g, '\\t');
|
|
84
|
+
return `"${escaped}"`;
|
|
85
|
+
}
|
|
86
|
+
function typeTest(types) {
|
|
87
|
+
return types.length === 1
|
|
88
|
+
? `event.event_type == ${celString(types[0])}`
|
|
89
|
+
: `event.event_type in [${types.map(celString).join(', ')}]`;
|
|
90
|
+
}
|
|
91
|
+
/** One ATR regex condition as CEL, or null when it cannot be converted. */
|
|
92
|
+
function convertCondition(cond) {
|
|
93
|
+
const targets = FIELD_TARGETS[cond.field];
|
|
94
|
+
if (!targets || cond.operator !== 'regex' || !cond.value)
|
|
95
|
+
return null;
|
|
96
|
+
const spec = regexConditionSpec(cond.value);
|
|
97
|
+
const source = toRe2(spec.source);
|
|
98
|
+
if (source === null)
|
|
99
|
+
return null;
|
|
100
|
+
let js;
|
|
101
|
+
try {
|
|
102
|
+
js = new RegExp(spec.source, spec.flags);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null; // the ATR engine cannot compile it either
|
|
106
|
+
}
|
|
107
|
+
// SPEC §6.4: every condition matches case-insensitively.
|
|
108
|
+
const pattern = celString(`(?i)${source}`);
|
|
109
|
+
const branches = targets.map(([field, types]) => `(${typeTest(types)} && event.${field}.matches(${pattern}))`);
|
|
110
|
+
return {
|
|
111
|
+
cel: branches.length === 1 ? branches[0] : `(${branches.join(' || ')})`,
|
|
112
|
+
targets,
|
|
113
|
+
js,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Does a converted condition match this test event? */
|
|
117
|
+
function conditionMatches(cond, event) {
|
|
118
|
+
return cond.targets.some(([field, types]) => {
|
|
119
|
+
const value = event[field];
|
|
120
|
+
return types.includes(String(event['event_type'])) && typeof value === 'string' && cond.js.test(value);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Is there one numbat event type that every condition can read? */
|
|
124
|
+
function shareAnEventType(conditions) {
|
|
125
|
+
const typesOf = (c) => new Set(c.targets.flatMap(([, types]) => types));
|
|
126
|
+
const [first, ...rest] = conditions.map(typesOf);
|
|
127
|
+
return [...(first ?? [])].some((t) => rest.every((types) => types.has(t)));
|
|
128
|
+
}
|
|
129
|
+
function arrayConditions(rule) {
|
|
130
|
+
return Array.isArray(rule.detection.conditions) ? rule.detection.conditions : null;
|
|
131
|
+
}
|
|
132
|
+
const utf8Bytes = (s) => Buffer.byteLength(s, 'utf8');
|
|
133
|
+
/**
|
|
134
|
+
* The event a plain-text test case stands for. ATR's `input` key is the text
|
|
135
|
+
* of whatever the rule reads, so a rule that reads only tool output gets a
|
|
136
|
+
* tool result, and one that reads only tool arguments gets a tool call.
|
|
137
|
+
*/
|
|
138
|
+
function plainTextEventType(fields) {
|
|
139
|
+
// A named field says which event the text belongs to; `content` alone does not.
|
|
140
|
+
if (fields.has('tool_response'))
|
|
141
|
+
return 'tool.result';
|
|
142
|
+
if (fields.has('tool_args'))
|
|
143
|
+
return 'tool.call';
|
|
144
|
+
if (fields.has('user_input'))
|
|
145
|
+
return 'prompt.user';
|
|
146
|
+
if (fields.has('agent_output'))
|
|
147
|
+
return 'message.assistant';
|
|
148
|
+
return 'prompt.user';
|
|
149
|
+
}
|
|
150
|
+
/** One numbat test event per ATR test case, or null when the case has no mappable field. */
|
|
151
|
+
function testEvent(tc, fields) {
|
|
152
|
+
const str = (v) => (typeof v === 'string' ? v : JSON.stringify(v));
|
|
153
|
+
const name = typeof tc['tool_name'] === 'string' ? tc['tool_name'] : 'atr-test-tool';
|
|
154
|
+
if (tc['tool_args'] !== undefined) {
|
|
155
|
+
const input = str(tc['tool_args']);
|
|
156
|
+
return { event_type: 'tool.call', tool_name: name, tool_input: input, tool_input_bytes: utf8Bytes(input) };
|
|
157
|
+
}
|
|
158
|
+
if (tc['tool_response'] !== undefined) {
|
|
159
|
+
const result = str(tc['tool_response']);
|
|
160
|
+
return { event_type: 'tool.result', tool_name: name, tool_result: result, tool_result_bytes: utf8Bytes(result) };
|
|
161
|
+
}
|
|
162
|
+
const message = (type, text) => ({ event_type: type, content: text, content_bytes: utf8Bytes(text) });
|
|
163
|
+
if (typeof tc['agent_output'] === 'string')
|
|
164
|
+
return message('message.assistant', tc['agent_output']);
|
|
165
|
+
const text = tc['user_input'] ?? tc['input'] ?? tc['content'];
|
|
166
|
+
if (typeof text !== 'string')
|
|
167
|
+
return null;
|
|
168
|
+
const type = plainTextEventType(fields);
|
|
169
|
+
if (type === 'tool.result')
|
|
170
|
+
return { event_type: type, tool_name: name, tool_result: text, tool_result_bytes: utf8Bytes(text) };
|
|
171
|
+
if (type === 'tool.call')
|
|
172
|
+
return { event_type: type, tool_name: name, tool_input: text, tool_input_bytes: utf8Bytes(text) };
|
|
173
|
+
return message(type, text);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The companion test file. A true positive that only a dropped condition
|
|
177
|
+
* caught would fail in numbat, so it is left out and reported instead.
|
|
178
|
+
*/
|
|
179
|
+
function companionTests(rule, id, converted, logic) {
|
|
180
|
+
const fields = new Set(arrayConditions(rule)?.map((c) => c.field) ?? []);
|
|
181
|
+
const cases = [];
|
|
182
|
+
const uncovered = [];
|
|
183
|
+
const unmapped = [];
|
|
184
|
+
const matches = (event) => logic === 'all' ? converted.every((c) => conditionMatches(c, event)) : converted.some((c) => conditionMatches(c, event));
|
|
185
|
+
const add = (list, expect, label) => {
|
|
186
|
+
if (!Array.isArray(list))
|
|
187
|
+
return;
|
|
188
|
+
list.forEach((tc, i) => {
|
|
189
|
+
const event = tc && typeof tc === 'object' ? testEvent(tc, fields) : null;
|
|
190
|
+
if (!event) {
|
|
191
|
+
unmapped.push(`${expect === 'match' ? 'tp' : 'tn'}-${i + 1}`);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (expect === 'match' && !matches(event)) {
|
|
195
|
+
uncovered.push(i + 1);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
cases.push({ name: `${label}-${i + 1}`, expect, events: [event] });
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
add(rule.test_cases?.true_positives, 'match', 'atr-true-positive');
|
|
202
|
+
add(rule.test_cases?.true_negatives, 'no_match', 'atr-true-negative');
|
|
203
|
+
return {
|
|
204
|
+
yaml: cases.length === 0 ? null : yaml.dump({ rule_id: id, cases }, { lineWidth: -1, noRefs: true }),
|
|
205
|
+
uncovered,
|
|
206
|
+
unmapped,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/** Convert one ATR rule, or say why it cannot be converted. */
|
|
210
|
+
export function ruleToNumbat(rule) {
|
|
211
|
+
const conditions = arrayConditions(rule);
|
|
212
|
+
if (!conditions)
|
|
213
|
+
return { ruleId: rule.id, reason: 'named-format conditions are not supported' };
|
|
214
|
+
const converted = [];
|
|
215
|
+
const dropped = [];
|
|
216
|
+
conditions.forEach((cond, i) => {
|
|
217
|
+
const c = convertCondition(cond);
|
|
218
|
+
if (c === null)
|
|
219
|
+
dropped.push(i);
|
|
220
|
+
else
|
|
221
|
+
converted.push(c);
|
|
222
|
+
});
|
|
223
|
+
const logic = String(rule.detection.condition ?? 'any').trim().toLowerCase();
|
|
224
|
+
if (logic !== 'any' && logic !== 'all')
|
|
225
|
+
return { ruleId: rule.id, reason: `detection condition "${logic}" is not supported` };
|
|
226
|
+
if (converted.length === 0)
|
|
227
|
+
return { ruleId: rule.id, reason: 'no condition converts (fields or RE2)' };
|
|
228
|
+
if (logic === 'all' && dropped.length > 0) {
|
|
229
|
+
return { ruleId: rule.id, reason: 'an "all" rule with an unconvertible condition would change meaning' };
|
|
230
|
+
}
|
|
231
|
+
if (logic === 'all' && !shareAnEventType(converted)) {
|
|
232
|
+
return { ruleId: rule.id, reason: 'an "all" rule whose conditions read different numbat events can never match' };
|
|
233
|
+
}
|
|
234
|
+
if (!/^ATR-\d{4}-\d{5}$/.test(rule.id))
|
|
235
|
+
return { ruleId: rule.id, reason: 'rule id is not in ATR-YYYY-NNNNN form' };
|
|
236
|
+
const id = numbatRuleId(rule.id);
|
|
237
|
+
const doc = {
|
|
238
|
+
id,
|
|
239
|
+
version: String(rule.rule_version ?? 1),
|
|
240
|
+
title: rule.title,
|
|
241
|
+
severity: SEVERITY[rule.severity] ?? 'medium',
|
|
242
|
+
description: `${String(rule.description ?? '').trim()}\n\nConverted from Agent Threat Rules ${rule.id} (https://github.com/Agent-Threat-Rule/agent-threat-rules).`.trim(),
|
|
243
|
+
enabled: true,
|
|
244
|
+
enforce: false,
|
|
245
|
+
tags: ['atr', `atr:${rule.id}`, `atr-category:${rule.tags.category}`],
|
|
246
|
+
expr: converted.map((c) => c.cel).join(logic === 'all' ? ' &&\n' : ' ||\n'),
|
|
247
|
+
};
|
|
248
|
+
const tests = companionTests(rule, id, converted, logic);
|
|
249
|
+
return {
|
|
250
|
+
ruleId: rule.id,
|
|
251
|
+
stem: rule.id.toLowerCase(),
|
|
252
|
+
rule: yaml.dump(doc, { lineWidth: -1, noRefs: true }),
|
|
253
|
+
tests: tests.yaml,
|
|
254
|
+
droppedConditions: dropped,
|
|
255
|
+
uncoveredTruePositives: tests.uncovered,
|
|
256
|
+
unmappedCases: tests.unmapped,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
/** Convert the rules in `lane` (default `enforce`). Embedding-confirmed rules are left out of enforce and alert. */
|
|
260
|
+
export function rulesToNumbat(rules, options = {}) {
|
|
261
|
+
const lane = options.lane ?? 'enforce';
|
|
262
|
+
const files = [];
|
|
263
|
+
const skipped = [];
|
|
264
|
+
for (const rule of rules) {
|
|
265
|
+
if (rule.status === 'draft' || rule.status === 'deprecated')
|
|
266
|
+
continue;
|
|
267
|
+
if (!laneAllows(rule.maturity, lane))
|
|
268
|
+
continue;
|
|
269
|
+
if (lane !== 'hunt' && requiresConfirm(rule)) {
|
|
270
|
+
skipped.push({ ruleId: rule.id, reason: 'needs embedding confirmation, which numbat does not run' });
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const out = ruleToNumbat(rule);
|
|
274
|
+
if ('reason' in out)
|
|
275
|
+
skipped.push(out);
|
|
276
|
+
else
|
|
277
|
+
files.push(out);
|
|
278
|
+
}
|
|
279
|
+
return { files, skipped };
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=numbat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"numbat.js","sourceRoot":"","sources":["../../src/converters/numbat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ATR-to-Quarry converter
|
|
3
|
+
*
|
|
4
|
+
* Exports ATR rules as Project Quarry rules (Cisco AI Defense, DSL 0.1): one
|
|
5
|
+
* `.rule` file per ATR rule, a `quarry.yaml` that loads them, and one test
|
|
6
|
+
* file per rule built from the rule's own test cases as OpenAI-shaped
|
|
7
|
+
* conversations. `check_atr_tests.py`, written next to them, runs those
|
|
8
|
+
* conversations through Quarry's scanner, so a JS/RE2 difference that a test
|
|
9
|
+
* case exercises shows up as a failing case.
|
|
10
|
+
*
|
|
11
|
+
* Field mapping (Quarry data surfaces):
|
|
12
|
+
* user_input -> user_messages.content, and tool_responses.content
|
|
13
|
+
* agent_output -> assistant_messages.content, and tool_responses.content
|
|
14
|
+
* (the ATR engine reads both on tool output, so
|
|
15
|
+
* indirect injection riding in on a tool result is seen)
|
|
16
|
+
* tool_args -> tool_calls.raw_arguments
|
|
17
|
+
* tool_response -> tool_responses.content
|
|
18
|
+
* tool_name -> tool_calls.name and tool_responses.name
|
|
19
|
+
* tool_description -> tools.description
|
|
20
|
+
* content -> the text of user and assistant messages, tool call
|
|
21
|
+
* arguments and tool results. System and developer
|
|
22
|
+
* messages are not read: the ATR engine sees them only
|
|
23
|
+
* when a producer sends them as user input.
|
|
24
|
+
*
|
|
25
|
+
* What does not carry over:
|
|
26
|
+
* - Conditions on other fields, and patterns RE2 cannot express, are
|
|
27
|
+
* dropped. A rule whose detection is "all" loses its meaning if any
|
|
28
|
+
* condition is dropped, so it is skipped; an "any" rule keeps the
|
|
29
|
+
* conditions that convert. The ATR engine tests an "all" rule's
|
|
30
|
+
* conditions on one event, so here they must hold on one item of one
|
|
31
|
+
* surface; a rule whose conditions share no surface is skipped.
|
|
32
|
+
* - The ATR engine folds Unicode confusables and normalizes NFKC/NFC before
|
|
33
|
+
* matching; Quarry matches the raw text, so obfuscated input can match in
|
|
34
|
+
* ATR and not here.
|
|
35
|
+
* - JavaScript and RE2 differ in a few classes: JS `\s` also matches `\v`,
|
|
36
|
+
* NBSP, the other Unicode spaces and the BOM (RE2's is ASCII only), JS `.` excludes `\r`, and a JS pattern without the
|
|
37
|
+
* `u` flag counts an astral character as two. RE2's case folding also
|
|
38
|
+
* maps U+017F to `s` and U+212A to `k`. The tests catch a
|
|
39
|
+
* difference only where a test case exercises it.
|
|
40
|
+
* - Quarry reports matches; the rules carry the ATR severity as metadata
|
|
41
|
+
* and blocking is the application's decision.
|
|
42
|
+
*
|
|
43
|
+
* @module agent-threat-rules/converters/quarry
|
|
44
|
+
*/
|
|
45
|
+
import type { ATRRule } from '../types.js';
|
|
46
|
+
import { type Lane } from '../quality/rule-contract.js';
|
|
47
|
+
export interface QuarryExportOptions {
|
|
48
|
+
/** Which ATR rules to export, by maturity lane. Default `enforce` (stable rules). */
|
|
49
|
+
readonly lane?: Lane;
|
|
50
|
+
}
|
|
51
|
+
export interface QuarryTestCase {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly expect: 'match' | 'no_match';
|
|
54
|
+
readonly conversation: Conversation;
|
|
55
|
+
}
|
|
56
|
+
export interface QuarryRuleFile {
|
|
57
|
+
readonly ruleId: string;
|
|
58
|
+
/** File stem, e.g. `atr-2026-00432`; write `rules/<stem>.rule` and `tests/<stem>.json`. */
|
|
59
|
+
readonly stem: string;
|
|
60
|
+
readonly rule: string;
|
|
61
|
+
/** Empty when no test case maps onto a conversation. */
|
|
62
|
+
readonly tests: readonly QuarryTestCase[];
|
|
63
|
+
/** ATR conditions that could not be converted, by index. */
|
|
64
|
+
readonly droppedConditions: readonly number[];
|
|
65
|
+
/** ATR true positives (1-based) the converted rule does not match, left out of the tests. */
|
|
66
|
+
readonly uncoveredTruePositives: readonly number[];
|
|
67
|
+
/** ATR test cases (labelled tp-N / tn-N) with no field that maps onto a conversation. */
|
|
68
|
+
readonly unmappedCases: readonly string[];
|
|
69
|
+
}
|
|
70
|
+
export interface QuarrySkip {
|
|
71
|
+
readonly ruleId: string;
|
|
72
|
+
readonly reason: string;
|
|
73
|
+
}
|
|
74
|
+
export interface QuarryExport {
|
|
75
|
+
readonly files: readonly QuarryRuleFile[];
|
|
76
|
+
readonly skipped: readonly QuarrySkip[];
|
|
77
|
+
}
|
|
78
|
+
interface ToolCall {
|
|
79
|
+
readonly id: string;
|
|
80
|
+
readonly type: 'function';
|
|
81
|
+
readonly function: {
|
|
82
|
+
readonly name: string;
|
|
83
|
+
readonly arguments: string;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
type Message = {
|
|
87
|
+
readonly role: 'user';
|
|
88
|
+
readonly content: string;
|
|
89
|
+
} | {
|
|
90
|
+
readonly role: 'assistant';
|
|
91
|
+
readonly content: string | null;
|
|
92
|
+
readonly tool_calls?: readonly ToolCall[];
|
|
93
|
+
} | {
|
|
94
|
+
readonly role: 'tool';
|
|
95
|
+
readonly tool_call_id: string;
|
|
96
|
+
readonly content: string;
|
|
97
|
+
};
|
|
98
|
+
interface ToolDefinition {
|
|
99
|
+
readonly type: 'function';
|
|
100
|
+
readonly function: {
|
|
101
|
+
readonly name: string;
|
|
102
|
+
readonly description: string;
|
|
103
|
+
readonly parameters: {
|
|
104
|
+
readonly type: 'object';
|
|
105
|
+
readonly properties: Record<string, never>;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export interface Conversation {
|
|
110
|
+
readonly messages: readonly Message[];
|
|
111
|
+
readonly tools?: readonly ToolDefinition[];
|
|
112
|
+
}
|
|
113
|
+
/** Convert one ATR rule, or say why it cannot be converted. */
|
|
114
|
+
export declare function ruleToQuarry(rule: ATRRule): QuarryRuleFile | QuarrySkip;
|
|
115
|
+
/** Convert the rules in `lane` (default `enforce`). Embedding-confirmed rules are left out of enforce and alert. */
|
|
116
|
+
export declare function rulesToQuarry(rules: readonly ATRRule[], options?: QuarryExportOptions): QuarryExport;
|
|
117
|
+
/** `quarry.yaml`, loading the exported rules. Quarry resolves `rules/` against the working directory. */
|
|
118
|
+
export declare const QUARRY_CONFIG = "config_version: 2\nrules:\n - rules/\n";
|
|
119
|
+
/** `check_atr_tests.py`: runs each rule's ATR test conversations through Quarry. */
|
|
120
|
+
export declare const QUARRY_CHECK_SCRIPT = "\"\"\"Run the ATR test cases exported with these rules through Quarry.\n\nWith Quarry installed:\n\n python check_atr_tests.py\n\nEach tests/<rule>.json holds that rule's ATR true positives (expect \"match\")\nand true negatives (expect \"no_match\") as conversations. A case passes when\nthe named rule's verdict is what the case expects; other rules may match too.\nExits 1 if any case fails or any scan is incomplete.\n\"\"\"\n\nimport json\nimport os\nimport sys\nfrom pathlib import Path\n\nfrom quarry import DSLScanner\nfrom quarry.config import QuarryConfig\n\nHERE = Path(__file__).resolve().parent\n\n\ndef main() -> int:\n # Quarry resolves the rules path in quarry.yaml against the working directory.\n os.chdir(HERE)\n scanner = DSLScanner(QuarryConfig.from_yaml(HERE / \"quarry.yaml\"))\n total = failed = 0\n for path in sorted((HERE / \"tests\").glob(\"*.json\")):\n suite = json.loads(path.read_text(encoding=\"utf-8\"))\n for case in suite[\"cases\"]:\n total += 1\n result = scanner.scan_data(case[\"conversation\"])\n if not result.is_complete:\n failed += 1\n print(f\"{suite['rule']} {case['name']}: scan {result.completion_state}\")\n continue\n matched = suite[\"rule\"] in result.rule_matches\n if matched != (case[\"expect\"] == \"match\"):\n failed += 1\n print(f\"{suite['rule']} {case['name']}: expected {case['expect']}\")\n print(f\"{total - failed}/{total} cases pass\")\n return 1 if failed else 0\n\n\nif __name__ == \"__main__\":\n sys.exit(main())\n";
|
|
121
|
+
export {};
|
|
122
|
+
//# sourceMappingURL=quarry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quarry.d.ts","sourceRoot":"","sources":["../../src/converters/quarry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,aAAa,CAAC;AAG9D,OAAO,EAA+B,KAAK,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAgCrF,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;CACrC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2FAA2F;IAC3F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,4DAA4D;IAC5D,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,6FAA6F;IAC7F,QAAQ,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAC;IACnD,yFAAyF;IACzF,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;CACzC;AA2FD,UAAU,QAAQ;IAChB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1E;AAED,KAAK,OAAO,GACR;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAA;CAAE,GAC1G;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvF,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,UAAU,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;YAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;SAAE,CAAC;KAC9F,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CAC5C;AAuJD,+DAA+D;AAC/D,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,UAAU,CAoDvE;AAED,oHAAoH;AACpH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,OAAO,GAAE,mBAAwB,GAAG,YAAY,CAgBxG;AAED,yGAAyG;AACzG,eAAO,MAAM,aAAa,4CAA4C,CAAC;AAEvE,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,6nDA+C/B,CAAC"}
|