agent-threat-rules 4.1.5 → 4.2.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 +688 -1
- package/README.md +355 -125
- package/dist/adapters/openshell-filter.d.ts +10 -3
- package/dist/adapters/openshell-filter.d.ts.map +1 -1
- package/dist/adapters/openshell-filter.js +10 -3
- package/dist/adapters/openshell-filter.js.map +1 -1
- package/dist/adapters/stdio-adapter.js +3 -3
- package/dist/adapters/stdio-adapter.js.map +1 -1
- package/dist/cli/mcp-tools-document.d.ts +40 -0
- package/dist/cli/mcp-tools-document.d.ts.map +1 -0
- package/dist/cli/mcp-tools-document.js +95 -0
- package/dist/cli/mcp-tools-document.js.map +1 -0
- package/dist/cli/scan-handler.d.ts +8 -0
- package/dist/cli/scan-handler.d.ts.map +1 -1
- package/dist/cli/scan-handler.js +126 -34
- package/dist/cli/scan-handler.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +80 -43
- package/dist/cli.js.map +1 -1
- package/dist/converters/sarif.d.ts +14 -2
- package/dist/converters/sarif.d.ts.map +1 -1
- package/dist/converters/sarif.js +7 -9
- package/dist/converters/sarif.js.map +1 -1
- package/dist/corpus-event.d.ts +47 -5
- package/dist/corpus-event.d.ts.map +1 -1
- package/dist/corpus-event.js +68 -29
- package/dist/corpus-event.js.map +1 -1
- package/dist/embedding/model-loader.d.ts +2 -0
- package/dist/embedding/model-loader.d.ts.map +1 -1
- package/dist/embedding/model-loader.js +3 -1
- package/dist/embedding/model-loader.js.map +1 -1
- package/dist/enforcement.d.ts +8 -0
- package/dist/enforcement.d.ts.map +1 -1
- package/dist/enforcement.js +10 -0
- package/dist/enforcement.js.map +1 -1
- package/dist/engine.d.ts +150 -6
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +337 -76
- package/dist/engine.js.map +1 -1
- package/dist/eval/embedding-provenance.d.ts +40 -0
- package/dist/eval/embedding-provenance.d.ts.map +1 -0
- package/dist/eval/embedding-provenance.js +71 -0
- package/dist/eval/embedding-provenance.js.map +1 -0
- package/dist/eval/eval-harness.d.ts +34 -0
- package/dist/eval/eval-harness.d.ts.map +1 -1
- package/dist/eval/eval-harness.js +49 -2
- package/dist/eval/eval-harness.js.map +1 -1
- package/dist/eval/hackaprompt-corpus.d.ts +12 -0
- package/dist/eval/hackaprompt-corpus.d.ts.map +1 -1
- package/dist/eval/hackaprompt-corpus.js +15 -1
- package/dist/eval/hackaprompt-corpus.js.map +1 -1
- package/dist/eval/hackaprompt-provenance.d.ts +52 -0
- package/dist/eval/hackaprompt-provenance.d.ts.map +1 -0
- package/dist/eval/hackaprompt-provenance.js +108 -0
- package/dist/eval/hackaprompt-provenance.js.map +1 -0
- package/dist/eval/run-hackaprompt-benchmark.d.ts +18 -8
- package/dist/eval/run-hackaprompt-benchmark.d.ts.map +1 -1
- package/dist/eval/run-hackaprompt-benchmark.js +154 -64
- package/dist/eval/run-hackaprompt-benchmark.js.map +1 -1
- package/dist/eval/run-pint-benchmark.js +11 -1
- package/dist/eval/run-pint-benchmark.js.map +1 -1
- package/dist/eval/skill-benchmark.d.ts +26 -6
- package/dist/eval/skill-benchmark.d.ts.map +1 -1
- package/dist/eval/skill-benchmark.js +49 -16
- package/dist/eval/skill-benchmark.js.map +1 -1
- package/dist/flywheel.d.ts +4 -0
- package/dist/flywheel.d.ts.map +1 -1
- package/dist/flywheel.js +12 -4
- package/dist/flywheel.js.map +1 -1
- package/dist/hook-handler.d.ts +11 -1
- package/dist/hook-handler.d.ts.map +1 -1
- package/dist/hook-handler.js +84 -16
- package/dist/hook-handler.js.map +1 -1
- package/dist/hook-tool-output.d.ts +47 -0
- package/dist/hook-tool-output.d.ts.map +1 -0
- package/dist/hook-tool-output.js +110 -0
- package/dist/hook-tool-output.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/layer-integration.d.ts +4 -2
- package/dist/layer-integration.d.ts.map +1 -1
- package/dist/layer-integration.js +6 -2
- package/dist/layer-integration.js.map +1 -1
- package/dist/mcp-server.d.ts +8 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +1 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools/scan.d.ts.map +1 -1
- package/dist/mcp-tools/scan.js +9 -1
- package/dist/mcp-tools/scan.js.map +1 -1
- package/dist/measurement/enforce-lane.d.ts +66 -0
- package/dist/measurement/enforce-lane.d.ts.map +1 -0
- package/dist/measurement/enforce-lane.js +89 -0
- package/dist/measurement/enforce-lane.js.map +1 -0
- package/dist/measurement/from-eval-harness.d.ts +16 -3
- package/dist/measurement/from-eval-harness.d.ts.map +1 -1
- package/dist/measurement/from-eval-harness.js +15 -5
- package/dist/measurement/from-eval-harness.js.map +1 -1
- package/dist/measurement/in-sample-ablation.d.ts +29 -0
- package/dist/measurement/in-sample-ablation.d.ts.map +1 -0
- package/dist/measurement/in-sample-ablation.js +45 -0
- package/dist/measurement/in-sample-ablation.js.map +1 -0
- package/dist/measurement/schema.d.ts +38 -1
- package/dist/measurement/schema.d.ts.map +1 -1
- package/dist/measurement/schema.js +43 -1
- package/dist/measurement/schema.js.map +1 -1
- package/dist/quality/action-eligibility.d.ts +4 -2
- package/dist/quality/action-eligibility.d.ts.map +1 -1
- package/dist/quality/action-eligibility.js.map +1 -1
- package/dist/rule-confidence.d.ts +31 -0
- package/dist/rule-confidence.d.ts.map +1 -0
- package/dist/rule-confidence.js +49 -0
- package/dist/rule-confidence.js.map +1 -0
- package/dist/rule-scaffolder.d.ts.map +1 -1
- package/dist/rule-scaffolder.js +43 -4
- package/dist/rule-scaffolder.js.map +1 -1
- package/dist/semantic-evaluator.d.ts.map +1 -1
- package/dist/semantic-evaluator.js +15 -13
- package/dist/semantic-evaluator.js.map +1 -1
- package/dist/shadow-evaluator.d.ts +7 -2
- package/dist/shadow-evaluator.d.ts.map +1 -1
- package/dist/shadow-evaluator.js +29 -25
- package/dist/shadow-evaluator.js.map +1 -1
- package/dist/testcase-event.d.ts +82 -1
- package/dist/testcase-event.d.ts.map +1 -1
- package/dist/testcase-event.js +135 -16
- package/dist/testcase-event.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/tool-definition-scope.d.ts +45 -0
- package/dist/tool-definition-scope.d.ts.map +1 -0
- package/dist/tool-definition-scope.js +68 -0
- package/dist/tool-definition-scope.js.map +1 -0
- package/dist/types.d.ts +42 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/verdict.d.ts +46 -2
- package/dist/verdict.d.ts.map +1 -1
- package/dist/verdict.js +99 -14
- package/dist/verdict.js.map +1 -1
- package/package.json +2 -1
- package/rules/agent-manipulation/ATR-2026-00030-cross-agent-attack.yaml +2 -2
- package/rules/agent-manipulation/ATR-2026-00139-casual-authority-redirect.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00328-named-mode-persona-override.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00330-yoja-allcaps-settings-block-persona.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00418-weknora-mcp-config-rce.yaml +5 -5
- package/rules/agent-manipulation/ATR-2026-00432-superagi-output-handler-eval-rce.yaml +1 -1
- package/rules/agent-manipulation/ATR-2026-00440-semantic-kernel-vector-store-eval-rce.yaml +3 -3
- package/rules/context-exfiltration/ATR-2026-00426-nl-output-injection-credential-leak.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00702-ipi-credential-exfil-via-agent-action.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00707-ipi-file-exfil-with-destroy-or-tamper.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-00851-indirect-pi-financial-payment-exfil.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01457-sysprompt-completion-clone-attack.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01462-sysprompt-override-pseudo-admin.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01463-imend-imsystem-prompt-injection-format.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-01606-ssrf-internal-network-scan.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-02847-policy-justified-exfil-to-email.yaml +1 -1
- package/rules/context-exfiltration/ATR-2026-02855-cross-email-exfil-relay.yaml +79 -0
- package/rules/context-exfiltration/ATR-2026-02861-exfil-send-email-with-body-directive.yaml +83 -0
- package/rules/context-exfiltration/ATR-2026-02866-idna-label-separator-in-fetch-url-host.yaml +104 -0
- package/rules/excessive-autonomy/ATR-2026-00710-ipi-physical-access-grant.yaml +1 -1
- package/rules/model-abuse/ATR-2026-01897-host-sabotage.yaml +2 -2
- package/rules/model-security/ATR-2026-00433-modelcache-torch-load-deserialization-rce.yaml +1 -1
- package/rules/privilege-escalation/ATR-2026-00436-enclave-vm-sandbox-escape-rce.yaml +1 -1
- package/rules/privilege-escalation/ATR-2026-01601-sql-injection-destructive-ddl.yaml +2 -2
- package/rules/privilege-escalation/ATR-2026-01612-debug-mode-privilege-escalation.yaml +1 -1
- package/rules/privilege-escalation/ATR-2026-02867-identifier-typed-tool-argument-dot-dot-path-segment.yaml +106 -0
- package/rules/prompt-injection/ATR-2026-00004-system-prompt-override.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-00080-encoding-evasion.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00086-visual-spoofing.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00203-context-pollution-skill-description.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00228-structured-jailbreak.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-00233-structured-jailbreak.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-00256-base-n-encoding-jailbreak.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00264-latent-injection-translation.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00265-latent-injection-rag-document.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00282-perez-prompt-injection-hijack.yaml +4 -4
- package/rules/prompt-injection/ATR-2026-00321-hyphenated-system-instruction-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00394-backspace-deletion-character-injection.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-00446-variable-assignment-payload-injection.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00476-garak-web-injection-javascript-alert-1.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00481-garak-dan-mixedunassigned.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-00489-garak-encoding-javascript-alert-1.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00508-escape-delimiter-wrapped-goal-hijacking-in-user-input-prompt.yaml +3 -3
- package/rules/prompt-injection/ATR-2026-00512-rules-file-backdoor-injection.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01005-many-shot-repetition-override.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01006-payload-split-string-concat-injection.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01015-evasion-refusal-suppression.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01016-evasion-response-priming-forced-opener.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01026-evasion-unicode-glitch-token-anomaly.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-01304-indirect-injection-carrier-important-override.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01759-dos-new-important-instructions-injection-in-document.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-01865-llmail-fake-email-boundary-marker.yaml +1 -1
- package/rules/prompt-injection/ATR-2026-02010-emoji-only-payload.yaml +2 -2
- package/rules/prompt-injection/ATR-2026-02815-injected-halt-order-canned-reply-dos.yaml +80 -0
- package/rules/prompt-injection/ATR-2026-02820-tool-result-orders-agent-to-hide-its-own-action.yaml +106 -0
- package/rules/prompt-injection/ATR-2026-02858-context-suppression-directive.yaml +73 -0
- package/rules/prompt-injection/ATR-2026-02859-tool-output-agent-role-reassignment.yaml +71 -0
- package/rules/prompt-injection/ATR-2026-02860-forged-instruction-boundary-marker.yaml +70 -0
- package/rules/prompt-injection/ATR-2026-02862-fake-chat-template-turn-injection.yaml +79 -0
- package/rules/skill-compromise/ATR-2026-00060-skill-impersonation.yaml +1 -1
- package/rules/skill-compromise/ATR-2026-00063-skill-chain-attack.yaml +2 -2
- package/rules/skill-compromise/ATR-2026-00066-parameter-injection.yaml +2 -2
- package/rules/skill-compromise/ATR-2026-00149-skill-exfil-compound.yaml +6 -6
- package/rules/skill-compromise/ATR-2026-00223-reverse-shell-dropper.yaml +2 -2
- package/rules/skill-compromise/ATR-2026-01755-backdoor-pot-linguistic-trigger-phrase.yaml +1 -1
- package/rules/skill-compromise/ATR-2026-01756-backdoor-pot-symbol-emoticon-trigger.yaml +1 -1
- package/rules/skill-compromise/ATR-2026-02261-code-credential-exfil-chain.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-00012-unauthorized-tool-call.yaml +23 -23
- package/rules/tool-poisoning/ATR-2026-00013-tool-ssrf.yaml +16 -5
- package/rules/tool-poisoning/ATR-2026-00161-important-tag-cross-tool-shadowing.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-00277-echo-template-command-injection.yaml +2 -2
- package/rules/tool-poisoning/ATR-2026-00494-garak-exploitation-mixedunassigned.yaml +4 -4
- package/rules/tool-poisoning/ATR-2026-00581-mcp-tool-rug-pull-post-approval-redefinition.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-01303-tool-schema-enumeration-social-engineering.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-01307-mcp-dns-rebinding-attack.yaml +2 -2
- package/rules/tool-poisoning/ATR-2026-01775-semantic-mcp-tool-manifest-poisoning.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-01932-shadow-undeclared-mcp-server-registration.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-02025-mcp-full-schema-poisoning-nondescription-field.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-02817-untrusted-content-dictates-tool-arguments.yaml +83 -0
- package/rules/tool-poisoning/ATR-2026-02864-non-canonical-ipv4-host-in-url-tool-argument.yaml +102 -0
- package/rules/tool-poisoning/ATR-2026-02865-read-only-utility-write-flag-in-command-argument.yaml +157 -0
- package/rules/tool-poisoning/ATR-2026-02868-ssrf-ipv6-transition-address-embedded-private-ipv4.yaml +107 -0
- package/spec/README.md +6 -3
- package/spec/atr-event-v1.0.md +13 -8
- package/spec/atr-method-v1.1.md +2 -2
- package/spec/category-registry/gemara/README.md +102 -0
- package/spec/category-registry/gemara/ai-agent-capability-catalog.yaml +157 -0
- package/spec/category-registry/gemara/atr-attack-vector-catalog.yaml +130 -0
- package/spec/category-registry/gemara/atr-detection-control-catalog.yaml +327 -0
- package/spec/category-registry/gemara/atr-threat-catalog.yaml +246 -0
- package/spec/category-registry/gemara/src/editorial.yaml +743 -0
- package/spec/mappings/README.md +1 -1
- package/spec/mappings/atr-to-nist-csf-2.0.md +5 -4
- package/spec/schema/event.schema.json +1 -1
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements the external content-filter contract proposed in
|
|
5
5
|
* NVIDIA/OpenShell#1272 ("L7 content inspection hooks — scriptable
|
|
6
|
-
* request/response filtering")
|
|
6
|
+
* request/response filtering"). That proposal was closed as not planned on
|
|
7
|
+
* 2026-06-11 (OpenShell's maintainers pointed to their middleware RFC,
|
|
8
|
+
* NVIDIA/OpenShell#1733), so no OpenShell release runs this filter. The
|
|
9
|
+
* contract: the OpenShell *supervisor* (outside the agent
|
|
7
10
|
* sandbox) runs this as a script against L7 request/response bodies. The body
|
|
8
11
|
* arrives on stdin; the script signals the verdict via exit code:
|
|
9
12
|
*
|
|
@@ -13,10 +16,14 @@
|
|
|
13
16
|
*
|
|
14
17
|
* Two run modes:
|
|
15
18
|
* one-shot (default): read the whole body from stdin, evaluate, exit.
|
|
16
|
-
* Matches the #1272 contract literally. Cold-starts the engine per call
|
|
19
|
+
* Matches the #1272 contract literally. Cold-starts the engine per call:
|
|
20
|
+
* starting Node, loading the rule set and compiling its patterns take
|
|
21
|
+
* hundreds of milliseconds to about a second.
|
|
17
22
|
* serve (--serve): read NDJSON requests line-by-line from stdin and write
|
|
18
23
|
* one NDJSON verdict per line. Loads the rule set ONCE, so per-request
|
|
19
|
-
* latency is just regex evaluation
|
|
24
|
+
* latency is just regex evaluation: a median of about a millisecond for
|
|
25
|
+
* a short body, tens of milliseconds for a multi-kilobyte one
|
|
26
|
+
* (scripts/bench-evaluate-latency.ts). Intended path for a
|
|
20
27
|
* persistent guard service (cf. NVIDIA/OpenShell#1733 RFC 0005). A bad
|
|
21
28
|
* line (or an engine error) yields a per-line error verdict and the
|
|
22
29
|
* stream continues — one malformed body cannot take the inspector offline.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openshell-filter.d.ts","sourceRoot":"","sources":["../../src/adapters/openshell-filter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"openshell-filter.d.ts","sourceRoot":"","sources":["../../src/adapters/openshell-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AAMH,qFAAqF;AACrF,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAE/C,mDAAmD;AACnD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAmBxC,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,4BAA4B,EAAE,MAAM,CAAC;CAC/C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;CACvC;AAmCD;;;GAGG;AACH,qBAAa,eAAe;IAKd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,WAAW,CAAgC;gBAEtB,OAAO,EAAE,aAAa;IAKnD,kEAAkE;IAClE,OAAO,CAAC,YAAY;IAKpB;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;CAmBpD;AAID,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAGjE;AAOD,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CAUrE"}
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements the external content-filter contract proposed in
|
|
5
5
|
* NVIDIA/OpenShell#1272 ("L7 content inspection hooks — scriptable
|
|
6
|
-
* request/response filtering")
|
|
6
|
+
* request/response filtering"). That proposal was closed as not planned on
|
|
7
|
+
* 2026-06-11 (OpenShell's maintainers pointed to their middleware RFC,
|
|
8
|
+
* NVIDIA/OpenShell#1733), so no OpenShell release runs this filter. The
|
|
9
|
+
* contract: the OpenShell *supervisor* (outside the agent
|
|
7
10
|
* sandbox) runs this as a script against L7 request/response bodies. The body
|
|
8
11
|
* arrives on stdin; the script signals the verdict via exit code:
|
|
9
12
|
*
|
|
@@ -13,10 +16,14 @@
|
|
|
13
16
|
*
|
|
14
17
|
* Two run modes:
|
|
15
18
|
* one-shot (default): read the whole body from stdin, evaluate, exit.
|
|
16
|
-
* Matches the #1272 contract literally. Cold-starts the engine per call
|
|
19
|
+
* Matches the #1272 contract literally. Cold-starts the engine per call:
|
|
20
|
+
* starting Node, loading the rule set and compiling its patterns take
|
|
21
|
+
* hundreds of milliseconds to about a second.
|
|
17
22
|
* serve (--serve): read NDJSON requests line-by-line from stdin and write
|
|
18
23
|
* one NDJSON verdict per line. Loads the rule set ONCE, so per-request
|
|
19
|
-
* latency is just regex evaluation
|
|
24
|
+
* latency is just regex evaluation: a median of about a millisecond for
|
|
25
|
+
* a short body, tens of milliseconds for a multi-kilobyte one
|
|
26
|
+
* (scripts/bench-evaluate-latency.ts). Intended path for a
|
|
20
27
|
* persistent guard service (cf. NVIDIA/OpenShell#1733 RFC 0005). A bad
|
|
21
28
|
* line (or an engine error) yields a per-line error verdict and the
|
|
22
29
|
* stream continues — one malformed body cannot take the inspector offline.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openshell-filter.js","sourceRoot":"","sources":["../../src/adapters/openshell-filter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"openshell-filter.js","sourceRoot":"","sources":["../../src/adapters/openshell-filter.ts"],"names":[],"mappings":"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"}
|
|
@@ -75,7 +75,7 @@ export class StdioAdapter {
|
|
|
75
75
|
async alert(ctx) {
|
|
76
76
|
const alertMsg = {
|
|
77
77
|
type: "alert",
|
|
78
|
-
severity: ctx.verdict.highestSeverity,
|
|
78
|
+
severity: ctx.verdict.highestSeverity ?? ctx.verdict.advisorySeverity ?? null,
|
|
79
79
|
reason: ctx.verdict.reason,
|
|
80
80
|
matchCount: ctx.verdict.matchCount,
|
|
81
81
|
};
|
|
@@ -89,7 +89,7 @@ export class StdioAdapter {
|
|
|
89
89
|
if (process.env.ATR_SHADOW_LOG) {
|
|
90
90
|
const shadowMsg = {
|
|
91
91
|
type: "shadow",
|
|
92
|
-
severity: ctx.verdict.highestSeverity,
|
|
92
|
+
severity: ctx.verdict.highestSeverity ?? ctx.verdict.advisorySeverity ?? null,
|
|
93
93
|
reason: ctx.verdict.reason,
|
|
94
94
|
matchCount: ctx.verdict.matchCount,
|
|
95
95
|
ruleIds: ctx.matches.map((m) => m.rule.id),
|
|
@@ -116,7 +116,7 @@ export class StdioAdapter {
|
|
|
116
116
|
async escalate(ctx) {
|
|
117
117
|
const escalation = {
|
|
118
118
|
type: "escalation",
|
|
119
|
-
severity: ctx.verdict.highestSeverity,
|
|
119
|
+
severity: ctx.verdict.highestSeverity ?? ctx.verdict.advisorySeverity ?? null,
|
|
120
120
|
reason: ctx.verdict.reason,
|
|
121
121
|
matchCount: ctx.verdict.matchCount,
|
|
122
122
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio-adapter.js","sourceRoot":"","sources":["../../src/adapters/stdio-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,SAAS,UAAU,CACjB,MAA8B,EAC9B,OAAe;IAEf,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,MAAM;QACN,OAAO,EAAE,IAAI;QACb,OAAO;QACP,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,OAAO,YAAY;IACd,IAAI,GAAG,OAAO,CAAC;IACP,cAAc,GAAc,EAAE,CAAC;IAEhD;;;OAGG;IACH,cAAc;QACZ,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC;QACrD,IAAI,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAqB;QACpC,MAAM,KAAK,GAAG;YACZ,MAAM,EAAE,aAAa;YACrB,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO;YAC5B,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM;SAC3B,CAAC;QACF,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,UAAU,CAAC,aAAa,EAAE,kCAAkC,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,GAAqB;QACrC,MAAM,KAAK,GAAG;YACZ,MAAM,EAAE,cAAc;YACtB,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO;YAC5B,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM;SAC3B,CAAC;QACF,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,UAAU,CAAC,cAAc,EAAE,mCAAmC,CAAC,CAAC;IACzE,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAqB;QACnC,MAAM,KAAK,GAAG;YACZ,MAAM,EAAE,YAAY;YACpB,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO;YAC5B,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM;YAC1B,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,WAAW,CAAC,IAAI,SAAS;SACnD,CAAC;QACF,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,UAAU,CAAC,YAAY,EAAE,iCAAiC,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,GAAqB;QAC3C,MAAM,KAAK,GAAG;YACZ,MAAM,EAAE,oBAAoB;YAC5B,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO;YAC5B,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS;SACtC,CAAC;QACF,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,UAAU,CACf,oBAAoB,EACpB,wCAAwC,CACzC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,GAAqB;QACtC,MAAM,KAAK,GAAG;YACZ,MAAM,EAAE,eAAe;YACvB,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,OAAO;SAC7B,CAAC;QACF,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,UAAU,CAAC,eAAe,EAAE,kCAAkC,CAAC,CAAC;IACzE,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAqB;QAC/B,MAAM,QAAQ,GAAG;YACf,IAAI,EAAE,OAAO;YACb,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,eAAe;
|
|
1
|
+
{"version":3,"file":"stdio-adapter.js","sourceRoot":"","sources":["../../src/adapters/stdio-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An MCP `tools/list` document is a list of tool definitions, not an event.
|
|
3
|
+
*
|
|
4
|
+
* `atr scan tools.json` on `{"tools":[{"name":"add","description":"...","inputSchema":{...}}]}`
|
|
5
|
+
* — or on the JSON-RPC response `{"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}` — used to
|
|
6
|
+
* wrap the document as one event, find no `content` on it, skip it, and report
|
|
7
|
+
* "nothing was scanned". That is the input a tool-poisoning check most often starts from.
|
|
8
|
+
*
|
|
9
|
+
* Each tool becomes its own `mcp_exchange` event:
|
|
10
|
+
* - `content` is the tool definition as JSON, the whole object. It carries the name, the
|
|
11
|
+
* description and the input schema, including per-parameter descriptions, extra property
|
|
12
|
+
* fields, `required` and defaults: every one of those reaches the model, and the
|
|
13
|
+
* full-schema-poisoning rules anchor on the schema structure (`inputSchema`, `properties`).
|
|
14
|
+
* - `fields.tool_name` is the tool's name and `fields.tool_description` its top-level
|
|
15
|
+
* description, for the rules that read those fields. They are set only when the definition
|
|
16
|
+
* carries them as strings, never filled with something else.
|
|
17
|
+
* One event per tool keeps a finding attributable to a tool name.
|
|
18
|
+
*
|
|
19
|
+
* The CLI scans these events on an engine constructed with `toolDefinitions: true`, so the rules
|
|
20
|
+
* written for a tool being called do not run on them (src/tool-definition-scope.ts).
|
|
21
|
+
*
|
|
22
|
+
* @module agent-threat-rules/cli/mcp-tools-document
|
|
23
|
+
*/
|
|
24
|
+
import type { AgentEvent } from '../types.js';
|
|
25
|
+
type JsonObject = Record<string, unknown>;
|
|
26
|
+
/** One mcp_exchange event for one tool definition. */
|
|
27
|
+
export declare function toolDefinitionToEvent(tool: JsonObject, timestamp: string): AgentEvent;
|
|
28
|
+
/**
|
|
29
|
+
* Events for a `tools/list` document, or null when the input is not one.
|
|
30
|
+
*
|
|
31
|
+
* Accepted: `{"tools":[...]}`, `{"result":{"tools":[...]}}` (a JSON-RPC response), and a
|
|
32
|
+
* non-empty JSON array whose every element is one of those (a log of several responses).
|
|
33
|
+
* A document that lists no tools yields an empty array, so the caller reports that nothing
|
|
34
|
+
* was scanned instead of a clean result. A tools entry that is not an object yields an event
|
|
35
|
+
* with no content, which the caller counts as skipped. An object that is already an event (string
|
|
36
|
+
* content, or an event type) is never read as a tools/list document, tools array or not.
|
|
37
|
+
*/
|
|
38
|
+
export declare function toolsDocumentToEvents(parsed: unknown): AgentEvent[] | null;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=mcp-tools-document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-tools-document.d.ts","sourceRoot":"","sources":["../../src/cli/mcp-tools-document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAoC1C,sDAAsD;AACtD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CAarF;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,UAAU,EAAE,GAAG,IAAI,CAc1E"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An MCP `tools/list` document is a list of tool definitions, not an event.
|
|
3
|
+
*
|
|
4
|
+
* `atr scan tools.json` on `{"tools":[{"name":"add","description":"...","inputSchema":{...}}]}`
|
|
5
|
+
* — or on the JSON-RPC response `{"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}` — used to
|
|
6
|
+
* wrap the document as one event, find no `content` on it, skip it, and report
|
|
7
|
+
* "nothing was scanned". That is the input a tool-poisoning check most often starts from.
|
|
8
|
+
*
|
|
9
|
+
* Each tool becomes its own `mcp_exchange` event:
|
|
10
|
+
* - `content` is the tool definition as JSON, the whole object. It carries the name, the
|
|
11
|
+
* description and the input schema, including per-parameter descriptions, extra property
|
|
12
|
+
* fields, `required` and defaults: every one of those reaches the model, and the
|
|
13
|
+
* full-schema-poisoning rules anchor on the schema structure (`inputSchema`, `properties`).
|
|
14
|
+
* - `fields.tool_name` is the tool's name and `fields.tool_description` its top-level
|
|
15
|
+
* description, for the rules that read those fields. They are set only when the definition
|
|
16
|
+
* carries them as strings, never filled with something else.
|
|
17
|
+
* One event per tool keeps a finding attributable to a tool name.
|
|
18
|
+
*
|
|
19
|
+
* The CLI scans these events on an engine constructed with `toolDefinitions: true`, so the rules
|
|
20
|
+
* written for a tool being called do not run on them (src/tool-definition-scope.ts).
|
|
21
|
+
*
|
|
22
|
+
* @module agent-threat-rules/cli/mcp-tools-document
|
|
23
|
+
*/
|
|
24
|
+
function isObject(value) {
|
|
25
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
/** Event types an AgentEvent carries (src/types.ts AgentEvent['type']). */
|
|
28
|
+
const EVENT_TYPES = new Set([
|
|
29
|
+
'llm_input',
|
|
30
|
+
'llm_output',
|
|
31
|
+
'tool_call',
|
|
32
|
+
'tool_response',
|
|
33
|
+
'mcp_exchange',
|
|
34
|
+
'agent_behavior',
|
|
35
|
+
'multi_agent_message',
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* An object that is already an event: string `content`, or a `type` that is an event type. Such an
|
|
39
|
+
* object stays on the event path even when it also carries a `tools` array (an LLM request logged
|
|
40
|
+
* with its tool list, for example): reading it as a tools/list document would drop its content.
|
|
41
|
+
*/
|
|
42
|
+
function looksLikeEvent(doc) {
|
|
43
|
+
return typeof doc['content'] === 'string' || (typeof doc['type'] === 'string' && EVENT_TYPES.has(doc['type']));
|
|
44
|
+
}
|
|
45
|
+
/** The tools array of a `tools/list` result, bare or inside a JSON-RPC envelope; else null. */
|
|
46
|
+
function toolsArrayOf(doc) {
|
|
47
|
+
if (!isObject(doc))
|
|
48
|
+
return null;
|
|
49
|
+
if (looksLikeEvent(doc))
|
|
50
|
+
return null;
|
|
51
|
+
if (Array.isArray(doc['tools']))
|
|
52
|
+
return doc['tools'];
|
|
53
|
+
const result = doc['result'];
|
|
54
|
+
if (isObject(result) && Array.isArray(result['tools']))
|
|
55
|
+
return result['tools'];
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
/** One mcp_exchange event for one tool definition. */
|
|
59
|
+
export function toolDefinitionToEvent(tool, timestamp) {
|
|
60
|
+
const name = tool['name'];
|
|
61
|
+
const description = tool['description'];
|
|
62
|
+
const fields = {
|
|
63
|
+
...(typeof name === 'string' && name !== '' ? { tool_name: name } : {}),
|
|
64
|
+
...(typeof description === 'string' && description !== '' ? { tool_description: description } : {}),
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
type: 'mcp_exchange',
|
|
68
|
+
timestamp,
|
|
69
|
+
content: JSON.stringify(tool),
|
|
70
|
+
...(Object.keys(fields).length > 0 ? { fields } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Events for a `tools/list` document, or null when the input is not one.
|
|
75
|
+
*
|
|
76
|
+
* Accepted: `{"tools":[...]}`, `{"result":{"tools":[...]}}` (a JSON-RPC response), and a
|
|
77
|
+
* non-empty JSON array whose every element is one of those (a log of several responses).
|
|
78
|
+
* A document that lists no tools yields an empty array, so the caller reports that nothing
|
|
79
|
+
* was scanned instead of a clean result. A tools entry that is not an object yields an event
|
|
80
|
+
* with no content, which the caller counts as skipped. An object that is already an event (string
|
|
81
|
+
* content, or an event type) is never read as a tools/list document, tools array or not.
|
|
82
|
+
*/
|
|
83
|
+
export function toolsDocumentToEvents(parsed) {
|
|
84
|
+
const documents = Array.isArray(parsed) ? parsed : [parsed];
|
|
85
|
+
if (documents.length === 0)
|
|
86
|
+
return null;
|
|
87
|
+
const toolLists = documents.map(toolsArrayOf);
|
|
88
|
+
if (toolLists.some((list) => list === null))
|
|
89
|
+
return null;
|
|
90
|
+
const timestamp = new Date().toISOString();
|
|
91
|
+
return toolLists.flatMap((tools) => tools.map((tool) => isObject(tool)
|
|
92
|
+
? toolDefinitionToEvent(tool, timestamp)
|
|
93
|
+
: { type: 'mcp_exchange', timestamp, content: '' }));
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=mcp-tools-document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-tools-document.js","sourceRoot":"","sources":["../../src/cli/mcp-tools-document.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { ScanType } from '../types.js';
|
|
8
8
|
import type { Lane } from '../quality/rule-contract.js';
|
|
9
|
+
import { type ModelDecisions } from '../verdict.js';
|
|
9
10
|
export interface ScanOptions {
|
|
10
11
|
readonly rules?: string;
|
|
11
12
|
readonly json?: boolean;
|
|
@@ -21,6 +22,13 @@ export interface ScanOptions {
|
|
|
21
22
|
readonly semanticModel?: string;
|
|
22
23
|
readonly semanticTimeout?: string;
|
|
23
24
|
readonly semanticNoJsonMode?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* What a match a model decided (here, a semantic rule's judge) may do.
|
|
27
|
+
* 'advisory' (default): listed, marked, and not counted toward --fail-on, and
|
|
28
|
+
* written to SARIF at level note with properties.enforced false. 'enforce':
|
|
29
|
+
* counted and written like any match.
|
|
30
|
+
*/
|
|
31
|
+
readonly modelDecisions?: ModelDecisions;
|
|
24
32
|
/**
|
|
25
33
|
* Detection lane (which rule maturities may fire). Undefined means the
|
|
26
34
|
* engine's built-in default; `src/cli.ts` resolves `--lane` and `ATR_LANE`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan-handler.d.ts","sourceRoot":"","sources":["../../src/cli/scan-handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAoC,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"scan-handler.d.ts","sourceRoot":"","sources":["../../src/cli/scan-handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAoC,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AAIxD,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAoBpE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;CACtB;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,QAAQ,CA0B5D;AAED,6FAA6F;AAC7F,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CA+Df"}
|
package/dist/cli/scan-handler.js
CHANGED
|
@@ -10,6 +10,8 @@ import { ATREngine } from '../engine.js';
|
|
|
10
10
|
import { scanResultToSARIF } from '../converters/sarif.js';
|
|
11
11
|
import { createTCReporter, resolveTcUrl } from '../tc-reporter.js';
|
|
12
12
|
import { createSemanticJudgeFromConfig } from './semantic-judge-config.js';
|
|
13
|
+
import { isModelDecided } from '../verdict.js';
|
|
14
|
+
import { toolsDocumentToEvents } from './mcp-tools-document.js';
|
|
13
15
|
const SEVERITY_ORDER = ['informational', 'low', 'medium', 'high', 'critical'];
|
|
14
16
|
// ANSI colors
|
|
15
17
|
const RED = '\x1b[31m';
|
|
@@ -114,6 +116,36 @@ export async function cmdScanUnified(target, rulesDir, options) {
|
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
118
|
// ── MCP Event Scan ─────────────────────────────────────────────
|
|
119
|
+
/**
|
|
120
|
+
* A `.mcp.json` is a configuration document, not an event.
|
|
121
|
+
*
|
|
122
|
+
* `atr scan .mcp.json` — the invocation the README shows — parsed the file, wrapped it as one
|
|
123
|
+
* event, found no `content` on it and skipped it, while the summary still said one event had been
|
|
124
|
+
* scanned. The rules never saw a single server, so a config that sets `NODE_OPTIONS` to a require
|
|
125
|
+
* payload came back as `threats_detected: 0` instead of as an error.
|
|
126
|
+
*
|
|
127
|
+
* Each server becomes its own event, with the config as its content, which is the shape the MCP
|
|
128
|
+
* rules evaluate (ATR-2026-02300, the rule written for exactly this `.mcp.json` env-block surface,
|
|
129
|
+
* fires on it — see tests/cli-mcp-config-scan.test.ts). One event per server also keeps a finding
|
|
130
|
+
* attributable to a server name.
|
|
131
|
+
*/
|
|
132
|
+
function configDocumentToEvents(parsed) {
|
|
133
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
134
|
+
return null;
|
|
135
|
+
const servers = parsed.mcpServers;
|
|
136
|
+
if (!servers || typeof servers !== 'object' || Array.isArray(servers))
|
|
137
|
+
return null;
|
|
138
|
+
const timestamp = new Date().toISOString();
|
|
139
|
+
const events = [];
|
|
140
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
141
|
+
events.push({
|
|
142
|
+
type: 'mcp_exchange',
|
|
143
|
+
timestamp,
|
|
144
|
+
content: JSON.stringify({ mcpServers: { [name]: config } }),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return events.length > 0 ? events : null;
|
|
148
|
+
}
|
|
117
149
|
async function scanMcpEvents(eventsPath, rulesDir, options, reporter) {
|
|
118
150
|
const fileStat = statSync(eventsPath);
|
|
119
151
|
if (fileStat.size > 50 * 1024 * 1024) {
|
|
@@ -122,9 +154,18 @@ async function scanMcpEvents(eventsPath, rulesDir, options, reporter) {
|
|
|
122
154
|
}
|
|
123
155
|
const raw = readFileSync(eventsPath, 'utf-8');
|
|
124
156
|
let events;
|
|
157
|
+
// Whether the input is a tools/list document: every event is then a tool definition, and the
|
|
158
|
+
// engine is told so (toolDefinitions), so the rules written for a tool being called do not run
|
|
159
|
+
// on it (src/tool-definition-scope.ts). Config documents and event files are scanned as before.
|
|
160
|
+
let toolDefinitions = false;
|
|
125
161
|
try {
|
|
126
162
|
const parsed = JSON.parse(raw);
|
|
127
|
-
|
|
163
|
+
// A config document becomes one event per server, a tools/list document one event per
|
|
164
|
+
// tool; every other input is unchanged.
|
|
165
|
+
const configEvents = configDocumentToEvents(parsed);
|
|
166
|
+
const toolEvents = configEvents === null ? toolsDocumentToEvents(parsed) : null;
|
|
167
|
+
toolDefinitions = toolEvents !== null;
|
|
168
|
+
events = configEvents ?? toolEvents ?? (Array.isArray(parsed) ? parsed : [parsed]);
|
|
128
169
|
}
|
|
129
170
|
catch {
|
|
130
171
|
console.error(`${RED}Error: Invalid JSON in ${eventsPath}${RESET}`);
|
|
@@ -136,7 +177,10 @@ async function scanMcpEvents(eventsPath, rulesDir, options, reporter) {
|
|
|
136
177
|
reporter,
|
|
137
178
|
semanticJudge: semantic.judge,
|
|
138
179
|
...(options.lane ? { lane: options.lane } : {}),
|
|
180
|
+
...(options.modelDecisions ? { modelDecisions: options.modelDecisions } : {}),
|
|
181
|
+
...(toolDefinitions ? { toolDefinitions: true } : {}),
|
|
139
182
|
});
|
|
183
|
+
const heldBack = (m) => engine.getModelDecisions() === 'advisory' && isModelDecided(m);
|
|
140
184
|
await engine.loadRules();
|
|
141
185
|
if (semantic.enabled && !options.json && !options.sarif) {
|
|
142
186
|
console.error(`${DIM}Semantic judge: enabled for method=semantic rules${RESET}`);
|
|
@@ -146,9 +190,14 @@ async function scanMcpEvents(eventsPath, rulesDir, options, reporter) {
|
|
|
146
190
|
const allResults = [];
|
|
147
191
|
let totalThreats = 0;
|
|
148
192
|
let failHits = 0;
|
|
193
|
+
let eventsEvaluated = 0;
|
|
194
|
+
let eventsSkipped = 0;
|
|
149
195
|
for (const event of events) {
|
|
150
|
-
if (!event.content)
|
|
151
|
-
|
|
196
|
+
if (!event.content) {
|
|
197
|
+
eventsSkipped += 1; // skip malformed events, but never silently
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
eventsEvaluated += 1;
|
|
152
201
|
const result = semantic.enabled
|
|
153
202
|
? await engine.evaluateFullAsync(event, eventsPath)
|
|
154
203
|
: engine.evaluateFull(event, eventsPath);
|
|
@@ -157,48 +206,85 @@ async function scanMcpEvents(eventsPath, rulesDir, options, reporter) {
|
|
|
157
206
|
allResults.push({ event, result, filtered });
|
|
158
207
|
totalThreats += filtered.length;
|
|
159
208
|
if (failIdx >= 0) {
|
|
160
|
-
failHits += filtered.filter((m) => SEVERITY_ORDER.indexOf(m.rule.severity) >= failIdx).length;
|
|
209
|
+
failHits += filtered.filter((m) => !heldBack(m) && SEVERITY_ORDER.indexOf(m.rule.severity) >= failIdx).length;
|
|
161
210
|
}
|
|
162
211
|
}
|
|
163
212
|
}
|
|
213
|
+
// An event that never reached the engine is not a clean result, and the counts have to say so:
|
|
214
|
+
// `events_scanned` used to be `events.length`, computed before the skip.
|
|
215
|
+
if (events.length === 0) {
|
|
216
|
+
console.error(`${RED}Error: nothing was scanned — the input contains no events and no tools${RESET}`);
|
|
217
|
+
}
|
|
218
|
+
else if (eventsEvaluated === 0) {
|
|
219
|
+
console.error(`${RED}Error: nothing was scanned — ${eventsSkipped} event(s) had no content${RESET}`);
|
|
220
|
+
}
|
|
221
|
+
else if (eventsSkipped > 0) {
|
|
222
|
+
console.error(`${DIM}Warning: ${eventsSkipped} event(s) had no content and were not scanned${RESET}`);
|
|
223
|
+
}
|
|
224
|
+
// On a tools/list document some rules do not run (src/tool-definition-scope.ts): say so, and how
|
|
225
|
+
// many, so "not run" cannot read as "ran and found nothing"; and name the tool of each result.
|
|
226
|
+
const scope = engine.getToolDefinitionScope();
|
|
227
|
+
const scopeReport = scope
|
|
228
|
+
? { tool_definition_scope: true, rules_evaluated: scope.rulesEvaluated, rules_skipped_by_scope: scope.rulesSkippedByScope }
|
|
229
|
+
: {};
|
|
230
|
+
const toolNameOf = (event) => toolDefinitions ? event.fields?.['tool_name'] : undefined;
|
|
164
231
|
if (options.sarif) {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
matches: filtered,
|
|
168
|
-
|
|
169
|
-
|
|
232
|
+
const toolNames = new Map();
|
|
233
|
+
const sarifResults = allResults.map(({ event, result, filtered }) => {
|
|
234
|
+
const scoped = { ...result, matches: filtered, threat_count: filtered.length };
|
|
235
|
+
const name = toolNameOf(event);
|
|
236
|
+
if (name !== undefined)
|
|
237
|
+
toolNames.set(scoped, name);
|
|
238
|
+
return scoped;
|
|
239
|
+
});
|
|
170
240
|
const version = process.env['npm_package_version'] ?? '1.0.0';
|
|
171
|
-
console.log(JSON.stringify(scanResultToSARIF(sarifResults, version
|
|
172
|
-
|
|
241
|
+
console.log(JSON.stringify(scanResultToSARIF(sarifResults, version, {
|
|
242
|
+
heldBack,
|
|
243
|
+
...(scope ? { runProperties: scopeReport } : {}),
|
|
244
|
+
resultProperties: (r) => (toolNames.has(r) ? { tool_name: toolNames.get(r) } : undefined),
|
|
245
|
+
}), null, 2));
|
|
246
|
+
return eventsEvaluated === 0 ? 1 : failHits;
|
|
173
247
|
}
|
|
174
248
|
if (options.json) {
|
|
175
249
|
console.log(JSON.stringify({
|
|
176
250
|
scan_type: 'mcp',
|
|
177
|
-
events_scanned:
|
|
251
|
+
events_scanned: eventsEvaluated,
|
|
252
|
+
events_skipped: eventsSkipped,
|
|
178
253
|
threats_detected: totalThreats,
|
|
179
254
|
rules_loaded: engine.getRuleCount(),
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
event
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
255
|
+
...scopeReport,
|
|
256
|
+
results: allResults.map(({ event, result, filtered }) => {
|
|
257
|
+
const toolName = toolNameOf(event);
|
|
258
|
+
return {
|
|
259
|
+
content_hash: result.content_hash,
|
|
260
|
+
event: {
|
|
261
|
+
type: event.type,
|
|
262
|
+
timestamp: event.timestamp,
|
|
263
|
+
...(toolName !== undefined ? { tool_name: toolName } : {}),
|
|
264
|
+
content_preview: event.content.slice(0, 100),
|
|
265
|
+
},
|
|
266
|
+
matches: filtered.map((m) => formatMatchJson(m, heldBack(m))),
|
|
267
|
+
};
|
|
268
|
+
}),
|
|
189
269
|
}, null, 2));
|
|
190
|
-
return failHits;
|
|
270
|
+
return eventsEvaluated === 0 ? 1 : failHits;
|
|
271
|
+
}
|
|
272
|
+
printScanHeader('MCP', eventsEvaluated, engine.getRuleCount(), totalThreats);
|
|
273
|
+
if (scope) {
|
|
274
|
+
console.log(`${DIM} Tool definitions: ${scope.rulesSkippedByScope} rule(s) written for a tool being called were not run; ${scope.rulesEvaluated} were.${RESET}\n`);
|
|
191
275
|
}
|
|
192
|
-
printScanHeader('MCP', events.length, engine.getRuleCount(), totalThreats);
|
|
193
276
|
if (totalThreats === 0) {
|
|
194
|
-
console.log(
|
|
195
|
-
|
|
277
|
+
console.log(eventsEvaluated === 0
|
|
278
|
+
? `${RED}Nothing was scanned.${RESET}\n`
|
|
279
|
+
: `${GREEN}No threats detected.${RESET}\n`);
|
|
280
|
+
return eventsEvaluated === 0 ? 1 : failHits;
|
|
196
281
|
}
|
|
197
282
|
for (const { event, filtered } of allResults) {
|
|
198
283
|
const preview = event.content.slice(0, 80).replace(/\n/g, ' ');
|
|
199
|
-
|
|
284
|
+
const toolName = toolNameOf(event);
|
|
285
|
+
console.log(` ${DIM}Event: [${event.type}]${toolName !== undefined ? ` tool ${toolName}` : ''} "${preview}..."${RESET}`);
|
|
200
286
|
for (const m of filtered) {
|
|
201
|
-
printMatch(m);
|
|
287
|
+
printMatch(m, heldBack(m));
|
|
202
288
|
}
|
|
203
289
|
console.log('');
|
|
204
290
|
}
|
|
@@ -217,7 +303,9 @@ async function scanSkillFiles(targetPath, rulesDir, options, reporter) {
|
|
|
217
303
|
reporter,
|
|
218
304
|
semanticJudge: semantic.judge,
|
|
219
305
|
...(options.lane ? { lane: options.lane } : {}),
|
|
306
|
+
...(options.modelDecisions ? { modelDecisions: options.modelDecisions } : {}),
|
|
220
307
|
});
|
|
308
|
+
const heldBack = (m) => engine.getModelDecisions() === 'advisory' && isModelDecided(m);
|
|
221
309
|
await engine.loadRules();
|
|
222
310
|
if (semantic.enabled && !options.json && !options.sarif) {
|
|
223
311
|
console.error(`${DIM}Semantic judge: enabled for method=semantic rules${RESET}`);
|
|
@@ -242,7 +330,7 @@ async function scanSkillFiles(targetPath, rulesDir, options, reporter) {
|
|
|
242
330
|
allResults.push({ file, result, filtered });
|
|
243
331
|
totalThreats += filtered.length;
|
|
244
332
|
if (failIdx >= 0) {
|
|
245
|
-
failHits += filtered.filter((m) => SEVERITY_ORDER.indexOf(m.rule.severity) >= failIdx).length;
|
|
333
|
+
failHits += filtered.filter((m) => !heldBack(m) && SEVERITY_ORDER.indexOf(m.rule.severity) >= failIdx).length;
|
|
246
334
|
}
|
|
247
335
|
}
|
|
248
336
|
}
|
|
@@ -253,7 +341,7 @@ async function scanSkillFiles(targetPath, rulesDir, options, reporter) {
|
|
|
253
341
|
threat_count: filtered.length,
|
|
254
342
|
}));
|
|
255
343
|
const version = process.env['npm_package_version'] ?? '1.0.0';
|
|
256
|
-
console.log(JSON.stringify(scanResultToSARIF(sarifResults, version), null, 2));
|
|
344
|
+
console.log(JSON.stringify(scanResultToSARIF(sarifResults, version, { heldBack }), null, 2));
|
|
257
345
|
return failHits;
|
|
258
346
|
}
|
|
259
347
|
if (options.json) {
|
|
@@ -265,7 +353,7 @@ async function scanSkillFiles(targetPath, rulesDir, options, reporter) {
|
|
|
265
353
|
results: allResults.map(({ file, result, filtered }) => ({
|
|
266
354
|
file,
|
|
267
355
|
content_hash: result.content_hash,
|
|
268
|
-
matches: filtered.map(formatMatchJson),
|
|
356
|
+
matches: filtered.map((m) => formatMatchJson(m, heldBack(m))),
|
|
269
357
|
})),
|
|
270
358
|
}, null, 2));
|
|
271
359
|
return failHits;
|
|
@@ -279,7 +367,7 @@ async function scanSkillFiles(targetPath, rulesDir, options, reporter) {
|
|
|
279
367
|
const relPath = file.replace(process.cwd() + '/', '');
|
|
280
368
|
console.log(` ${BOLD}${relPath}${RESET}`);
|
|
281
369
|
for (const m of filtered) {
|
|
282
|
-
printMatch(m);
|
|
370
|
+
printMatch(m, heldBack(m));
|
|
283
371
|
}
|
|
284
372
|
console.log('');
|
|
285
373
|
}
|
|
@@ -318,13 +406,16 @@ function walkForSkills(dir, out) {
|
|
|
318
406
|
}
|
|
319
407
|
}
|
|
320
408
|
}
|
|
321
|
-
function formatMatchJson(m) {
|
|
409
|
+
function formatMatchJson(m, heldBack) {
|
|
322
410
|
return {
|
|
323
411
|
rule_id: m.rule.id,
|
|
324
412
|
title: m.rule.title,
|
|
325
413
|
severity: m.rule.severity,
|
|
326
414
|
confidence: m.confidence,
|
|
327
415
|
matched_conditions: m.matchedConditions,
|
|
416
|
+
decided_by: m.decidedBy ?? null,
|
|
417
|
+
// false: a model decided it and it does not count toward --fail-on.
|
|
418
|
+
enforced: !heldBack,
|
|
328
419
|
};
|
|
329
420
|
}
|
|
330
421
|
function printScanHeader(type, scanned, rulesLoaded, threats) {
|
|
@@ -338,9 +429,10 @@ function printScanHeader(type, scanned, rulesLoaded, threats) {
|
|
|
338
429
|
console.log(`${DIM} Open source (MIT). Star: https://github.com/Agent-Threat-Rule/agent-threat-rules${RESET}`);
|
|
339
430
|
console.log('');
|
|
340
431
|
}
|
|
341
|
-
function printMatch(m) {
|
|
432
|
+
function printMatch(m, heldBack) {
|
|
342
433
|
const color = SEVERITY_COLORS[m.rule.severity] ?? '';
|
|
343
|
-
|
|
434
|
+
const note = heldBack ? ` ${DIM}(decided by ${m.decidedBy}: reported, not counted toward --fail-on)${RESET}` : '';
|
|
435
|
+
console.log(` ${color}${m.rule.severity.toUpperCase().padEnd(13)}${RESET} ${m.rule.id} - ${m.rule.title}${note}`);
|
|
344
436
|
console.log(` ${DIM}Confidence: ${(m.confidence * 100).toFixed(0)}% | Conditions: ${m.matchedConditions.join(', ')}${RESET}`);
|
|
345
437
|
}
|
|
346
438
|
//# sourceMappingURL=scan-handler.js.map
|