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
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,670 @@ All notable changes to ATR will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [4.2.0] - 2026-10-24
|
|
8
|
+
|
|
9
|
+
The first release since 4.1.5. For an integrator it changes three things at
|
|
10
|
+
runtime: the Claude Code guard reads what a tool returned instead of what it
|
|
11
|
+
was given, rules written for `context_window` and `skill_*` events now run on
|
|
12
|
+
the events their data arrives in, and a match that a model decided is
|
|
13
|
+
reported instead of enforced unless the integrator opts in. `atr scan` now
|
|
14
|
+
scans an MCP config file it used to skip, and an MCP `tools/list` document
|
|
15
|
+
as one event per tool, without running the rules written for a tool being
|
|
16
|
+
called. Many rule conditions that took polynomial time on long input were
|
|
17
|
+
rewritten, and three rules left the enforce
|
|
18
|
+
lane after matching benign samples. Like 4.1.5, the npm package carries the
|
|
19
|
+
rules without their test samples; the repository keeps them.
|
|
20
|
+
|
|
21
|
+
Default behaviour that changes, and how to keep the old one:
|
|
22
|
+
|
|
23
|
+
- Model-decided matches are advisory. A match from a semantic rule's judge
|
|
24
|
+
that its own pattern fallback does not corroborate, from the embedding
|
|
25
|
+
module or from the semantic layer is listed in `verdict.advisory` and no
|
|
26
|
+
longer sets the verdict's outcome or dispatches more than observe-tier
|
|
27
|
+
actions. To keep the old behaviour pass `modelDecisions: 'enforce'` to
|
|
28
|
+
`ATREngine` or `computeVerdict()`, or use `--model-decisions enforce` or
|
|
29
|
+
`ATR_MODEL_DECISIONS=enforce` with `atr scan` and `atr mcp`. Integrations
|
|
30
|
+
that wire no model, including `atr guard` and the bundled adapters, see no
|
|
31
|
+
change (#720). `verdict.highestSeverity` and `highestConfidence` now
|
|
32
|
+
describe only the matches the outcome came from (read `advisorySeverity`
|
|
33
|
+
for the rest), and `atr scan --json` gives every match `decided_by` and
|
|
34
|
+
`enforced`.
|
|
35
|
+
- More of what an agent sees reaches the rules. A guard registered under
|
|
36
|
+
Claude Code's PostToolUse now scans the tool's output, where it used to scan
|
|
37
|
+
the tool's input; registering it under PostToolUseFailure as well also scans
|
|
38
|
+
failed calls (#715, #721). Rules of type `context_window` and `skill_*` now
|
|
39
|
+
run on typed runtime events (#701). The OpenShell filter
|
|
40
|
+
and NemoClaw preflight adapters evaluate in the hunt lane and deny at `high`
|
|
41
|
+
and above by default (`ATR_MIN_SEVERITY`), so they can now deny on
|
|
42
|
+
ATR-2026-00070 and -01774 (high, `llm_input`/`llm_output`) and
|
|
43
|
+
ATR-2026-00157 and -02405 (critical, `tool_call`), where 4.1.5 did not. Both raise matches in
|
|
44
|
+
the hunt lane, the engine default, including on benign text (measured
|
|
45
|
+
figures under Fixed below); neither moves the enforce lane. There is no
|
|
46
|
+
switch back. An integration that blocks on PostToolUse should block on the
|
|
47
|
+
enforce lane: `atr guard --blocking --lane enforce` (or `ATR_BLOCKING=1
|
|
48
|
+
ATR_LANE=enforce`), or `lane: 'enforce'` on `ATREngine`. The guard's default
|
|
49
|
+
lane is hunt, so `--blocking` alone blocks on the hunt-lane matches above.
|
|
50
|
+
- ATR-2026-00080, -00436 and -01606 moved from maturity `stable` to `test`
|
|
51
|
+
after matching benign samples in the grown corpus (#625). They still alert;
|
|
52
|
+
an integration that blocks only in the enforce lane no longer blocks on
|
|
53
|
+
them.
|
|
54
|
+
- ATR-2026-00012 moved from maturity `test` to `experimental` (#683), so it
|
|
55
|
+
runs in the hunt lane only and no longer in the alert lane. ATR-2026-00233,
|
|
56
|
+
a duplicate of ATR-2026-00228, is deprecated and runs in no lane (#666).
|
|
57
|
+
- `atr scan` on an MCP config file scans one event per server (#581, by
|
|
58
|
+
@ciceroyang), and on an MCP `tools/list` document, bare or in a JSON-RPC
|
|
59
|
+
response, one event per tool (#737). 4.1.5 skipped both and reported them
|
|
60
|
+
clean. Any JSON file `atr scan` reads in which no event reaches the engine
|
|
61
|
+
now exits 1, in text, `--json` and `--sarif` output alike; `events_scanned`
|
|
62
|
+
counts only events that did, beside a new `events_skipped`. A CI step that ran
|
|
63
|
+
`atr scan` on such a file and always passed may now fail, on the exit code or
|
|
64
|
+
through `--fail-on`. The GitHub Action ignores the exit code (#575) but counts
|
|
65
|
+
the new findings toward `fail-on-finding`. Scanning a directory that holds
|
|
66
|
+
only an MCP config is not fixed yet (#575).
|
|
67
|
+
|
|
68
|
+
Under SPEC §10 this is a minor release: the rule format, identifier scheme and
|
|
69
|
+
Match output are unchanged (matches gain an optional `decidedBy`, which §7
|
|
70
|
+
allows). SPEC.md moves to 1.0.1, a [Compatible] correction of §6.4 (#699).
|
|
71
|
+
SPEC §10 versions the format, not the package's defaults, and some defaults
|
|
72
|
+
change above. 4.0.0 went major because every `atr guard` user lost a block
|
|
73
|
+
they had by default. Here an integration that wires no model loses no block;
|
|
74
|
+
one that wired a model judge and blocked on its decisions does, and one that
|
|
75
|
+
blocks in the hunt lane, as `atr guard --blocking` does on PostToolUse and the
|
|
76
|
+
OpenShell and NemoClaw adapters do, can gain blocks. If that describes your
|
|
77
|
+
integration, apply the switches above or stay on 4.1.5.
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- **New rules, all at maturity `test`** (hunt and alert lanes, not the
|
|
82
|
+
enforce lane): ATR-2026-02815, -02817 and -02820 (#612); -02858, -02859 and
|
|
83
|
+
-02860, technique rules for llm-guard samples lost to earlier FP repairs
|
|
84
|
+
(#668); -02855, -02861 and -02862 from the FN-mine sweeps (#650, #671);
|
|
85
|
+
-02864, -02865, -02866, -02867 and -02868 from agent-related CVEs published
|
|
86
|
+
2026-09-22 to 2026-10-21 (#730, #731).
|
|
87
|
+
|
|
88
|
+
- **The category registry as Gemara catalogs, generated and published from
|
|
89
|
+
here.** `spec/category-registry/gemara/` holds a vector, capability, threat
|
|
90
|
+
and control catalog validated against the Gemara v1.6.0 CUE schemas. The
|
|
91
|
+
vector catalog is generated from `spec/category-registry/v1.0.yaml` (ids and
|
|
92
|
+
descriptions verbatim) by `scripts/generate-gemara-catalogs.ts`;
|
|
93
|
+
`tests/gemara-catalogs.test.ts` fails when the committed catalogs are stale.
|
|
94
|
+
`.github/workflows/publish-gemara.yml` publishes them as one keyless-signed
|
|
95
|
+
OCI bundle, `ghcr.io/agent-threat-rule/agent-threat-rules/gemara:<bundle
|
|
96
|
+
version>`, on release tags only, and pushes nothing when that version is
|
|
97
|
+
already published with the same catalogs.
|
|
98
|
+
|
|
99
|
+
- **`atr test` reports a true positive that matches only through a copy the
|
|
100
|
+
test event makes.** The test event offers a case's input under every alias a
|
|
101
|
+
rule might read, whatever the event type: as `user_input` and `tool_args`, and
|
|
102
|
+
its content as `agent_message`. `buildEventFromTestCase` gains a `runtime`
|
|
103
|
+
field set, which holds what this repository's producers put on an event of the
|
|
104
|
+
type: content, the fields the case names (taken as given), `user_input` on an
|
|
105
|
+
`llm_input` event, and on a tool event the `tool_name` and `tool_args`
|
|
106
|
+
`src/hook-handler.ts` fills. `caseMatchesAtRuntime` runs a case on that set
|
|
107
|
+
for every event type, the engine's source filter deciding which of them the
|
|
108
|
+
rule runs on, or as a skill document. `atr test` lists a passing true positive
|
|
109
|
+
that matches none of them under `harnessOnly` (JSON) and "Harness-only"
|
|
110
|
+
(text); it still counts as passed. No true positive on main is one.
|
|
111
|
+
ATR-2026-01610's two cases declared as `user_input` match their `llm_input`
|
|
112
|
+
event only through the input copied into `tool_args`; the engine also runs
|
|
113
|
+
`llm_io` rules on `tool_response` events, where the hook fills `tool_args`,
|
|
114
|
+
and there they match. `tests/testcase-runtime-parity-rules.test.ts` runs every
|
|
115
|
+
rule's true positives the way `atr test` does, leaving out a type the engine
|
|
116
|
+
withholds as `atr test` does, and fails on one.
|
|
117
|
+
|
|
118
|
+
- **Every benchmark measurement records the enforce lane beside its
|
|
119
|
+
headline.** A measurement's `metrics.recall` is the `hunt` lane (the engine
|
|
120
|
+
default, every maturity). Each runner the weekly measurement calls now also
|
|
121
|
+
runs the same samples through its own detection loop on an engine
|
|
122
|
+
constructed with `lane: 'enforce'` (only `maturity: stable` rules, the lane
|
|
123
|
+
for blocking) and records the result as `breakdown.enforce_lane`. Where a
|
|
124
|
+
measurement records it, README's Recall cell cites both lanes and root
|
|
125
|
+
`stats.json` cites the enforce figure as `enforceLaneRecall`;
|
|
126
|
+
`scripts/check-benchmark-citations.ts` fails CI when either disagrees with
|
|
127
|
+
the measurement or leaves the enforce figure out. Before this, the enforce
|
|
128
|
+
figures in README were one-off replays.
|
|
129
|
+
- **Recall figures outside README are rendered from the measurement files.**
|
|
130
|
+
`scripts/render-recall-citations.ts` fills the figures between recall
|
|
131
|
+
markers in `LIMITATIONS.md` and the `docs/owasp-pr/` texts, from the file each
|
|
132
|
+
source's `latest.json` points at, in both lanes (every rendering is held to
|
|
133
|
+
the rule README's table is checked by). `scripts/sync-citation-garak.ts`
|
|
134
|
+
generates the garak sentence in `CITATION.cff` from root `stats.json`, with
|
|
135
|
+
both lanes and the closed-book caveat. The weekly measurement run re-renders
|
|
136
|
+
and stages both and lists what it could not render in the rolling PR's body;
|
|
137
|
+
CI (`eval.yml`) fails on a stale or unrenderable figure.
|
|
138
|
+
- **`scripts/bench-evaluate-latency.ts` times single `ATREngine.evaluate()`
|
|
139
|
+
calls** in the hunt and enforce lanes on three committed corpora of
|
|
140
|
+
different input length, so the latency wording in LIMITATIONS.md and
|
|
141
|
+
THREAT-MODEL.md can be checked on any hardware. The `latency_ms` blocks in
|
|
142
|
+
`data/measurements/` time whole corpus samples (several event shapes, a
|
|
143
|
+
skill scan and, when installed, an embedding pass) inside a full benchmark
|
|
144
|
+
run, and are not comparable.
|
|
145
|
+
- **CI re-checks the /red-team page's links against GitHub.** The page's list
|
|
146
|
+
of offensive tools and corpora moved out of the page into
|
|
147
|
+
`website/data/red-team-tools.json`. `scripts/verify-red-team-links.mjs`, run
|
|
148
|
+
by `verify-red-team-links.yml` when the list or the checker changes and
|
|
149
|
+
weekly, fails when an in-review entry's PR or issue is no longer open, an
|
|
150
|
+
in-production entry's PR is not merged, a corpus entry's link is neither a
|
|
151
|
+
merged PR nor a commit on the default branch, or the `stillPresent` file an
|
|
152
|
+
in-production or corpus entry must name no longer holds its text. It
|
|
153
|
+
reports drift, links that are gone, and lookups GitHub could not answer
|
|
154
|
+
separately, and counts none of them as a pass; a failure on main or on the schedule opens a tracking issue.
|
|
155
|
+
It would have flagged NVIDIA/garak#1676 within a week of its withdrawal on
|
|
156
|
+
2026-08-04, instead of the ten weeks it stayed listed before #688. It cannot
|
|
157
|
+
flag an open proposal nobody answers, which is why the page calls that group
|
|
158
|
+
"Open proposals". The three corpus entries linked to ATR PRs #51, #52 and
|
|
159
|
+
#53, which do not contain the rules they named; they now link to the commits
|
|
160
|
+
that added ATR-2026-00452..00456, 00500..00505 and 00510..00517, the second
|
|
161
|
+
no longer names NeMo-Guardrails as a rule source, and every figure they and
|
|
162
|
+
the page's 2026-05-12 sprint section quote now comes from a measurement file
|
|
163
|
+
or a committed re-run record. HackAPrompt is quoted per lane on its pinned
|
|
164
|
+
4,780-prompt subset, followed by its ablation reference (the same prompts
|
|
165
|
+
without the rules whose YAML names HackAPrompt), read from
|
|
166
|
+
`breakdown.in_sample_ablation` of the measurement file and called what it is,
|
|
167
|
+
an approximation of out-of-sample recall rather than an out-of-sample result;
|
|
168
|
+
the vendor-suite entry is followed by its own. The sprint's 28.6% to 66.2%
|
|
169
|
+
becomes the re-measured 28.6% to 66.0%: 66.2% could not be reproduced at the
|
|
170
|
+
sprint commits `e9efefdd` and `92c0d0fe` (66.0% at best), and the 28.6% is
|
|
171
|
+
itself in-sample. The paper is described as listed under Best Paper Awards at
|
|
172
|
+
EMNLP 2023, on gpt-3.5-turbo, FlanT5-XXL and text-davinci-003 (not
|
|
173
|
+
GPT/Claude/PaLM). The 2026-05-12 import is described as 79 of 94 samples
|
|
174
|
+
occurring verbatim in the vendor file they name. The sprint added 71 rule
|
|
175
|
+
files, not 75 (the 2.2.0 entry also counted four earlier CVE rules), and the
|
|
176
|
+
claims that the sprint's and the 2026-06 mega-scan's rules all passed the
|
|
177
|
+
quality gate with 0 benign FP are replaced by the gate re-run on 2026-10-19
|
|
178
|
+
and again on 2026-10-22 (`data/gate-reruns/2026-10-19_red-team-rule-batches.json`):
|
|
179
|
+
at 92c0d0fe, 10 of the 71 failed (own true positives) and none flagged a benign
|
|
180
|
+
SKILL.md file; at main b9d16840 on 2026-10-22, 33 of 71 and 4 of 29 fail, and
|
|
181
|
+
26 and 3 flag at least one benign sample, none of them in the enforce lane,
|
|
182
|
+
the same as at 9d74fc50 on 2026-10-19 although nine of the recorded rules
|
|
183
|
+
changed in between. Each corpus entry links its
|
|
184
|
+
measurement file.
|
|
185
|
+
|
|
186
|
+
### Changed
|
|
187
|
+
|
|
188
|
+
- **Three rules left the enforce lane.** ATR-2026-00080, -00436 and -01606
|
|
189
|
+
each matched benign samples in the grown corpus and were demoted from
|
|
190
|
+
maturity `stable` to `test` (#625). ATR-2026-00233, a duplicate of another
|
|
191
|
+
rule, is deprecated (#666).
|
|
192
|
+
- **Six rules follow MITRE ATLAS v2026.07.** AML.T0019 and AML.T0104, retired
|
|
193
|
+
into AML.T0115 as sub-techniques, are replaced by AML.T0115.000 and
|
|
194
|
+
AML.T0115.002 in the rules' `references.mitre_atlas` (#543).
|
|
195
|
+
|
|
196
|
+
- **A match a model decided is reported, not enforced, unless the integrator
|
|
197
|
+
opts in.** Three engine paths turn a model's output into a match: a semantic
|
|
198
|
+
rule's `semanticJudge`, the `embeddingModule` similarity signal (which in the
|
|
199
|
+
hunt and alert lanes could produce a critical `block_input` match), and the
|
|
200
|
+
`semanticModule` layer. No evidence a response tier is earned from measures
|
|
201
|
+
them: the benign-corpus evidence runs the synchronous engine, where a semantic
|
|
202
|
+
rule is its pattern fallback, and neither the embedding module nor the
|
|
203
|
+
semantic layer. Every match now carries `decidedBy`, and
|
|
204
|
+
`computeVerdict()` keeps a model-decided one out of the outcome and dispatches
|
|
205
|
+
only its observe-tier actions; it is listed in the new `verdict.advisory`
|
|
206
|
+
(with `verdict.advisorySeverity`), and `highestSeverity` and
|
|
207
|
+
`highestConfidence` now describe the matches the outcome came from. A
|
|
208
|
+
semantic rule whose judge matches is enforced when the rule's own pattern
|
|
209
|
+
fallback matches the same input (reported as the fallback's match); a judge
|
|
210
|
+
match the fallback does not corroborate is advisory. Rule, invariant,
|
|
211
|
+
blacklist and fingerprint matches decide as before. `modelDecisions:
|
|
212
|
+
'enforce'` on the engine (or on `computeVerdict`) restores the previous
|
|
213
|
+
behaviour; any other value throws. `computeVerdict` now sorts the matches it
|
|
214
|
+
is given instead of trusting their order.
|
|
215
|
+
- **`atr scan` and the MCP server follow the same default.** `atr scan
|
|
216
|
+
--semantic` lists a judge-only match but leaves it out of `--fail-on`, and
|
|
217
|
+
writes it to SARIF at level `note` with `properties.enforced: false`, which the
|
|
218
|
+
GitHub Action no longer counts toward `fail-on-finding`. JSON and SARIF output
|
|
219
|
+
name what decided each match (`decided_by`). The MCP scan tool reports a
|
|
220
|
+
judge-only match with outcome `allow`, `enforced: false` and observe-tier
|
|
221
|
+
recommended actions. `--model-decisions enforce` or
|
|
222
|
+
`ATR_MODEL_DECISIONS=enforce` restores counting for `atr scan` and `atr mcp`.
|
|
223
|
+
`atr guard` and the bundled adapters wire no model, so nothing they do
|
|
224
|
+
changes.
|
|
225
|
+
|
|
226
|
+
- **The ReDoS gate now times long inputs, every branch of an alternation
|
|
227
|
+
and input tiled with a pattern's own openers, and its baseline grew from 9
|
|
228
|
+
to 439 conditions because of it.** Nothing got slower; the gate started
|
|
229
|
+
looking. Two integrations packaging the corpus found conditions that took
|
|
230
|
+
seconds to minutes on 8,000- and 50,000-character inputs while
|
|
231
|
+
`scripts/gate-redos.py` reported them clean: its inputs were 1,500 to
|
|
232
|
+
3,000 characters, where a quadratic condition costs about a tenth of a
|
|
233
|
+
second, and only the first branch of an alternation was ever pumped. Each
|
|
234
|
+
condition is now also run on 20,000-character inputs built per quantifier
|
|
235
|
+
-- the text that reaches the quantifier, through whichever branch,
|
|
236
|
+
lookbehind or back-reference leads there, followed by a long run of its
|
|
237
|
+
own body -- and on its openers and pumped near-match tiled to length
|
|
238
|
+
(#601). Each worker calibrates per chunk and times are reported in
|
|
239
|
+
reference-machine seconds, which reduces, but does not remove, the effect
|
|
240
|
+
of load that changes during a run. `data/redos-baseline.json` now lists
|
|
241
|
+
439 conditions in 259 rules: 350 over the budget when it was written (201
|
|
242
|
+
of them did not return within the wall even timed alone) and 89 under the
|
|
243
|
+
budget but over a third of it, recorded too because one condition's
|
|
244
|
+
readings spread by more than 2x across runs and machines. 41 are in the
|
|
245
|
+
enforce lane and 412 are high or critical.
|
|
246
|
+
Each entry carries a fingerprint of its pattern, so editing a baselined
|
|
247
|
+
condition makes it new again instead of inheriting the exemption; an
|
|
248
|
+
unchanged baselined hang is not re-run. The gate stays a ratchet: it fails
|
|
249
|
+
on a slow condition outside that file. `scripts/verify_rule_redos.py` now
|
|
250
|
+
uses the gate's own inputs and timing -- it had reported PASS on
|
|
251
|
+
ATR-2026-02372 with both conditions hanging -- and with `--base REF` also
|
|
252
|
+
times a changed condition on the inputs derived from its previous pattern.
|
|
253
|
+
Twelve conditions that Python `re` cannot compile, and that the gate
|
|
254
|
+
skipped without saying so, are now listed by key. pyatr runs eleven of
|
|
255
|
+
them through its `regex` fallback and the TypeScript engine compiles all
|
|
256
|
+
twelve, so they run in production and are still untimed here; the twelfth,
|
|
257
|
+
ATR-2026-00290#3, back-references a group that does not exist, which pyatr
|
|
258
|
+
refuses and JavaScript reads as the character U+0001 (#331). The semantic
|
|
259
|
+
lane's per-draft ReDoS precheck now allows 300 s instead of 120 s.
|
|
260
|
+
|
|
261
|
+
- **Every recall figure the site, LIMITATIONS.md, CITATION.cff and the docs
|
|
262
|
+
above take from the measurement files now names its lane** and gives the
|
|
263
|
+
enforce figure wherever the measurement records one. Figures no measurement
|
|
264
|
+
file records are labelled instead: the about page's historical garak figure
|
|
265
|
+
as taken with every rule loaded, the neutral-benchmarks post's runs as having
|
|
266
|
+
no recorded lane. The website reads `breakdown.enforce_lane` from the measurement
|
|
267
|
+
files (home, coverage, research, red-team, garak integration, ATD and
|
|
268
|
+
sovereign-AI pages, the PINT blog post); it no longer reads recall
|
|
269
|
+
from the old eval report files or their typed fallbacks, and no longer
|
|
270
|
+
hydrates recall in the browser. The garak figures carry the closed-book
|
|
271
|
+
caveat, and so now do the PINT-format and HackAPrompt figures, which have
|
|
272
|
+
rules mined from them too. The SKILL.md benchmark is described as the
|
|
273
|
+
internal, labelled corpus it is, with the harness's layer names. An erratum
|
|
274
|
+
(`e-2026-10-16-recall-lanes`) records the change on the site.
|
|
275
|
+
- `sync-stats.ts` now fails when CITATION.cff has no garak sentence to
|
|
276
|
+
rewrite, instead of leaving an old figure in place.
|
|
277
|
+
- **`scripts/check-rules-safety.ts` checks that a modified rule still matches
|
|
278
|
+
its own true positives, and checks renamed rules.** A new rule's true
|
|
279
|
+
positives had to match; a modified rule only had to add no benign false
|
|
280
|
+
positive, and a rule that matches nothing adds none. `npm run
|
|
281
|
+
gate:generalization` covers pattern rules only, so a semantic rule whose
|
|
282
|
+
`fallback_method` was set to `none` passed every check. The new own-TP check
|
|
283
|
+
runs each true positive on both paths `atr test` tries, replaying every case
|
|
284
|
+
to the changed rule as the event the base rule would receive: a case the
|
|
285
|
+
base rule matched on a path (`evaluate()`, `scanSkill()`) must still match
|
|
286
|
+
there, one the change adds must match, and the changed rule must still match
|
|
287
|
+
one of the cases the base rule matched. That also catches re-routing a rule
|
|
288
|
+
away from its cases. A rule of a type the engine withholds from typed events
|
|
289
|
+
(`WITHHELD_SOURCE_TYPES`), which `atr test` reports as unevaluable, is
|
|
290
|
+
skipped while it stays that type. A renamed rule file was on neither the new
|
|
291
|
+
nor the modified list and skipped both checks; it is now compared with the
|
|
292
|
+
file it was at the base, and a changed file whose rule id differs is checked
|
|
293
|
+
as a new rule. `npm run validate` also checks a semantic rule's
|
|
294
|
+
`fallback_method` (`pattern` or `none`, compared exactly, as the engine
|
|
295
|
+
does), its `threshold` (a number from 0 to 1), and that a pattern fallback
|
|
296
|
+
has a condition to match.
|
|
297
|
+
- **`scripts/check-rules-safety.ts` runs a new rule's true positives the way
|
|
298
|
+
`atr test` does (Check 2).** It read only a case's `input` and ran it as the
|
|
299
|
+
text of the benign corpus shapes. On main, as if each rule past Check 1 were
|
|
300
|
+
new, 250 true positives in 54 rules carry their payload elsewhere
|
|
301
|
+
(`tool_response`, `tool_args`, `tool_description`, a `user_input` key, an
|
|
302
|
+
object `input`, ...) and were never read; 47 of those rules had none read
|
|
303
|
+
and passed with nothing run. The routing a rule declares was never
|
|
304
|
+
exercised. Each case now runs as the event built from it for the rule's own
|
|
305
|
+
`agent_source.type`, and through `scanSkill()`, with the code the own-TP
|
|
306
|
+
check for modified rules uses (`scripts/lib/own-true-positives.ts`), and its
|
|
307
|
+
text must still match on the corpus shapes, so a case that fires only
|
|
308
|
+
through a tool name, which no benign sample exercises, fails. The semantic
|
|
309
|
+
lane's pre-gate runs the same code. A case that cannot be run fails, and so
|
|
310
|
+
does a rule of a type the engine withholds from typed events
|
|
311
|
+
(`memory_access`), whose cases `atr test` reports as unevaluable. A new rule
|
|
312
|
+
the benign checks cannot run at all (deprecated, or a trace, behavioral or
|
|
313
|
+
signature method) is refused with the reason; Check 2 used to fail most of
|
|
314
|
+
them only because it could not match their cases either, and one whose cases
|
|
315
|
+
carried no `input` passed the whole gate.
|
|
316
|
+
- **README §8 discloses that the `garak` and `garak-full` figures are partly
|
|
317
|
+
closed-book.** A replay without the rules whose own YAML shows they were
|
|
318
|
+
written from the in-the-wild corpus is given in the new `[^garakcorpus]`
|
|
319
|
+
footnote, and the paragraph on the two `garak` rows no longer calls them
|
|
320
|
+
independent: `garak-full` contains that corpus twice.
|
|
321
|
+
- `data/stats.json` no longer carries the orphaned `benchmark.pintRecall`
|
|
322
|
+
block, a 3.5.11 figure nothing has written since #600 and nothing reads.
|
|
323
|
+
|
|
324
|
+
### Fixed
|
|
325
|
+
|
|
326
|
+
- **`atr scan` scans an MCP config file instead of skipping it.** The file was
|
|
327
|
+
parsed, wrapped as one event without `content` and skipped, while the
|
|
328
|
+
summary counted it as scanned, so a config that sets `NODE_OPTIONS` to a
|
|
329
|
+
`require` payload came back as no threats. Each server in the document is
|
|
330
|
+
now its own event, `events_scanned` counts what reached the engine beside
|
|
331
|
+
`events_skipped`, and a file with nothing scannable exits non-zero (#581,
|
|
332
|
+
by @ciceroyang; issue #575, where directory scans and the Action's exit
|
|
333
|
+
handling remain open).
|
|
334
|
+
|
|
335
|
+
- **Rule conditions that took polynomial time on long input were
|
|
336
|
+
rewritten.** 86 conditions in 48 rules found by the extended ReDoS gate
|
|
337
|
+
(#704), eight conditions quadratic in a long run of one character (#723),
|
|
338
|
+
and ATR-2026-00063 and -00228 (#666). The rewrites keep the language of
|
|
339
|
+
each condition, except four deliberate changes in #704:
|
|
340
|
+
ATR-2026-00446 condition 0 widens, and ATR-2026-01005 condition 1 and
|
|
341
|
+
ATR-2026-01307 conditions 0 and 1 narrow. ATR-2026-02261 condition 0
|
|
342
|
+
narrows on purpose in #723 (its back-reference no longer ties two different
|
|
343
|
+
variables), and ATR-2026-00228 condition 1 in #666 (on JSON-encoded content
|
|
344
|
+
the two labels must be on one line). Conditions still over the gate's
|
|
345
|
+
budget stay listed in `data/redos-baseline.json`.
|
|
346
|
+
- **Rule false positives.** ATR-2026-00012 matched 1,817 of 14,157 benign
|
|
347
|
+
samples and now matches 150, with every match of the new version also a
|
|
348
|
+
match of the old one (#683). ATR-2026-02010 no longer matches blank or
|
|
349
|
+
whitespace-only input (#704). ATR-2026-00013 also flags apex
|
|
350
|
+
loopback-wildcard domains (CVE-2026-55096) and its IPv6 condition matches
|
|
351
|
+
IPv4-mapped obfuscation only (#732, #733).
|
|
352
|
+
- **`atr scan` scans an MCP `tools/list` document, one event per tool.** A file
|
|
353
|
+
holding `{"tools": [...]}`, or the JSON-RPC response `{"jsonrpc": "2.0",
|
|
354
|
+
"result": {"tools": [...]}}`, was wrapped as one event with no content,
|
|
355
|
+
skipped, and reported as "nothing was scanned", so the usual input for a
|
|
356
|
+
tool-poisoning check reached no rule. Each tool is now an `mcp_exchange`
|
|
357
|
+
event (`src/cli/mcp-tools-document.ts`): the content is the tool definition as
|
|
358
|
+
JSON, input schema and per-parameter descriptions included, and
|
|
359
|
+
`tool_name` / `tool_description` are set from the definition. A JSON array of
|
|
360
|
+
such responses is accepted too. A document that lists no tools fails with
|
|
361
|
+
"nothing was scanned" rather than reporting clean. `mcpServers` documents and
|
|
362
|
+
event files are read as before. Tests: `tests/cli-mcp-tools-list-scan.test.ts`.
|
|
363
|
+
- **A tools/list scan does not run the rules written for a tool being called.**
|
|
364
|
+
A definition defines a tool and calls none, but an `mcp_exchange` event admits
|
|
365
|
+
every rule, so call rules (ATR-2026-00099 and others, which read the called
|
|
366
|
+
tool's name and arguments) flagged every definition named `delete_*`,
|
|
367
|
+
`*_reset` or `exec*`. New engine option `toolDefinitions` (`ATREngineConfig`,
|
|
368
|
+
default false), which `atr scan` sets only for a tools/list document: a rule
|
|
369
|
+
with `agent_source.type: tool_call` then runs only if one of its conditions
|
|
370
|
+
reads `tool_description` (the test is per rule: such a rule runs with all its
|
|
371
|
+
conditions), or if its subject is the tool's identity (`TOOL_IDENTITY_RULES`
|
|
372
|
+
in `src/tool-definition-scope.ts`: ATR-2026-00060, impersonation); every other
|
|
373
|
+
rule runs as before. No rule file changes; hooks, adapters, config documents
|
|
374
|
+
and event files are unaffected. `atr scan --json` reports
|
|
375
|
+
`tool_definition_scope`, `rules_evaluated` and `rules_skipped_by_scope` (SARIF:
|
|
376
|
+
run properties) and the `tool_name` of each result, so a rule that was not run
|
|
377
|
+
does not read as a clean result. An event that also carries a `tools` array
|
|
378
|
+
is still scanned as an event. Documented in `docs/ARCHITECTURE.md` §3.2,
|
|
379
|
+
`docs/schema-spec.md` (agent_source.type) and `docs/rule-writing-guide.md`.
|
|
380
|
+
On 710 benign tool definitions from 45 official and vendor MCP servers,
|
|
381
|
+
flagged definitions go from 58 to 6 (hunt) and 57 to 5 (alert); enforce is 0
|
|
382
|
+
before and after, which says nothing here, since none of the rules involved is
|
|
383
|
+
stable. The true positives of the rules that read `tool_description` give the
|
|
384
|
+
same result with and without the scope: of 41, 39 fire (35 description texts,
|
|
385
|
+
and 4 ATR-2026-00441 code snippets through its content conditions) and 2 do
|
|
386
|
+
not (ATR-2026-00441 tool calls given as JSON with arguments, which are not
|
|
387
|
+
definitions). The 6 remaining hunt-lane false positives come from rules this
|
|
388
|
+
change does not touch: ATR-2026-01987, -00225, -00217, -00161 and -00569 (over-
|
|
389
|
+
broad conditions) and a form match of ATR-2026-00579 (experimental); see
|
|
390
|
+
`data/measurements/mcp-tools-list-benign/README.md`. The scope also costs
|
|
391
|
+
detection: a poisoned definition that only a call rule catches (for example
|
|
392
|
+
ATR-2026-00114) is missed in a tools/list scan; no malicious tool-definition
|
|
393
|
+
corpus measures this yet. Tests:
|
|
394
|
+
`tests/tool-definition-scope.test.ts`, `tests/cli-mcp-tools-list-scan.test.ts`.
|
|
395
|
+
|
|
396
|
+
- **On Claude Code, the guard scans what a tool returned on PostToolUse.**
|
|
397
|
+
Claude Code sends a tool's output at the top level of the PostToolUse hook
|
|
398
|
+
input, as `tool_response` (checked against the shipped 2.1.280 binary).
|
|
399
|
+
`src/hook-handler.ts` read an output only from `tool_input.output` or
|
|
400
|
+
`tool_input.response`, where Claude Code never puts one, so the event's content
|
|
401
|
+
was the tool's input, and a prompt injection in a command's output, a file or a
|
|
402
|
+
fetched page reached no rule. The event's content and `tool_response` are now
|
|
403
|
+
the text of the output: `src/hook-tool-output.ts` keeps every string in it, as
|
|
404
|
+
written, and leaves out binary data, what Claude Code keeps from the model,
|
|
405
|
+
and labels and strings the tool only repeats from its input. A tool that edits
|
|
406
|
+
a file returns the edit it made, so its event is still built from its input.
|
|
407
|
+
`tool_args` stays the JSON of the input, and `tool_input.output` and
|
|
408
|
+
`.response` are still read for a host that sends the output there, now as raw
|
|
409
|
+
text in content too. Measured through the hook in the default hunt lane, each
|
|
410
|
+
sample as a Bash command's output: the own true positives of the 746 rules the
|
|
411
|
+
engine runs on a tool response (`llm_io`, `mcp_exchange` and `context_window`)
|
|
412
|
+
match 3,852 of 3,890 times (0 before, and the same as a Read or MCP output);
|
|
413
|
+
289 of PINT's 451 attacks match (0 before) and none of its 399 benign samples;
|
|
414
|
+
1,246 of the 14,157 benign-corpus samples match a rule (0 before), most of them
|
|
415
|
+
through rules at maturity `test`. In the alert lane 1,183 match, in the enforce
|
|
416
|
+
lane none. Not covered: an output whose text exceeds 100,000 characters
|
|
417
|
+
taken together (`MAX_EVAL_LENGTH`); and some rules take time quadratic in a
|
|
418
|
+
long run of one character. `atr init` registers the guard on
|
|
419
|
+
PreToolUse only, so this reaches an installation that added it under
|
|
420
|
+
PostToolUse.
|
|
421
|
+
|
|
422
|
+
- **The guard also scans a failed tool call's output (PostToolUseFailure).**
|
|
423
|
+
When a tool call fails, a Bash command whose exit code Claude Code counts as
|
|
424
|
+
a failure among them (most non-zero codes; `grep`'s exit 1 is not one),
|
|
425
|
+
Claude Code sends `PostToolUseFailure` instead of `PostToolUse`. Its input
|
|
426
|
+
carries `tool_name`, `tool_input`, `tool_use_id`, `error`, `is_interrupt`
|
|
427
|
+
and `duration_ms`, and no `tool_response` (checked against the shipped
|
|
428
|
+
2.1.280 binary). The guard answered it "Unknown hook type" and allowed it,
|
|
429
|
+
so ending a command with `; exit 1` kept its output from every rule. It now
|
|
430
|
+
builds the event from `error`, the failure as the model sees it: for a shell
|
|
431
|
+
command, its stderr and stdout after Claude Code's `Exit code N` line and
|
|
432
|
+
abort note, which are left out. A command that printed nothing leaves no
|
|
433
|
+
text, and its event is built from the input, as an output-less PostToolUse
|
|
434
|
+
is. It answers in the PostToolUse contract under the `PostToolUseFailure`
|
|
435
|
+
name, which Claude Code accepts, and with blocking on its `decision:
|
|
436
|
+
"block"` reaches the model as a blocking error. Measured through the hook in
|
|
437
|
+
the default hunt lane, each sample as a failed Bash command's error: the own
|
|
438
|
+
true positives of the 746 rules the engine runs on a tool response match
|
|
439
|
+
3,852 of 3,890 times (0 before); 289 of PINT's 451 attacks match (0 before)
|
|
440
|
+
and none of its 399 benign samples; 1,246 of the 14,157 benign-corpus
|
|
441
|
+
samples match a rule (0 before). On every sample the verdict and the rules
|
|
442
|
+
matched are those of the same text as a Bash command's output on
|
|
443
|
+
PostToolUse. Claude Code cuts a failed call's error to about 10,000
|
|
444
|
+
characters; 1,117 of the benign samples are longer and were sent whole. To
|
|
445
|
+
use it, register the guard under `PostToolUseFailure` too.
|
|
446
|
+
|
|
447
|
+
- **Flywheel shadow rules can match.** `ShadowEvaluator` compiled each
|
|
448
|
+
condition with `new RegExp(value, 'i')`. A value that starts with `(?i)` is
|
|
449
|
+
a SyntaxError in JavaScript, the error was swallowed, and the condition
|
|
450
|
+
never matched. `RuleScaffolder` starts every pattern it writes with `(?i)`,
|
|
451
|
+
so no rule the flywheel generated could match in shadow mode or reach
|
|
452
|
+
promotion. Shadow conditions now go through the engine's own
|
|
453
|
+
`compileRegexCondition()` (leading flag group removed per SPEC §6.4, `u`
|
|
454
|
+
when needed, ReDoS gate) and `safeRegexTest()` (the engine's input-length
|
|
455
|
+
limit), and a condition whose value is not a string is skipped instead of
|
|
456
|
+
throwing. Shadow evaluation still differs from `ATREngine` in field
|
|
457
|
+
resolution, operators, `condition: all`, Unicode normalisation and
|
|
458
|
+
`agent_source` routing; those differences are unchanged.
|
|
459
|
+
- **Flywheel promotion needs a reviewed true positive, and promotes a rule
|
|
460
|
+
once.** `ShadowEvaluator.getPromotionCandidates()` divided confirmed false
|
|
461
|
+
positives by all matches, so a rule with no recorded feedback had an FP rate
|
|
462
|
+
of 0 and became a candidate after its first match. Once shadow rules could
|
|
463
|
+
match (above), `FlywheelManager.promoteReady()` would have handed such rules
|
|
464
|
+
to `onPromote` with status `stable`, and the same rule again on every call.
|
|
465
|
+
A rule is now a candidate only after at least `minReviewedMatches` (new
|
|
466
|
+
`FlywheelConfig` option, default 1) matches with recorded feedback including
|
|
467
|
+
a confirmed true positive; the FP rate is computed over reviewed matches; and
|
|
468
|
+
`promoteReady()` hands each rule to `onPromote` once. A promoted rule keeps
|
|
469
|
+
`maturity: draft`.
|
|
470
|
+
- **`RuleScaffolder` no longer writes a pattern that misses its own example
|
|
471
|
+
payload.** An attack indicator is chosen by a broad test and writes a
|
|
472
|
+
narrower pattern: "Ignore all previous instructions", "please ignore the
|
|
473
|
+
rules above" and "disregard everything you were told" all selected the
|
|
474
|
+
instruction-override indicator, whose pattern did not match them. An
|
|
475
|
+
indicator now counts only when its pattern matches the payload, and the
|
|
476
|
+
exact payload is used if nothing else does. Keyword patterns are now tried
|
|
477
|
+
only at line starts, `(?:^|[\n\r\u2028\u2029])(?=[^\n\r\u2028\u2029]*word)...`,
|
|
478
|
+
and exact patterns are the escaped payload without a surrounding `.*`. Both
|
|
479
|
+
match the same inputs as before, but neither is quadratic on a long
|
|
480
|
+
single-line input any more (about 7-12 seconds per pattern on 100,000
|
|
481
|
+
characters before, about a millisecond now). An empty or whitespace-only
|
|
482
|
+
example payload, which produced a pattern that matched everything, is now
|
|
483
|
+
rejected. Output of `atr scaffold` and the `atr_submit_proposal` MCP tool
|
|
484
|
+
changes accordingly.
|
|
485
|
+
|
|
486
|
+
- **SPEC §6.4 corrected: SPEC.md 1.0.1 [Compatible].** A `regex` pattern
|
|
487
|
+
condition matches case-insensitively whether or not it begins with `(?i)`,
|
|
488
|
+
and `m`, `s` and `x` in a leading flag group have no effect. The 1.0.0 text,
|
|
489
|
+
added on 2026-07-19 (#340), said an engine applies the flags named in that
|
|
490
|
+
group, which made a pattern without `(?i)` case-sensitive and gave `(?s)`
|
|
491
|
+
and `(?m)` their dotAll and multiline meaning. Compatible: an engine that
|
|
492
|
+
passes the conformance suite needs no change, because its true-positive
|
|
493
|
+
fixtures already require case-insensitive matching; an implementation built
|
|
494
|
+
to the 1.0.0 wording does. 1.0.1 also admits `x` in the group, which both
|
|
495
|
+
engines already removed, says when the `u` flag applies, advises against
|
|
496
|
+
modifier groups such as `(?-i:...)`, and adds a revision history (Appendix
|
|
497
|
+
D). Issue #698 has the evidence; erratum `e-2026-10-19-regex-dialect`
|
|
498
|
+
records the error. `tests/spec-regex-dialect.test.ts` and pyATR's
|
|
499
|
+
`TestRegexDialectSpec64` pin the statements. `scripts/generate-sigma.py` no
|
|
500
|
+
longer copies `m` and `s` from that group into exported Sigma patterns,
|
|
501
|
+
where a backend would honour them; of the three rules on main that carry
|
|
502
|
+
such a group, only ATR-2026-00450's `(?im)^...` condition matched
|
|
503
|
+
differently because of it. Other exports that still follow the 1.0.0
|
|
504
|
+
wording are listed in #698.
|
|
505
|
+
- **Rules with a `context_window` or `skill_*` `agent_source.type` run on the
|
|
506
|
+
events their data arrives in.** `src/engine.ts` maps an event type to one of
|
|
507
|
+
five source types and skipped every rule whose type differed, so a rule of an
|
|
508
|
+
extended type never ran on a typed event: on the `tool_call` and
|
|
509
|
+
`tool_response` events `src/hook-handler.ts` builds, ATR-2026-00070, -00075,
|
|
510
|
+
-01155, -01774, -00134, -00157 and -02405 never fired, while `atr test` mapped
|
|
511
|
+
their types to a base type and passed every case. The engine now does that
|
|
512
|
+
mapping itself (`RUNTIME_SOURCE_TYPE`): `context_window` runs where `llm_io`
|
|
513
|
+
rules run, the `skill_*` types where `tool_call` rules run, and a rule without
|
|
514
|
+
`agent_source` no longer throws a `TypeError` that aborted the whole
|
|
515
|
+
evaluation of a typed event. `memory_access` is withheld
|
|
516
|
+
(`WITHHELD_SOURCE_TYPES`): ATR-2026-00075 also matches standing instructions
|
|
517
|
+
in an ordinary chat turn ("From now on, always answer in French."), which the
|
|
518
|
+
Mastra processor and the OpenShell filter, both blocking by default, would
|
|
519
|
+
refuse; -00075 and -01155 still run only on an untyped event and in a skill
|
|
520
|
+
scan, and a new `memory_access` rule fails the test suite until the type is
|
|
521
|
+
mapped. `atr test` no longer maps anything, so what passes there is what the
|
|
522
|
+
engine runs, and it reports a withheld type's cases as unevaluable, with the
|
|
523
|
+
reason. The rule-latency gate profiles the five rules instead of listing them
|
|
524
|
+
as unexercised. The benign-corpus measurement does not change, because its
|
|
525
|
+
untyped shape already ran these rules; what changes is that their matches now
|
|
526
|
+
surface at runtime in the hunt lane, the default: ATR-2026-00070 (high)
|
|
527
|
+
matches 20 of the 14,157 benign samples. On the weekly benchmark suite three
|
|
528
|
+
hunt-lane recalls rise: garak 601 to 602 of 650 and garak-full 1,989 to 1,991
|
|
529
|
+
of 3,475 (one prompt, which -00070 catches on the tool-response channel and
|
|
530
|
+
garak-full holds twice), and atr-self-test 310 to 312 of 321 (-00070's own
|
|
531
|
+
true positives). No precision or false-positive figure moves (the new matches
|
|
532
|
+
fall on attack samples), and no enforce-lane figure moves (none of the five
|
|
533
|
+
rules is stable).
|
|
534
|
+
|
|
535
|
+
- **A tool event built from a test case always names a tool.**
|
|
536
|
+
`buildEventFromTestCase` (`src/testcase-event.ts`, behind `atr test`,
|
|
537
|
+
`scripts/validate-rule-testcases.ts` and the case-sensitivity gate) gave a case
|
|
538
|
+
that names no tool the `tool_name` `""`, meant to keep the case's text out of
|
|
539
|
+
`tool_name`. `src/engine.ts` reads a field only when it is truthy and, on a
|
|
540
|
+
`tool_call` event, then falls back to the event's content, so a `tool_name`
|
|
541
|
+
condition read the payload as the tool's name and a rule keyed on the tool
|
|
542
|
+
could pass its cases without naming one. A tool event now carries
|
|
543
|
+
`atr-test-tool` (`UNNAMED_TOOL`) when the case names no tool, as
|
|
544
|
+
`src/hook-handler.ts` fills it from the host's tool call, and the
|
|
545
|
+
case-sensitivity gate leaves that name out of the text it checks a condition
|
|
546
|
+
against. No rule on main relied on the old behaviour: every true positive and
|
|
547
|
+
true negative of every rule tests the same.
|
|
548
|
+
|
|
549
|
+
- **A test case's own `agent_message` is no longer replaced by its content.**
|
|
550
|
+
`buildEventFromTestCase` set the field from the case and then copied the
|
|
551
|
+
event's content over it, so a case that names an `agent_message` beside its
|
|
552
|
+
input was run on the input there. No case on main names one.
|
|
553
|
+
|
|
554
|
+
- **A `medium` confidence label no longer depends on float rounding to stay
|
|
555
|
+
out of `deny`.** A pattern match scored `0.7 + 0.1 * coverage`, and a
|
|
556
|
+
`high`-severity match denies at `>= 0.8`. At full coverage that sum is
|
|
557
|
+
`0.7999999999999999` in IEEE doubles, so the verdict was `ask` only because
|
|
558
|
+
of the rounding direction; the same quantity as an integer percentage is
|
|
559
|
+
`0.8`. Label-derived confidence is now computed in one place
|
|
560
|
+
(`src/rule-confidence.ts`) on a basis-point scale, and a label below the
|
|
561
|
+
deny threshold is capped one basis point under it. No verdict or
|
|
562
|
+
auto-response decision changes. The raw `confidence` of a `medium` match at
|
|
563
|
+
full coverage is `0.7999` instead of `0.7999999999999999`, and other
|
|
564
|
+
partial-coverage values lose trailing float noise (`0.9500000000000001`
|
|
565
|
+
becomes `0.95`). One displayed percentage moves as a result: a
|
|
566
|
+
named-condition rule labelled `medium` at 3/4 coverage shows 78% (`0.775`)
|
|
567
|
+
where the noise used to show 77%. No shipped rule reaches that cell.
|
|
568
|
+
|
|
569
|
+
- **A semantic rule without a pattern fallback is no longer recorded as
|
|
570
|
+
measured clean.** The benign-corpus measurement
|
|
571
|
+
(`scripts/gate-promotion-fp.ts`, and `scripts/remeasure-benign-fp.ts`
|
|
572
|
+
through it) drives the synchronous engine, which returns null for
|
|
573
|
+
`method: semantic` unless `detection.semantic.fallback_method` is exactly
|
|
574
|
+
`pattern`; the judge runs only in `evaluateAsync()`. `fieldCoverage()`
|
|
575
|
+
named trace, behavioral and signature as methods it cannot run, but not
|
|
576
|
+
this one, and it skipped a rule with no condition field, such as a pattern
|
|
577
|
+
fallback with nothing in it. Either was written down as `fp_count: 0,
|
|
578
|
+
partial_measurement: false`, which `scripts/gate-action-eligibility.ts`
|
|
579
|
+
graded `terminate` at maturity `stable` and `degrade` at `test`. Both are
|
|
580
|
+
now recorded partial and capped at `observe`. No rule on main is in either
|
|
581
|
+
state, so the committed evidence is unchanged. The skill-path check uses
|
|
582
|
+
the same predicate; it used to read `fallback_method: Pattern` as
|
|
583
|
+
`pattern`, which the engine does not, and it exempted a
|
|
584
|
+
`scan_target: both` rule with no conditions.
|
|
585
|
+
- **NIST-related statements corrected across the docs and the website.**
|
|
586
|
+
Pages described a NIST CAISI dialogue and position paper that did not
|
|
587
|
+
happen, an RFI response ATR never filed, an OSCAL "Path 1" acceptance,
|
|
588
|
+
and a quote NIST never published; they listed NIST among adopters and
|
|
589
|
+
in a trust bar, and showed "100%" or "4/4 functions" for an AI RMF
|
|
590
|
+
mapping that covers a subset of subcategories. usnistgov/oscal-content#338
|
|
591
|
+
is described as what it is: open, reworked against NIST's AI RMF catalog,
|
|
592
|
+
awaiting NIST review. The website changelog page now shows the notes on
|
|
593
|
+
historical entries, which it used to drop.
|
|
594
|
+
- **Integration status, provenance, evasion and latency claims corrected.**
|
|
595
|
+
The red-team page listed seven proposals as under review that their ATR
|
|
596
|
+
author had withdrawn unmerged (NVIDIA/garak#1676 on 2026-08-04; HarmBench,
|
|
597
|
+
AgentDojo, JailbreakBench, Counterfit, PromptInject and Damn Vulnerable MCP
|
|
598
|
+
Server on 2026-10-14 and 15, six of them never answered). They are removed,
|
|
599
|
+
along with the lapsed "On deck" schedule, and the garak, sovereign-AI,
|
|
600
|
+
responsible-use, integrate and STANDARDIZATION-STATUS surfaces no longer
|
|
601
|
+
call the garak integration or any OWASP PR open. The garak page and the
|
|
602
|
+
contributor docs (QUALITY-GATE.md, TSC-ONBOARDING.md, REVIEWER-GUIDE.md,
|
|
603
|
+
RULE-PRODUCTION.md, the issue-to-proposal PR text) no longer describe
|
|
604
|
+
auto-merge, which has been off since 2026-06-03. The red-team and governance
|
|
605
|
+
pages no longer promise a `metadata_provenance.discovered_by` field no rule
|
|
606
|
+
has, and the home, red-team, about and quality-standard pages no longer
|
|
607
|
+
credit daily auto-crystallization for growth that came from CVE sweeps,
|
|
608
|
+
benchmark-miss mining and contributors. The sovereign-AI page no longer says
|
|
609
|
+
ATR detects intent and cannot be evaded; LIMITATIONS.md and the
|
|
610
|
+
responsible-use, integrate and research pages no longer say every rule
|
|
611
|
+
carries evasion tests or that there are 64; LIMITATIONS.md and
|
|
612
|
+
THREAT-MODEL.md now agree that the optional judge and embedding modules ship
|
|
613
|
+
but are off by default. "Sub-millisecond", "< 5ms", "5ms pattern match" and
|
|
614
|
+
"Done in 47ms" are replaced by how evaluation cost scales; the OpenShell
|
|
615
|
+
example is marked as a sketch of a proposal OpenShell closed, and its
|
|
616
|
+
timeouts now exceed a one-shot cold start. CITATION.cff no longer calls the
|
|
617
|
+
rules hand-authored and says what the safety gate checks for added and
|
|
618
|
+
changed rules; the OWASP PR text drops its 76-rule count and notes the PR
|
|
619
|
+
was withdrawn. Errata `e-2026-10-16-integration-status`,
|
|
620
|
+
`e-2026-10-16-evasion`, `e-2026-10-16-latency` and `e-2026-10-16-provenance`
|
|
621
|
+
record the site changes.
|
|
622
|
+
- **A rule the engine does not evaluate no longer gets a false-positive
|
|
623
|
+
record.** `scripts/gate-promotion-fp.ts --emit-measurement` wrote
|
|
624
|
+
`fp_count: 0` for draft and deprecated rules, which the engine skips, so
|
|
625
|
+
nothing was counted for them. `detection_fingerprint` does not cover
|
|
626
|
+
`status`, so a one-line revival handed the rule that 0, and
|
|
627
|
+
`scripts/gate-action-eligibility.ts` trusted it: at maturity `test`, enough
|
|
628
|
+
for `block_tool`. Seven records in `data/benign-fp-measurement.json` were of
|
|
629
|
+
this kind. The measurement now asks the engine (`ATREngine.evaluates`) and
|
|
630
|
+
lists such a rule, or an id two loaded files share, under NOT MEASURED
|
|
631
|
+
instead. Without `--filter-mode` that fails the gate: promoting a rule the
|
|
632
|
+
engine does not evaluate no longer passes as clean.
|
|
633
|
+
`scripts/remeasure-benign-fp.ts` leaves the same rules out, and measures in
|
|
634
|
+
full, rather than extends, a record for a rule that was inert at its base
|
|
635
|
+
commit. `scripts/promote-to-stable.ts` no longer counts a draft rule at
|
|
636
|
+
maturity `test` as FP-clean.
|
|
637
|
+
- **The action gate refuses evidence that holds a record for a rule that is
|
|
638
|
+
not live** (inert, or its file gone): exit 2, as for evidence taken on
|
|
639
|
+
another corpus. A record carries no status, so the gate cannot tell a count
|
|
640
|
+
taken while the rule was live from a 0 nothing measured, and left in place
|
|
641
|
+
either is trusted the day the rule is revived or restored. Deprecating or
|
|
642
|
+
deleting a rule now means re-emitting the evidence in the same change. The
|
|
643
|
+
gate also reads a status that is not a string (`status: [draft]`) as live,
|
|
644
|
+
as the engine does; it used to skip such a rule while the engine ran it.
|
|
645
|
+
- **Two rule files with the same name are both measured.** The measurement
|
|
646
|
+
copied rule files into its scratch directory by file name, so of two
|
|
647
|
+
same-named files in different directories only the last was read, and the
|
|
648
|
+
other was reported clean.
|
|
649
|
+
- **`scripts/promote-to-stable.ts` no longer promotes on a 0 it did not
|
|
650
|
+
measure.** A `maturity: test` rule whose conditions the benign corpus cannot
|
|
651
|
+
fully exercise (a field no sample fills, such as `tool_name`, or a method the
|
|
652
|
+
synchronous engine cannot run, such as `trace`) scored 0 false positives
|
|
653
|
+
without being read, and `--write` promoted it to `stable`. Such a rule is now
|
|
654
|
+
reported as not promotable; one that did false-positive stays with the rules
|
|
655
|
+
that have false positives. No `maturity: test` rule on main is affected today.
|
|
656
|
+
- **verify-adopters.yml no longer fails when other commits land on main during
|
|
657
|
+
a run, and its failures get noticed.** The job commits the refreshed
|
|
658
|
+
`data/adopters-verification.json` straight to main with a plain push, which
|
|
659
|
+
was rejected whenever another commit landed first, failing the run with
|
|
660
|
+
nothing wrong in ADOPTERS.md (2026-10-07, 2026-10-15). It now rebases its
|
|
661
|
+
one-file commit onto the moved main and retries, at most three times; runs
|
|
662
|
+
one verification at a time per branch; and checks out the branch tip rather
|
|
663
|
+
than the triggering commit, so a run that waited behind another starts from
|
|
664
|
+
the data the other pushed instead of conflicting with it (the 2026-10-07
|
|
665
|
+
shape: two adopter merges, each run writing the same file). A conflict with
|
|
666
|
+
a hand edit of the generated file still fails the run. Four red runs in a
|
|
667
|
+
row (2026-10-12 to 10-15) went unnoticed for days; a failure on main now
|
|
668
|
+
opens one tracking issue, or rewrites the open one, and the next green run
|
|
669
|
+
closes it. The issue lists drift only from a verification that finished.
|
|
670
|
+
|
|
7
671
|
## [4.1.5] - 2026-10-10
|
|
8
672
|
|
|
9
673
|
4.1.4 was staged on npm and removed by publish-time scanning before it could be
|
|
@@ -788,6 +1452,14 @@ No rule content changed and no threshold was loosened in any of the above.
|
|
|
788
1452
|
### Metrics
|
|
789
1453
|
|
|
790
1454
|
- HackAPrompt (4,780 samples): recall 28.6% → 66.2% (+37.6pp), 100% precision maintained.
|
|
1455
|
+
- Erratum (2026-10-19): re-run on the same 4,780 samples at the sprint commits,
|
|
1456
|
+
recall was 28.6% (1,368, at `3a6145b7`) → 65.0% (3,108, at `e9efefdd`) and
|
|
1457
|
+
66.0% (3,154, at `92c0d0fe`); 66.2% could not be reproduced at either. The
|
|
1458
|
+
28.6% starting point was itself in-sample (ATR-2026-00442..00447 were mined
|
|
1459
|
+
from this subset). "100% precision" was not a measurement: the corpus has
|
|
1460
|
+
no benign samples, so precision is undefined on it. Procedure and figures:
|
|
1461
|
+
`data/hackaprompt/METHOD.md`; current measurement, with the ablation
|
|
1462
|
+
reference: `data/measurements/hackaprompt/`.
|
|
791
1463
|
- garak (3,475 prompts): ATR-core families ~80%+; per-family breakdown: latentinjection 34.4%, sysprompt_extraction 67.9%, dan 90.2%.
|
|
792
1464
|
- PINT (850 samples): 0.25% FP maintained, 0 FP regression.
|
|
793
1465
|
- SKILL.md (341 samples): 100% precision, 0% FP, 0 FP regression.
|
|
@@ -856,6 +1528,13 @@ No rule content changed and no threshold was loosened in any of the above.
|
|
|
856
1528
|
|
|
857
1529
|
## [2.1.0] - 2026-05-09
|
|
858
1530
|
|
|
1531
|
+
> **Historical entry — the AI RMF line below overstated coverage.**
|
|
1532
|
+
> "100% coverage of all 4 AI RMF functions" described the community OSCAL
|
|
1533
|
+
> catalog, which encodes all 72 AI RMF subcategories as controls. ATR's
|
|
1534
|
+
> rules map to a subset of those subcategories. The catalog is a community
|
|
1535
|
+
> contribution and is not endorsed by NIST. Do not quote the line as a
|
|
1536
|
+
> coverage claim. (Note added 2026-10-16.)
|
|
1537
|
+
|
|
859
1538
|
### Added
|
|
860
1539
|
|
|
861
1540
|
- **NIST AI RMF mapping v0.2** — 100% coverage of all 4 AI RMF functions (Govern / Map / Measure / Manage), 72 controls + 31 cross-reference links + worked example profile. Schema-valid OSCAL catalog published under `Agent-Threat-Rule/ai-rmf-oscal-catalog` (PR #46).
|
|
@@ -867,6 +1546,14 @@ No rule content changed and no threshold was loosened in any of the above.
|
|
|
867
1546
|
|
|
868
1547
|
## [2.0.11] - 2026-04-21
|
|
869
1548
|
|
|
1549
|
+
> **Historical entry — do not quote the garak or SKILL.md figures below.**
|
|
1550
|
+
> The 97.1% in-the-wild recall was measured on a garak corpus that many of
|
|
1551
|
+
> this release's new rules were written from, so it is not an out-of-sample
|
|
1552
|
+
> result. The SKILL.md precision and FP figures are in-sample: the benign
|
|
1553
|
+
> set is the one the rules were tuned against. Current figures, reported
|
|
1554
|
+
> per lane and with their closed-book share, are in README §8 Evaluation.
|
|
1555
|
+
> (Note added 2026-10-16.)
|
|
1556
|
+
|
|
870
1557
|
### Added
|
|
871
1558
|
|
|
872
1559
|
- **193 new rules** covering the full NVIDIA garak probe corpus (ATR-00300 ~ ATR-00414), bringing total to **311 rules**
|
|
@@ -966,7 +1653,7 @@ No rule content changed and no threshold was loosened in any of the above.
|
|
|
966
1653
|
- Wild scan (96K): 1.35% flag rate, 751 confirmed malware
|
|
967
1654
|
- OWASP Agentic Top 10: 10/10 coverage
|
|
968
1655
|
- MITRE ATLAS: 100/113 rules mapped
|
|
969
|
-
- SAFE-MCP:
|
|
1656
|
+
- SAFE-MCP: technique coverage figure (withdrawn 2026-10: it was not backed by rule-level data; see docs/SAFE-MCP-MAPPING.md)
|
|
970
1657
|
- Avg scan latency: 14ms per file
|
|
971
1658
|
|
|
972
1659
|
### Ecosystem
|