agent-threat-rules 3.5.12 → 4.0.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/README.md +119 -23
- package/dist/action-executor.d.ts +31 -0
- package/dist/action-executor.d.ts.map +1 -1
- package/dist/action-executor.js +29 -0
- package/dist/action-executor.js.map +1 -1
- package/dist/cli/scan-handler.d.ts +9 -0
- package/dist/cli/scan-handler.d.ts.map +1 -1
- package/dist/cli/scan-handler.js +12 -2
- package/dist/cli/scan-handler.js.map +1 -1
- package/dist/cli.js +131 -8
- package/dist/cli.js.map +1 -1
- package/dist/enforcement.d.ts +233 -0
- package/dist/enforcement.d.ts.map +1 -0
- package/dist/enforcement.js +316 -0
- package/dist/enforcement.js.map +1 -0
- package/dist/engine.d.ts +18 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +17 -3
- package/dist/engine.js.map +1 -1
- package/dist/hook-handler.d.ts +98 -3
- package/dist/hook-handler.d.ts.map +1 -1
- package/dist/hook-handler.js +119 -12
- package/dist/hook-handler.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.d.ts +13 -2
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +22 -5
- package/dist/mcp-server.js.map +1 -1
- package/dist/types.d.ts +13 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/rules/model-abuse/ATR-2026-01750-dos-repeat-word-n-times-output-amplification.yaml +14 -2
- package/rules/prompt-injection/ATR-2026-00086-visual-spoofing.yaml +56 -6
- package/rules/prompt-injection/ATR-2026-00443-word-fragment-concat-assembly.yaml +11 -2
- package/rules/prompt-injection/ATR-2026-01904-link-injection.yaml +12 -3
- package/rules/prompt-injection/ATR-2026-02500-prompt-layer-self-replication.yaml +287 -0
- package/rules/prompt-injection/ATR-2026-02502-covert-remote-script-injection-into-generated-artifacts.yaml +209 -0
- package/rules/tool-poisoning/ATR-2026-00103-hidden-safety-bypass-instruction.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-00161-important-tag-cross-tool-shadowing.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-01775-semantic-mcp-tool-manifest-poisoning.yaml +1 -1
- package/rules/tool-poisoning/ATR-2026-02025-mcp-full-schema-poisoning-nondescription-field.yaml +1 -1
- package/spec/atr-method-v1.1.md +2 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforcement policy — the two operator switches, and the one precedence rule
|
|
3
|
+
* that governs both.
|
|
4
|
+
*
|
|
5
|
+
* ATR separates DETECTION from ENFORCEMENT. Detection always runs; enforcement
|
|
6
|
+
* is what the engine is allowed to DO with a detection:
|
|
7
|
+
*
|
|
8
|
+
* lane Which rule maturities may fire at all.
|
|
9
|
+
* 'enforce' = stable only, 'alert' = stable+test, 'hunt' = all.
|
|
10
|
+
* Implemented by the lane gate in engine.ts (see passesLane).
|
|
11
|
+
*
|
|
12
|
+
* blocking Whether ATR may express a blocking decision. OFF BY DEFAULT.
|
|
13
|
+
* When off: the Claude Code hook contract omits permissionDecision
|
|
14
|
+
* entirely (the host applies its own default — ATR does not vote),
|
|
15
|
+
* and the ActionExecutor refuses to dispatch any response action
|
|
16
|
+
* above the OBSERVE blast-radius tier. Detection output is
|
|
17
|
+
* unchanged in both modes.
|
|
18
|
+
*
|
|
19
|
+
* WHY BLOCKING IS OPT-IN
|
|
20
|
+
*
|
|
21
|
+
* The load-bearing requirement is SPEC.md §5.5, which is engine-wide:
|
|
22
|
+
* "Engines MUST NOT execute response actions automatically without an explicit
|
|
23
|
+
* configuration directive from the operator." Until this module existed there
|
|
24
|
+
* was no way for an operator to express that directive — no CLI flag, no
|
|
25
|
+
* environment variable, no documented config key. This module IS that directive.
|
|
26
|
+
*
|
|
27
|
+
* Two weaker statements point the same way but must not be quoted as general
|
|
28
|
+
* rules, because they are not:
|
|
29
|
+
* - spec/atr-method-v1.1.md §5.6 says "Engines SHOULD NOT auto-block ON A
|
|
30
|
+
* HASH MATCH without operator policy explicitly enabling it". That sits
|
|
31
|
+
* under §5 Signature Method and is scoped to hash matches. An earlier
|
|
32
|
+
* revision of this comment elided "on a hash match" and widened it into a
|
|
33
|
+
* general SHOULD NOT. It is not one.
|
|
34
|
+
* - docs/QUALITY-STANDARD.md restricts blocking to `maturity: stable` rules,
|
|
35
|
+
* but that section is deployment guidance addressed to consumers, not a
|
|
36
|
+
* normative engine requirement.
|
|
37
|
+
*
|
|
38
|
+
* WHY ATR NEVER SAYS "allow"
|
|
39
|
+
*
|
|
40
|
+
* In the Claude Code PreToolUse contract, `permissionDecision: "allow"` is not
|
|
41
|
+
* neutral — it is an affirmative approval that suppresses the host's own
|
|
42
|
+
* permission prompt. ATR emits a permission decision only to RESTRAIN an
|
|
43
|
+
* operation (deny / ask). "No rule matched" is not "I approve this": it is ATR
|
|
44
|
+
* having nothing to say, and the host must be left on its own default path.
|
|
45
|
+
* This holds in BOTH modes — advisory omits every decision, and blocking omits
|
|
46
|
+
* the affirmative one. See toClaudeCodePreToolUse in hook-handler.ts.
|
|
47
|
+
*
|
|
48
|
+
* WHO READS THE ENVIRONMENT
|
|
49
|
+
*
|
|
50
|
+
* For the lane and blocking switches: only the CLI. `resolveEnforcementPolicy`
|
|
51
|
+
* below is the only function that reads `process.env` for THESE TWO switches,
|
|
52
|
+
* and `src/cli.ts` is its only caller; it hands the resolved values to
|
|
53
|
+
* ATREngine / ActionExecutor / HookHandler explicitly.
|
|
54
|
+
*
|
|
55
|
+
* Not "the only function in the package that touches process.env" — that is
|
|
56
|
+
* false and an earlier revision of this comment said it. Ten files under src/
|
|
57
|
+
* read the environment in code, including `adapters/openshell-filter.ts` and
|
|
58
|
+
* `adapters/nemoclaw-preflight.ts`, which read `ATR_MIN_SEVERITY` and block by
|
|
59
|
+
* default. Those adapters are outside this switch and stay that way; see the
|
|
60
|
+
* scope limit in CHANGELOG.md.
|
|
61
|
+
*
|
|
62
|
+
* `grep -rl "process\.env" src/` returns eleven, not ten: `src/index.ts` only
|
|
63
|
+
* names it in a comment. The correction above was itself first written as
|
|
64
|
+
* "eleven" off that grep, which is the same mistake one level down.
|
|
65
|
+
*
|
|
66
|
+
* Library constructors take `laneFromConfig` / `blockingFromConfig`, which
|
|
67
|
+
* cannot read the environment — `resolveLane` and `resolveBlocking` require an
|
|
68
|
+
* EnvSource argument, so there is no overload that silently falls through to
|
|
69
|
+
* `process.env`. This is structural, not a convention: an embedder cannot
|
|
70
|
+
* accidentally opt into ambient configuration.
|
|
71
|
+
*
|
|
72
|
+
* The rule exists because ambient configuration crosses trust boundaries
|
|
73
|
+
* invisibly. `new ATREngine()` inside a VS Code extension, a Mastra pipeline,
|
|
74
|
+
* or the `/openshell-filter`, `/nemoclaw-preflight` and `/mcp` entry points did
|
|
75
|
+
* not ask to be reconfigured by a shell profile — and `ATR_LANE=enforce` is a
|
|
76
|
+
* VALID value, so the old behaviour produced no warning at all while narrowing
|
|
77
|
+
* those callers to maturity=stable rules only.
|
|
78
|
+
*
|
|
79
|
+
* PRECEDENCE
|
|
80
|
+
*
|
|
81
|
+
* library: explicit config > built-in default
|
|
82
|
+
* CLI: flag > environment variable > built-in default
|
|
83
|
+
*
|
|
84
|
+
* An invalid explicit value (config field or CLI flag) throws or exits: that is
|
|
85
|
+
* the caller's own bug, reported where the caller is looking. An invalid
|
|
86
|
+
* ENVIRONMENT value degrades loudly instead — see resolveEnforcementPolicy for
|
|
87
|
+
* the measurement behind that split.
|
|
88
|
+
*
|
|
89
|
+
* @module agent-threat-rules/enforcement
|
|
90
|
+
*/
|
|
91
|
+
import { type Lane } from './quality/rule-contract.js';
|
|
92
|
+
/** Environment variable naming the detection lane. */
|
|
93
|
+
export declare const LANE_ENV_VAR = "ATR_LANE";
|
|
94
|
+
/** Environment variable enabling blocking (the operator directive). */
|
|
95
|
+
export declare const BLOCKING_ENV_VAR = "ATR_BLOCKING";
|
|
96
|
+
/**
|
|
97
|
+
* Lane used when neither config nor environment says otherwise.
|
|
98
|
+
* 'hunt' keeps every maturity visible — advisory breadth, which is safe
|
|
99
|
+
* precisely because blocking is off by default.
|
|
100
|
+
*/
|
|
101
|
+
export declare const DEFAULT_LANE: Lane;
|
|
102
|
+
/** Blocking is off until an operator turns it on. */
|
|
103
|
+
export declare const DEFAULT_BLOCKING = false;
|
|
104
|
+
/** Minimal read-only view of an environment. Keeps this module testable. */
|
|
105
|
+
export type EnvSource = Readonly<Record<string, string | undefined>>;
|
|
106
|
+
/**
|
|
107
|
+
* Parse a lane name. Returns null for anything not in LANES, so callers that
|
|
108
|
+
* want to report a usage error (the CLI) can do so without a try/catch.
|
|
109
|
+
*
|
|
110
|
+
* Trimmed and case-INSENSITIVE, matching parseBooleanFlag exactly. The two were
|
|
111
|
+
* asymmetric: `ATR_BLOCKING=ON` worked while `ATR_LANE=ENFORCE` did not, and the
|
|
112
|
+
* pair `ATR_LANE=ENFORCE ATR_BLOCKING=ON` therefore turned blocking on while
|
|
113
|
+
* silently widening the lane to hunt — the operator asked for the narrowest
|
|
114
|
+
* enforcement posture and got the broadest one. The fallback direction was
|
|
115
|
+
* exactly the dangerous one, so the fix is to accept the spelling, not to
|
|
116
|
+
* document the trap.
|
|
117
|
+
*/
|
|
118
|
+
export declare function parseLane(value: string): Lane | null;
|
|
119
|
+
/**
|
|
120
|
+
* Parse a boolean switch value. Returns null for anything unrecognised.
|
|
121
|
+
* An empty string is treated as "not set" by the resolvers, not by this
|
|
122
|
+
* function — it returns null here so the caller decides.
|
|
123
|
+
*/
|
|
124
|
+
export declare function parseBooleanFlag(value: string): boolean | null;
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the active detection lane: explicit > env > default.
|
|
127
|
+
*
|
|
128
|
+
* `env` is REQUIRED and has no `process.env` default. That is the whole
|
|
129
|
+
* mechanism keeping ambient configuration out of the library: a caller must
|
|
130
|
+
* name the environment it wants read, and library constructors pass NO_ENV via
|
|
131
|
+
* laneFromConfig. Only the CLI passes `process.env`.
|
|
132
|
+
*
|
|
133
|
+
* @throws TypeError when either source carries a value that is not a lane.
|
|
134
|
+
*/
|
|
135
|
+
export declare function resolveLane(explicit: Lane | string | undefined, env: EnvSource): Lane;
|
|
136
|
+
/**
|
|
137
|
+
* Resolve whether blocking is enabled: explicit > env > default (false).
|
|
138
|
+
*
|
|
139
|
+
* `env` is REQUIRED — see resolveLane for why.
|
|
140
|
+
*
|
|
141
|
+
* @throws TypeError when the explicit value is not a boolean, or when the
|
|
142
|
+
* environment variable is set to a value that is neither truthy nor
|
|
143
|
+
* falsy by the table above.
|
|
144
|
+
*
|
|
145
|
+
* The explicit-value check is not decoration. `blocking` crosses from
|
|
146
|
+
* untyped JavaScript (and from JSON config files) as often as from
|
|
147
|
+
* TypeScript, and every non-empty string is truthy: without this,
|
|
148
|
+
* `new ActionExecutor({ adapter, blocking: "false" })` TURNS BLOCKING
|
|
149
|
+
* ON while reading, to its author, as an explicit "off". The lane path
|
|
150
|
+
* validated its explicit value from the start; this one did not, and a
|
|
151
|
+
* switch that fails toward "more enforcement" is the wrong asymmetry to
|
|
152
|
+
* leave in place.
|
|
153
|
+
*/
|
|
154
|
+
export declare function resolveBlocking(explicit: boolean | undefined, env: EnvSource): boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Lane for a LIBRARY caller: explicit config, or the built-in default. Never
|
|
157
|
+
* reads the environment.
|
|
158
|
+
*
|
|
159
|
+
* @throws TypeError when the caller passes something that is not a lane.
|
|
160
|
+
*/
|
|
161
|
+
export declare function laneFromConfig(explicit?: Lane | string): Lane;
|
|
162
|
+
/**
|
|
163
|
+
* Blocking for a LIBRARY caller: explicit config, or off. Never reads the
|
|
164
|
+
* environment.
|
|
165
|
+
*
|
|
166
|
+
* @throws TypeError when the caller passes a non-boolean.
|
|
167
|
+
*/
|
|
168
|
+
export declare function blockingFromConfig(explicit?: boolean): boolean;
|
|
169
|
+
/** The enforcement posture a CLI process will run under. */
|
|
170
|
+
export interface EnforcementPolicy {
|
|
171
|
+
readonly lane: Lane;
|
|
172
|
+
readonly blocking: boolean;
|
|
173
|
+
/** Human-readable problems found while resolving. Empty on a clean read. */
|
|
174
|
+
readonly notes: readonly string[];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Resolve the posture for a CLI process: flag > environment > default.
|
|
178
|
+
*
|
|
179
|
+
* THE ONLY FUNCTION IN THIS PACKAGE THAT READS `process.env`.
|
|
180
|
+
*
|
|
181
|
+
* A bad FLAG is a usage error the CLI reports and exits on (see readLaneOption
|
|
182
|
+
* in cli.ts): it was typed by a person at a prompt who is looking at the exit
|
|
183
|
+
* code. A bad ENVIRONMENT VALUE is different, and this function degrades
|
|
184
|
+
* instead of throwing. That split was measured, not assumed.
|
|
185
|
+
*
|
|
186
|
+
* MEASUREMENT (Claude Code 2.1.76, hook dispatcher in the shipped binary)
|
|
187
|
+
* A `command` hook's exit status is mapped as:
|
|
188
|
+
* status 0 -> `hook_success`; the renderer for that attachment returns null.
|
|
189
|
+
* status 2 -> `hook_blocking_error`; stderr is shown AND the tool is blocked.
|
|
190
|
+
* otherwise -> `hook_non_blocking_error`; the tool RUNS, the attachment maps
|
|
191
|
+
* to [] for the model, and the renderer prints exactly
|
|
192
|
+
* "<hookName> hook error" — the captured stderr is not shown.
|
|
193
|
+
*
|
|
194
|
+
* So for `atr guard`, which `atr init` installs as a PreToolUse command hook,
|
|
195
|
+
* exiting non-zero over a typo would: lose all detection for that call, let the
|
|
196
|
+
* tool run anyway, and tell the operator nothing beyond a bare "hook error".
|
|
197
|
+
* It buys no visibility at the cost of the entire product. The only loud exit
|
|
198
|
+
* status is 2 — which BLOCKS the tool, i.e. a stray shell variable would block
|
|
199
|
+
* every tool call. Both alternatives are worse than degrading.
|
|
200
|
+
*
|
|
201
|
+
* THE TRADE-OFF, STATED
|
|
202
|
+
* Degrading means an operator can believe enforcement is on when it is not.
|
|
203
|
+
* That is a real cost and it is why `notes` is not optional: the caller must
|
|
204
|
+
* print every note. It is accepted because the degraded posture is advisory —
|
|
205
|
+
* ATR emits no permission decision and dispatches no action above OBSERVE — so
|
|
206
|
+
* the failure mode is "ATR does not act", never "ATR acts wrongly". Exiting
|
|
207
|
+
* would produce "ATR does not act" too, and silently, minus the detection.
|
|
208
|
+
*
|
|
209
|
+
* WHY AN UNREADABLE LANE ALSO FORCES BLOCKING OFF
|
|
210
|
+
* Falling back to hunt while blocking is on is the one combination that makes
|
|
211
|
+
* the degraded posture MORE dangerous than the requested one: the operator
|
|
212
|
+
* asked to block on maturity=stable rules only, and would instead block on
|
|
213
|
+
* every maturity including draft. So when the requested lane cannot be honoured
|
|
214
|
+
* the process drops to advisory. An explicit `--blocking` does not override
|
|
215
|
+
* this: the flag says "you may block", not "block on a lane I never chose".
|
|
216
|
+
*/
|
|
217
|
+
export declare function resolveEnforcementPolicy(flags?: {
|
|
218
|
+
readonly lane?: Lane | undefined;
|
|
219
|
+
readonly blocking?: boolean | undefined;
|
|
220
|
+
}, env?: EnvSource): EnforcementPolicy;
|
|
221
|
+
/**
|
|
222
|
+
* Is this response action enforcement rather than observation?
|
|
223
|
+
*
|
|
224
|
+
* The observe/enforce split is NOT redefined here — it reads the blast-radius
|
|
225
|
+
* ladder in src/quality/action-eligibility.ts, which is the project's single
|
|
226
|
+
* ranking of what an action destroys. OBSERVE (alert / snapshot / shadow /
|
|
227
|
+
* escalate) changes nothing about the agent's execution and therefore always
|
|
228
|
+
* runs. Everything above it (INTERRUPT / DEGRADE / TERMINATE) denies an
|
|
229
|
+
* operation, alters session state, or destroys the session — that is
|
|
230
|
+
* enforcement and requires the operator directive.
|
|
231
|
+
*/
|
|
232
|
+
export declare function isEnforcementAction(action: string): boolean;
|
|
233
|
+
//# sourceMappingURL=enforcement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enforcement.d.ts","sourceRoot":"","sources":["../src/enforcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AAEH,OAAO,EAAS,KAAK,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAG9D,sDAAsD;AACtD,eAAO,MAAM,YAAY,aAAa,CAAC;AAEvC,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,iBAAiB,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,IAAa,CAAC;AAEzC,qDAAqD;AACrD,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAOtC,4EAA4E;AAC5E,MAAM,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAGpD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAK9D;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,EACnC,GAAG,EAAE,SAAS,GACb,IAAI,CAuBN;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,OAAO,GAAG,SAAS,EAC7B,GAAG,EAAE,SAAS,GACb,OAAO,CAyBT;AASD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAE7D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAE9D;AAED,4DAA4D;AAC5D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,GAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAAO,EACzF,GAAG,GAAE,SAAuB,GAC3B,iBAAiB,CAuCnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE3D"}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforcement policy — the two operator switches, and the one precedence rule
|
|
3
|
+
* that governs both.
|
|
4
|
+
*
|
|
5
|
+
* ATR separates DETECTION from ENFORCEMENT. Detection always runs; enforcement
|
|
6
|
+
* is what the engine is allowed to DO with a detection:
|
|
7
|
+
*
|
|
8
|
+
* lane Which rule maturities may fire at all.
|
|
9
|
+
* 'enforce' = stable only, 'alert' = stable+test, 'hunt' = all.
|
|
10
|
+
* Implemented by the lane gate in engine.ts (see passesLane).
|
|
11
|
+
*
|
|
12
|
+
* blocking Whether ATR may express a blocking decision. OFF BY DEFAULT.
|
|
13
|
+
* When off: the Claude Code hook contract omits permissionDecision
|
|
14
|
+
* entirely (the host applies its own default — ATR does not vote),
|
|
15
|
+
* and the ActionExecutor refuses to dispatch any response action
|
|
16
|
+
* above the OBSERVE blast-radius tier. Detection output is
|
|
17
|
+
* unchanged in both modes.
|
|
18
|
+
*
|
|
19
|
+
* WHY BLOCKING IS OPT-IN
|
|
20
|
+
*
|
|
21
|
+
* The load-bearing requirement is SPEC.md §5.5, which is engine-wide:
|
|
22
|
+
* "Engines MUST NOT execute response actions automatically without an explicit
|
|
23
|
+
* configuration directive from the operator." Until this module existed there
|
|
24
|
+
* was no way for an operator to express that directive — no CLI flag, no
|
|
25
|
+
* environment variable, no documented config key. This module IS that directive.
|
|
26
|
+
*
|
|
27
|
+
* Two weaker statements point the same way but must not be quoted as general
|
|
28
|
+
* rules, because they are not:
|
|
29
|
+
* - spec/atr-method-v1.1.md §5.6 says "Engines SHOULD NOT auto-block ON A
|
|
30
|
+
* HASH MATCH without operator policy explicitly enabling it". That sits
|
|
31
|
+
* under §5 Signature Method and is scoped to hash matches. An earlier
|
|
32
|
+
* revision of this comment elided "on a hash match" and widened it into a
|
|
33
|
+
* general SHOULD NOT. It is not one.
|
|
34
|
+
* - docs/QUALITY-STANDARD.md restricts blocking to `maturity: stable` rules,
|
|
35
|
+
* but that section is deployment guidance addressed to consumers, not a
|
|
36
|
+
* normative engine requirement.
|
|
37
|
+
*
|
|
38
|
+
* WHY ATR NEVER SAYS "allow"
|
|
39
|
+
*
|
|
40
|
+
* In the Claude Code PreToolUse contract, `permissionDecision: "allow"` is not
|
|
41
|
+
* neutral — it is an affirmative approval that suppresses the host's own
|
|
42
|
+
* permission prompt. ATR emits a permission decision only to RESTRAIN an
|
|
43
|
+
* operation (deny / ask). "No rule matched" is not "I approve this": it is ATR
|
|
44
|
+
* having nothing to say, and the host must be left on its own default path.
|
|
45
|
+
* This holds in BOTH modes — advisory omits every decision, and blocking omits
|
|
46
|
+
* the affirmative one. See toClaudeCodePreToolUse in hook-handler.ts.
|
|
47
|
+
*
|
|
48
|
+
* WHO READS THE ENVIRONMENT
|
|
49
|
+
*
|
|
50
|
+
* For the lane and blocking switches: only the CLI. `resolveEnforcementPolicy`
|
|
51
|
+
* below is the only function that reads `process.env` for THESE TWO switches,
|
|
52
|
+
* and `src/cli.ts` is its only caller; it hands the resolved values to
|
|
53
|
+
* ATREngine / ActionExecutor / HookHandler explicitly.
|
|
54
|
+
*
|
|
55
|
+
* Not "the only function in the package that touches process.env" — that is
|
|
56
|
+
* false and an earlier revision of this comment said it. Ten files under src/
|
|
57
|
+
* read the environment in code, including `adapters/openshell-filter.ts` and
|
|
58
|
+
* `adapters/nemoclaw-preflight.ts`, which read `ATR_MIN_SEVERITY` and block by
|
|
59
|
+
* default. Those adapters are outside this switch and stay that way; see the
|
|
60
|
+
* scope limit in CHANGELOG.md.
|
|
61
|
+
*
|
|
62
|
+
* `grep -rl "process\.env" src/` returns eleven, not ten: `src/index.ts` only
|
|
63
|
+
* names it in a comment. The correction above was itself first written as
|
|
64
|
+
* "eleven" off that grep, which is the same mistake one level down.
|
|
65
|
+
*
|
|
66
|
+
* Library constructors take `laneFromConfig` / `blockingFromConfig`, which
|
|
67
|
+
* cannot read the environment — `resolveLane` and `resolveBlocking` require an
|
|
68
|
+
* EnvSource argument, so there is no overload that silently falls through to
|
|
69
|
+
* `process.env`. This is structural, not a convention: an embedder cannot
|
|
70
|
+
* accidentally opt into ambient configuration.
|
|
71
|
+
*
|
|
72
|
+
* The rule exists because ambient configuration crosses trust boundaries
|
|
73
|
+
* invisibly. `new ATREngine()` inside a VS Code extension, a Mastra pipeline,
|
|
74
|
+
* or the `/openshell-filter`, `/nemoclaw-preflight` and `/mcp` entry points did
|
|
75
|
+
* not ask to be reconfigured by a shell profile — and `ATR_LANE=enforce` is a
|
|
76
|
+
* VALID value, so the old behaviour produced no warning at all while narrowing
|
|
77
|
+
* those callers to maturity=stable rules only.
|
|
78
|
+
*
|
|
79
|
+
* PRECEDENCE
|
|
80
|
+
*
|
|
81
|
+
* library: explicit config > built-in default
|
|
82
|
+
* CLI: flag > environment variable > built-in default
|
|
83
|
+
*
|
|
84
|
+
* An invalid explicit value (config field or CLI flag) throws or exits: that is
|
|
85
|
+
* the caller's own bug, reported where the caller is looking. An invalid
|
|
86
|
+
* ENVIRONMENT value degrades loudly instead — see resolveEnforcementPolicy for
|
|
87
|
+
* the measurement behind that split.
|
|
88
|
+
*
|
|
89
|
+
* @module agent-threat-rules/enforcement
|
|
90
|
+
*/
|
|
91
|
+
import { LANES } from './quality/rule-contract.js';
|
|
92
|
+
import { ACTION_TIERS, actionTier } from './quality/action-eligibility.js';
|
|
93
|
+
/** Environment variable naming the detection lane. */
|
|
94
|
+
export const LANE_ENV_VAR = 'ATR_LANE';
|
|
95
|
+
/** Environment variable enabling blocking (the operator directive). */
|
|
96
|
+
export const BLOCKING_ENV_VAR = 'ATR_BLOCKING';
|
|
97
|
+
/**
|
|
98
|
+
* Lane used when neither config nor environment says otherwise.
|
|
99
|
+
* 'hunt' keeps every maturity visible — advisory breadth, which is safe
|
|
100
|
+
* precisely because blocking is off by default.
|
|
101
|
+
*/
|
|
102
|
+
export const DEFAULT_LANE = 'hunt';
|
|
103
|
+
/** Blocking is off until an operator turns it on. */
|
|
104
|
+
export const DEFAULT_BLOCKING = false;
|
|
105
|
+
const LANE_SET = new Set(LANES);
|
|
106
|
+
const TRUE_VALUES = new Set(['1', 'true', 'yes', 'on']);
|
|
107
|
+
const FALSE_VALUES = new Set(['0', 'false', 'no', 'off']);
|
|
108
|
+
/**
|
|
109
|
+
* Parse a lane name. Returns null for anything not in LANES, so callers that
|
|
110
|
+
* want to report a usage error (the CLI) can do so without a try/catch.
|
|
111
|
+
*
|
|
112
|
+
* Trimmed and case-INSENSITIVE, matching parseBooleanFlag exactly. The two were
|
|
113
|
+
* asymmetric: `ATR_BLOCKING=ON` worked while `ATR_LANE=ENFORCE` did not, and the
|
|
114
|
+
* pair `ATR_LANE=ENFORCE ATR_BLOCKING=ON` therefore turned blocking on while
|
|
115
|
+
* silently widening the lane to hunt — the operator asked for the narrowest
|
|
116
|
+
* enforcement posture and got the broadest one. The fallback direction was
|
|
117
|
+
* exactly the dangerous one, so the fix is to accept the spelling, not to
|
|
118
|
+
* document the trap.
|
|
119
|
+
*/
|
|
120
|
+
export function parseLane(value) {
|
|
121
|
+
const normalized = value.trim().toLowerCase();
|
|
122
|
+
return LANE_SET.has(normalized) ? normalized : null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Parse a boolean switch value. Returns null for anything unrecognised.
|
|
126
|
+
* An empty string is treated as "not set" by the resolvers, not by this
|
|
127
|
+
* function — it returns null here so the caller decides.
|
|
128
|
+
*/
|
|
129
|
+
export function parseBooleanFlag(value) {
|
|
130
|
+
const normalized = value.trim().toLowerCase();
|
|
131
|
+
if (TRUE_VALUES.has(normalized))
|
|
132
|
+
return true;
|
|
133
|
+
if (FALSE_VALUES.has(normalized))
|
|
134
|
+
return false;
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Resolve the active detection lane: explicit > env > default.
|
|
139
|
+
*
|
|
140
|
+
* `env` is REQUIRED and has no `process.env` default. That is the whole
|
|
141
|
+
* mechanism keeping ambient configuration out of the library: a caller must
|
|
142
|
+
* name the environment it wants read, and library constructors pass NO_ENV via
|
|
143
|
+
* laneFromConfig. Only the CLI passes `process.env`.
|
|
144
|
+
*
|
|
145
|
+
* @throws TypeError when either source carries a value that is not a lane.
|
|
146
|
+
*/
|
|
147
|
+
export function resolveLane(explicit, env) {
|
|
148
|
+
if (explicit !== undefined) {
|
|
149
|
+
const parsed = parseLane(explicit);
|
|
150
|
+
if (parsed === null) {
|
|
151
|
+
throw new TypeError(`Invalid lane "${explicit}". Expected one of: ${LANES.join(', ')}.`);
|
|
152
|
+
}
|
|
153
|
+
return parsed;
|
|
154
|
+
}
|
|
155
|
+
const fromEnv = env[LANE_ENV_VAR];
|
|
156
|
+
if (fromEnv !== undefined && fromEnv.trim() !== '') {
|
|
157
|
+
const parsed = parseLane(fromEnv);
|
|
158
|
+
if (parsed === null) {
|
|
159
|
+
throw new TypeError(`Invalid ${LANE_ENV_VAR}="${fromEnv}". Expected one of: ${LANES.join(', ')}.`);
|
|
160
|
+
}
|
|
161
|
+
return parsed;
|
|
162
|
+
}
|
|
163
|
+
return DEFAULT_LANE;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve whether blocking is enabled: explicit > env > default (false).
|
|
167
|
+
*
|
|
168
|
+
* `env` is REQUIRED — see resolveLane for why.
|
|
169
|
+
*
|
|
170
|
+
* @throws TypeError when the explicit value is not a boolean, or when the
|
|
171
|
+
* environment variable is set to a value that is neither truthy nor
|
|
172
|
+
* falsy by the table above.
|
|
173
|
+
*
|
|
174
|
+
* The explicit-value check is not decoration. `blocking` crosses from
|
|
175
|
+
* untyped JavaScript (and from JSON config files) as often as from
|
|
176
|
+
* TypeScript, and every non-empty string is truthy: without this,
|
|
177
|
+
* `new ActionExecutor({ adapter, blocking: "false" })` TURNS BLOCKING
|
|
178
|
+
* ON while reading, to its author, as an explicit "off". The lane path
|
|
179
|
+
* validated its explicit value from the start; this one did not, and a
|
|
180
|
+
* switch that fails toward "more enforcement" is the wrong asymmetry to
|
|
181
|
+
* leave in place.
|
|
182
|
+
*/
|
|
183
|
+
export function resolveBlocking(explicit, env) {
|
|
184
|
+
if (explicit !== undefined) {
|
|
185
|
+
if (typeof explicit !== 'boolean') {
|
|
186
|
+
throw new TypeError(`Invalid blocking value ${JSON.stringify(explicit)} (${typeof explicit}). ` +
|
|
187
|
+
`Expected a boolean. Note that any non-empty string, including "false", ` +
|
|
188
|
+
`would otherwise enable blocking.`);
|
|
189
|
+
}
|
|
190
|
+
return explicit;
|
|
191
|
+
}
|
|
192
|
+
const fromEnv = env[BLOCKING_ENV_VAR];
|
|
193
|
+
if (fromEnv !== undefined && fromEnv.trim() !== '') {
|
|
194
|
+
const parsed = parseBooleanFlag(fromEnv);
|
|
195
|
+
if (parsed === null) {
|
|
196
|
+
throw new TypeError(`Invalid ${BLOCKING_ENV_VAR}="${fromEnv}". Expected one of: ` +
|
|
197
|
+
`${[...TRUE_VALUES].join('/')} or ${[...FALSE_VALUES].join('/')}.`);
|
|
198
|
+
}
|
|
199
|
+
return parsed;
|
|
200
|
+
}
|
|
201
|
+
return DEFAULT_BLOCKING;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* An environment that is empty by construction. Library constructors resolve
|
|
205
|
+
* against this, so "the library does not read the environment" is enforced by
|
|
206
|
+
* the type system rather than by reviewer vigilance.
|
|
207
|
+
*/
|
|
208
|
+
const NO_ENV = Object.freeze({});
|
|
209
|
+
/**
|
|
210
|
+
* Lane for a LIBRARY caller: explicit config, or the built-in default. Never
|
|
211
|
+
* reads the environment.
|
|
212
|
+
*
|
|
213
|
+
* @throws TypeError when the caller passes something that is not a lane.
|
|
214
|
+
*/
|
|
215
|
+
export function laneFromConfig(explicit) {
|
|
216
|
+
return resolveLane(explicit, NO_ENV);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Blocking for a LIBRARY caller: explicit config, or off. Never reads the
|
|
220
|
+
* environment.
|
|
221
|
+
*
|
|
222
|
+
* @throws TypeError when the caller passes a non-boolean.
|
|
223
|
+
*/
|
|
224
|
+
export function blockingFromConfig(explicit) {
|
|
225
|
+
return resolveBlocking(explicit, NO_ENV);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Resolve the posture for a CLI process: flag > environment > default.
|
|
229
|
+
*
|
|
230
|
+
* THE ONLY FUNCTION IN THIS PACKAGE THAT READS `process.env`.
|
|
231
|
+
*
|
|
232
|
+
* A bad FLAG is a usage error the CLI reports and exits on (see readLaneOption
|
|
233
|
+
* in cli.ts): it was typed by a person at a prompt who is looking at the exit
|
|
234
|
+
* code. A bad ENVIRONMENT VALUE is different, and this function degrades
|
|
235
|
+
* instead of throwing. That split was measured, not assumed.
|
|
236
|
+
*
|
|
237
|
+
* MEASUREMENT (Claude Code 2.1.76, hook dispatcher in the shipped binary)
|
|
238
|
+
* A `command` hook's exit status is mapped as:
|
|
239
|
+
* status 0 -> `hook_success`; the renderer for that attachment returns null.
|
|
240
|
+
* status 2 -> `hook_blocking_error`; stderr is shown AND the tool is blocked.
|
|
241
|
+
* otherwise -> `hook_non_blocking_error`; the tool RUNS, the attachment maps
|
|
242
|
+
* to [] for the model, and the renderer prints exactly
|
|
243
|
+
* "<hookName> hook error" — the captured stderr is not shown.
|
|
244
|
+
*
|
|
245
|
+
* So for `atr guard`, which `atr init` installs as a PreToolUse command hook,
|
|
246
|
+
* exiting non-zero over a typo would: lose all detection for that call, let the
|
|
247
|
+
* tool run anyway, and tell the operator nothing beyond a bare "hook error".
|
|
248
|
+
* It buys no visibility at the cost of the entire product. The only loud exit
|
|
249
|
+
* status is 2 — which BLOCKS the tool, i.e. a stray shell variable would block
|
|
250
|
+
* every tool call. Both alternatives are worse than degrading.
|
|
251
|
+
*
|
|
252
|
+
* THE TRADE-OFF, STATED
|
|
253
|
+
* Degrading means an operator can believe enforcement is on when it is not.
|
|
254
|
+
* That is a real cost and it is why `notes` is not optional: the caller must
|
|
255
|
+
* print every note. It is accepted because the degraded posture is advisory —
|
|
256
|
+
* ATR emits no permission decision and dispatches no action above OBSERVE — so
|
|
257
|
+
* the failure mode is "ATR does not act", never "ATR acts wrongly". Exiting
|
|
258
|
+
* would produce "ATR does not act" too, and silently, minus the detection.
|
|
259
|
+
*
|
|
260
|
+
* WHY AN UNREADABLE LANE ALSO FORCES BLOCKING OFF
|
|
261
|
+
* Falling back to hunt while blocking is on is the one combination that makes
|
|
262
|
+
* the degraded posture MORE dangerous than the requested one: the operator
|
|
263
|
+
* asked to block on maturity=stable rules only, and would instead block on
|
|
264
|
+
* every maturity including draft. So when the requested lane cannot be honoured
|
|
265
|
+
* the process drops to advisory. An explicit `--blocking` does not override
|
|
266
|
+
* this: the flag says "you may block", not "block on a lane I never chose".
|
|
267
|
+
*/
|
|
268
|
+
export function resolveEnforcementPolicy(flags = {}, env = process.env) {
|
|
269
|
+
const notes = [];
|
|
270
|
+
let lane;
|
|
271
|
+
let laneHonoured = true;
|
|
272
|
+
try {
|
|
273
|
+
lane = resolveLane(flags.lane, env);
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (flags.lane !== undefined)
|
|
277
|
+
throw error; // a flag is the caller's bug
|
|
278
|
+
laneHonoured = false;
|
|
279
|
+
lane = DEFAULT_LANE;
|
|
280
|
+
notes.push(`${error.message} Falling back to lane "${DEFAULT_LANE}". ` +
|
|
281
|
+
`Detection still runs; this only changes which maturities may fire.`);
|
|
282
|
+
}
|
|
283
|
+
let blocking;
|
|
284
|
+
try {
|
|
285
|
+
blocking = resolveBlocking(flags.blocking, env);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
if (flags.blocking !== undefined)
|
|
289
|
+
throw error;
|
|
290
|
+
blocking = DEFAULT_BLOCKING;
|
|
291
|
+
notes.push(`${error.message} Falling back to blocking=${DEFAULT_BLOCKING}. ` +
|
|
292
|
+
`If you meant to enable enforcement, it is NOT enabled.`);
|
|
293
|
+
}
|
|
294
|
+
if (!laneHonoured && blocking) {
|
|
295
|
+
blocking = false;
|
|
296
|
+
notes.push(`Blocking disabled: ${LANE_ENV_VAR} could not be read, and blocking on the ` +
|
|
297
|
+
`fallback lane "${DEFAULT_LANE}" would enforce on more rule maturities ` +
|
|
298
|
+
`than you asked for. Fix ${LANE_ENV_VAR} to re-enable enforcement.`);
|
|
299
|
+
}
|
|
300
|
+
return Object.freeze({ lane, blocking, notes: Object.freeze(notes) });
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Is this response action enforcement rather than observation?
|
|
304
|
+
*
|
|
305
|
+
* The observe/enforce split is NOT redefined here — it reads the blast-radius
|
|
306
|
+
* ladder in src/quality/action-eligibility.ts, which is the project's single
|
|
307
|
+
* ranking of what an action destroys. OBSERVE (alert / snapshot / shadow /
|
|
308
|
+
* escalate) changes nothing about the agent's execution and therefore always
|
|
309
|
+
* runs. Everything above it (INTERRUPT / DEGRADE / TERMINATE) denies an
|
|
310
|
+
* operation, alters session state, or destroys the session — that is
|
|
311
|
+
* enforcement and requires the operator directive.
|
|
312
|
+
*/
|
|
313
|
+
export function isEnforcementAction(action) {
|
|
314
|
+
return actionTier(action) > ACTION_TIERS.OBSERVE;
|
|
315
|
+
}
|
|
316
|
+
//# sourceMappingURL=enforcement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enforcement.js","sourceRoot":"","sources":["../src/enforcement.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.d.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @module agent-threat-rules/engine
|
|
13
13
|
*/
|
|
14
14
|
import type { ATRRule, ATRMatch, AgentEvent, ATRVerdict, ActionResult, ScanResult, ATRLanguage, ATRSemanticJudge } from './types.js';
|
|
15
|
+
import { type Lane } from './quality/rule-contract.js';
|
|
15
16
|
import type { SessionTracker } from './session-tracker.js';
|
|
16
17
|
import type { ActionExecutor } from './action-executor.js';
|
|
17
18
|
import type { SkillFingerprintStore } from './skill-fingerprint.js';
|
|
@@ -79,6 +80,15 @@ export interface ATREngineConfig {
|
|
|
79
80
|
* 'alert' : maturity stable + test (analyst/correlation lane).
|
|
80
81
|
* 'hunt' : all maturities (advisory/eval; default, backward-compatible).
|
|
81
82
|
* Deprecated/draft rules are always skipped regardless of lane.
|
|
83
|
+
*
|
|
84
|
+
* This field is the ONLY input: the engine does not read `ATR_LANE` or any
|
|
85
|
+
* other environment variable. Leaving it undefined pins the lane to 'hunt',
|
|
86
|
+
* the widest and most advisory setting, no matter what the surrounding shell
|
|
87
|
+
* says. A CLI that wants environment support resolves it itself and passes
|
|
88
|
+
* the result here (src/cli.ts, resolveEnforcementPolicy).
|
|
89
|
+
*
|
|
90
|
+
* An unrecognised value throws at construction time — that is a caller bug,
|
|
91
|
+
* not ambient noise.
|
|
82
92
|
*/
|
|
83
93
|
lane?: 'enforce' | 'alert' | 'hunt';
|
|
84
94
|
/**
|
|
@@ -95,6 +105,12 @@ export declare class ATREngine {
|
|
|
95
105
|
private rules;
|
|
96
106
|
private readonly compiledPatterns;
|
|
97
107
|
private readonly semanticModuleInstance;
|
|
108
|
+
/**
|
|
109
|
+
* Active detection lane, resolved ONCE at construction from config alone.
|
|
110
|
+
* Held as a field so every gate in this engine sees the same lane for its
|
|
111
|
+
* lifetime.
|
|
112
|
+
*/
|
|
113
|
+
private readonly lane;
|
|
98
114
|
/**
|
|
99
115
|
* Find bundled rules directory shipped with the npm package.
|
|
100
116
|
* Checks: ../rules (from dist/), ./rules (repo root)
|
|
@@ -111,6 +127,8 @@ export declare class ATREngine {
|
|
|
111
127
|
* Keeps experimental content out of the auto-block path without deleting it.
|
|
112
128
|
*/
|
|
113
129
|
private passesLane;
|
|
130
|
+
/** Active detection lane (config, else 'hunt'). Diagnostics/tests. */
|
|
131
|
+
getLane(): Lane;
|
|
114
132
|
/**
|
|
115
133
|
* Load a single rule file and add it to the engine.
|
|
116
134
|
*/
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,QAAQ,EACR,UAAU,EAGV,UAAU,EACV,YAAY,EACZ,UAAU,EAEV,WAAW,EACX,gBAAgB,EACjB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|