agent-threat-rules 3.5.8 → 3.5.9

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.
Files changed (35) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/nemoclaw-preflight.d.ts +102 -0
  3. package/dist/adapters/nemoclaw-preflight.d.ts.map +1 -0
  4. package/dist/adapters/nemoclaw-preflight.js +290 -0
  5. package/dist/adapters/nemoclaw-preflight.js.map +1 -0
  6. package/dist/adapters/openshell-filter.d.ts +95 -0
  7. package/dist/adapters/openshell-filter.d.ts.map +1 -0
  8. package/dist/adapters/openshell-filter.js +231 -0
  9. package/dist/adapters/openshell-filter.js.map +1 -0
  10. package/package.json +10 -2
  11. package/rules/agent-manipulation/ATR-2026-00030-cross-agent-attack.yaml +6 -10
  12. package/rules/agent-manipulation/ATR-2026-00418-weknora-mcp-config-rce.yaml +38 -8
  13. package/rules/context-exfiltration/ATR-2026-00021-api-key-exposure.yaml +0 -9
  14. package/rules/context-exfiltration/ATR-2026-02262-env-resolution-credential-exfil.yaml +172 -0
  15. package/rules/data-poisoning/ATR-2026-00070-data-poisoning.yaml +0 -9
  16. package/rules/excessive-autonomy/ATR-2026-00050-runaway-agent-loop.yaml +0 -5
  17. package/rules/excessive-autonomy/ATR-2026-00098-unauthorized-financial-action.yaml +0 -9
  18. package/rules/privilege-escalation/ATR-2026-00040-privilege-escalation.yaml +0 -5
  19. package/rules/privilege-escalation/ATR-2026-00204-stealth-execution-persistence.yaml +5 -5
  20. package/rules/privilege-escalation/ATR-2026-00441-semantic-kernel-sessions-python-plugin-startup-persistence.yaml +0 -5
  21. package/rules/prompt-injection/ATR-2026-00001-direct-prompt-injection.yaml +4 -4
  22. package/rules/prompt-injection/ATR-2026-00002-indirect-prompt-injection.yaml +2 -2
  23. package/rules/prompt-injection/ATR-2026-00203-context-pollution-skill-description.yaml +1 -1
  24. package/rules/prompt-injection/ATR-2026-00442-quoted-exact-output-forcing.yaml +10 -2
  25. package/rules/skill-compromise/ATR-2026-00120-skill-instruction-injection.yaml +33 -2
  26. package/rules/skill-compromise/ATR-2026-00121-skill-dangerous-script.yaml +30 -4
  27. package/rules/skill-compromise/ATR-2026-00135-exfil-url-in-instructions.yaml +30 -2
  28. package/rules/skill-compromise/ATR-2026-00149-skill-exfil-compound.yaml +12 -2
  29. package/rules/skill-compromise/ATR-2026-02261-code-credential-exfil-chain.yaml +198 -0
  30. package/rules/tool-poisoning/ATR-2026-00010-mcp-malicious-response.yaml +14 -14
  31. package/rules/tool-poisoning/ATR-2026-00259-ansi-escape-injection.yaml +16 -2
  32. package/rules/tool-poisoning/ATR-2026-00419-cursor-mcp-zero-click-config.yaml +30 -10
  33. package/rules/tool-poisoning/ATR-2026-00576-hades-agent-credential-theft.yaml +29 -6
  34. package/rules/tool-poisoning/ATR-2026-02260-agentjacking-remediation-framed-execution.yaml +175 -0
  35. package/rules/tool-poisoning/ATR-2026-02263-workflow-sql-template-expression-injection.yaml +173 -0
package/README.md CHANGED
@@ -13,7 +13,7 @@ AI Agent 威脅偵測規則的開放格式
13
13
  [![GitHub Marketplace](https://img.shields.io/badge/Marketplace-ATR%20Scan-2ea44f?style=flat-square&logo=github)](https://github.com/marketplace/actions/atr-scan)
14
14
  [![License: MIT](https://img.shields.io/badge/license-MIT-brightgreen?style=flat-square)](LICENSE)
15
15
  [![DOI](https://img.shields.io/badge/DOI-10.5281%2Fzenodo.19178002-blue?style=flat-square)](https://doi.org/10.5281/zenodo.19178002)
16
- [![Rules](https://img.shields.io/badge/rules-747-blue?style=flat-square)](#5-specification)
16
+ [![Rules](https://img.shields.io/badge/rules-751-blue?style=flat-square)](#5-specification)
17
17
  [![Categories](https://img.shields.io/badge/categories-10-blue?style=flat-square)](#7-coverage)
18
18
  [![OWASP Agentic](https://img.shields.io/badge/OWASP_Agentic_Top_10-10%2F10-brightgreen?style=flat-square)](#7-coverage)
19
19
  [![SAFE-MCP](https://img.shields.io/badge/SAFE--MCP-91.8%25-brightgreen?style=flat-square)](#7-coverage)
@@ -0,0 +1,102 @@
1
+ /**
2
+ * NemoClaw / OpenShell supply-chain pre-flight scanner (ATR Seam B).
3
+ *
4
+ * Scans an agent bundle — SKILL.md skills and MCP server configs — BEFORE it is
5
+ * provisioned into a NemoClaw/OpenShell sandbox, and returns a gate verdict:
6
+ * exit 0 = pass (all targets scanned, nothing at/above threshold)
7
+ * exit 1 = block (a rule at/above the threshold matched)
8
+ * exit 2 = incomplete (could not fully scan — oversized/unreadable/symlink/
9
+ * too many files) OR internal error
10
+ *
11
+ * It is FAIL-CLOSED: because this is the only pre-deployment barrier, an
12
+ * incomplete scan is never reported as a pass. A skipped file (too large to
13
+ * scan, unreadable, a symlink, or beyond the file cap) yields an "incomplete"
14
+ * verdict the caller must treat as a gate failure, not a clean result.
15
+ *
16
+ * This is the pre-deployment counterpart to the runtime content filter
17
+ * (openshell-filter): the filter inspects L7 traffic at run time; this inspects
18
+ * the skill / MCP supply chain at provision time — a gap neither OpenShell's
19
+ * capability policy nor a runtime guard covers.
20
+ *
21
+ * Why MCP configs specifically: OpenShell/NemoClaw run OpenClaw/Hermes agents
22
+ * that load skills and register MCP servers (.mcp.json, .cursor/mcp.json,
23
+ * .claude/settings.json, ...). Malicious server definitions (stdio RCE, unauth
24
+ * registration, SymJack symlink-config redirection) are a content-level attack
25
+ * surface ATR has rules for. The `atr scan` CLI treats .json as MCP *events*;
26
+ * this scans MCP *config* files as content, which is the missing path.
27
+ *
28
+ * Symlinks are never followed (cycle-safe, version-consistent across the
29
+ * supported Node range, and SymJack-safe); a symlink that would otherwise be a
30
+ * scan target is recorded as skipped so it cannot pass silently.
31
+ *
32
+ * Findings use the canonical atr.* shape (matched values redacted by default per
33
+ * the engine INTERFACE-CONTRACT), consistent with openshell-filter, so Seam A
34
+ * (runtime) and Seam B (supply chain) feed the same OCSF detection_finding
35
+ * audit stream. Reuses ATREngine in skill context; adds no dependency.
36
+ *
37
+ * @module agent-threat-rules/nemoclaw-preflight
38
+ */
39
+ export type Decision = 'pass' | 'block' | 'incomplete';
40
+ /** Reason a target could not be fully scanned (fail-closed signals). */
41
+ export type SkipReason = 'too_large' | 'unreadable' | 'symlink' | 'max_files_exceeded';
42
+ export interface PreflightOptions {
43
+ /** Block when a matched rule's severity is at or above this (default "high"). */
44
+ readonly minSeverity: string;
45
+ /** Directory of ATR rule YAML files. Omit to use the rules bundled with the package. */
46
+ readonly rulesDir?: string;
47
+ /** When true, include the raw matched value instead of redacting it. */
48
+ readonly forensic: boolean;
49
+ /** Skip files larger than this (default 1 MB) — skipped files block the gate. */
50
+ readonly maxFileBytes?: number;
51
+ /** Stop collecting after this many target files (default 2000) — truncation blocks the gate. */
52
+ readonly maxFiles?: number;
53
+ }
54
+ /** A finding, in the canonical atr.* shape plus the source file that triggered it. */
55
+ export interface PreflightFinding {
56
+ readonly 'atr.rule_id': string;
57
+ readonly 'atr.severity': string;
58
+ readonly 'atr.category': string;
59
+ readonly 'atr.confidence': number;
60
+ readonly 'atr.matched_value_redacted': string;
61
+ readonly 'atr.source_path': string;
62
+ }
63
+ export interface SkippedFile {
64
+ readonly path: string;
65
+ readonly reason: SkipReason;
66
+ }
67
+ export interface PreflightResult {
68
+ readonly decision: Decision;
69
+ /** Number of files actually read and evaluated (not merely discovered). */
70
+ readonly scanned: number;
71
+ /** Targets that could not be fully scanned. Non-empty ⇒ verdict cannot be a clean pass. */
72
+ readonly skipped: readonly SkippedFile[];
73
+ readonly findings: readonly PreflightFinding[];
74
+ }
75
+ /** The supply-chain pre-flight scanner. Construct once, reuse across bundles. */
76
+ export declare class NemoClawPreflight {
77
+ private readonly options;
78
+ private readonly engine;
79
+ private readonly blockRank;
80
+ private readonly maxFileBytes;
81
+ private readonly maxFiles;
82
+ private loadPromise;
83
+ constructor(options: PreflightOptions);
84
+ private ensureLoaded;
85
+ /**
86
+ * Scan one file's content in skill context. Returns the findings and a skip
87
+ * reason (null if the file was actually read and evaluated). Oversized and
88
+ * unreadable files are reported as skipped (fail-closed), never silently
89
+ * treated as clean.
90
+ */
91
+ private scanFile;
92
+ private toFinding;
93
+ /**
94
+ * Scan an agent bundle (directory or single file). Block on any finding at or
95
+ * above the threshold; otherwise report incomplete if any target could not be
96
+ * fully scanned (fail-closed); otherwise pass.
97
+ */
98
+ scanBundle(root: string): Promise<PreflightResult>;
99
+ }
100
+ /** Build options from the ATR_* environment contract. */
101
+ export declare function optionsFromEnv(): PreflightOptions;
102
+ //# sourceMappingURL=nemoclaw-preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nemoclaw-preflight.d.ts","sourceRoot":"","sources":["../../src/adapters/nemoclaw-preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAOH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,CAAC;AAEvD,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,GAAG,oBAAoB,CAAC;AAiCvF,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,gGAAgG;IAChG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,sFAAsF;AACtF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,4BAA4B,EAAE,MAAM,CAAC;IAC9C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,2FAA2F;IAC3F,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAChD;AAiFD,iFAAiF;AACjF,qBAAa,iBAAiB;IAOhB,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,WAAW,CAAgC;gBAEtB,OAAO,EAAE,gBAAgB;IAOtD,OAAO,CAAC,YAAY;IAKpB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IA8BhB,OAAO,CAAC,SAAS;IAYjB;;;;OAIG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAyBzD;AAID,yDAAyD;AACzD,wBAAgB,cAAc,IAAI,gBAAgB,CASjD"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * NemoClaw / OpenShell supply-chain pre-flight scanner (ATR Seam B).
3
+ *
4
+ * Scans an agent bundle — SKILL.md skills and MCP server configs — BEFORE it is
5
+ * provisioned into a NemoClaw/OpenShell sandbox, and returns a gate verdict:
6
+ * exit 0 = pass (all targets scanned, nothing at/above threshold)
7
+ * exit 1 = block (a rule at/above the threshold matched)
8
+ * exit 2 = incomplete (could not fully scan — oversized/unreadable/symlink/
9
+ * too many files) OR internal error
10
+ *
11
+ * It is FAIL-CLOSED: because this is the only pre-deployment barrier, an
12
+ * incomplete scan is never reported as a pass. A skipped file (too large to
13
+ * scan, unreadable, a symlink, or beyond the file cap) yields an "incomplete"
14
+ * verdict the caller must treat as a gate failure, not a clean result.
15
+ *
16
+ * This is the pre-deployment counterpart to the runtime content filter
17
+ * (openshell-filter): the filter inspects L7 traffic at run time; this inspects
18
+ * the skill / MCP supply chain at provision time — a gap neither OpenShell's
19
+ * capability policy nor a runtime guard covers.
20
+ *
21
+ * Why MCP configs specifically: OpenShell/NemoClaw run OpenClaw/Hermes agents
22
+ * that load skills and register MCP servers (.mcp.json, .cursor/mcp.json,
23
+ * .claude/settings.json, ...). Malicious server definitions (stdio RCE, unauth
24
+ * registration, SymJack symlink-config redirection) are a content-level attack
25
+ * surface ATR has rules for. The `atr scan` CLI treats .json as MCP *events*;
26
+ * this scans MCP *config* files as content, which is the missing path.
27
+ *
28
+ * Symlinks are never followed (cycle-safe, version-consistent across the
29
+ * supported Node range, and SymJack-safe); a symlink that would otherwise be a
30
+ * scan target is recorded as skipped so it cannot pass silently.
31
+ *
32
+ * Findings use the canonical atr.* shape (matched values redacted by default per
33
+ * the engine INTERFACE-CONTRACT), consistent with openshell-filter, so Seam A
34
+ * (runtime) and Seam B (supply chain) feed the same OCSF detection_finding
35
+ * audit stream. Reuses ATREngine in skill context; adds no dependency.
36
+ *
37
+ * @module agent-threat-rules/nemoclaw-preflight
38
+ */
39
+ import { existsSync, lstatSync, readFileSync, readdirSync, statSync } from 'node:fs';
40
+ import { basename, join, relative } from 'node:path';
41
+ import { ATREngine } from '../engine.js';
42
+ /** Severity ordering for the block threshold (ATR_MIN_SEVERITY). */
43
+ const SEVERITY_RANK = Object.freeze({
44
+ informational: 0,
45
+ low: 1,
46
+ medium: 2,
47
+ high: 3,
48
+ critical: 4,
49
+ });
50
+ const DEFAULT_MIN_SEVERITY = 'high';
51
+ const DEFAULT_MAX_FILE_BYTES = 1024 * 1024; // 1 MB per file
52
+ const DEFAULT_MAX_FILES = 2000;
53
+ const REDACTED = '[REDACTED]';
54
+ const SKIP_DIRS = new Set([
55
+ '.git', 'node_modules', '.venv', 'venv', 'dist', 'build', '__pycache__', '.next',
56
+ ]);
57
+ /** MCP config file basenames (exact) and path suffixes (agent-specific locations). */
58
+ const MCP_CONFIG_BASENAMES = new Set(['.mcp.json', 'mcp.json']);
59
+ const MCP_CONFIG_SUFFIXES = [
60
+ '.cursor/mcp.json',
61
+ '.vscode/mcp.json',
62
+ '.claude/settings.json',
63
+ '.gemini/settings.json',
64
+ '.codex/config.toml',
65
+ ];
66
+ function severityRank(severity) {
67
+ return SEVERITY_RANK[severity.toLowerCase()] ?? 0;
68
+ }
69
+ function isSkillFile(base) {
70
+ return base.toLowerCase() === 'skill.md';
71
+ }
72
+ function isMcpConfig(relPath, base) {
73
+ if (MCP_CONFIG_BASENAMES.has(base))
74
+ return true;
75
+ const norm = relPath.split('\\').join('/');
76
+ return MCP_CONFIG_SUFFIXES.some((suffix) => norm.endsWith(suffix));
77
+ }
78
+ /**
79
+ * Recursively collect skill + MCP-config files under root (bounded, skipping
80
+ * vendor dirs and never following symlinks). A symlink that would otherwise be a
81
+ * target is reported as skipped; hitting maxFiles sets truncated.
82
+ */
83
+ function collectTargets(root, maxFiles) {
84
+ // lstat (not stat) so a symlinked root is NOT silently followed — the
85
+ // per-entry guard during traversal only covers symlinks found inside dirs,
86
+ // not the root argument itself. Honors the module's "never follow symlinks".
87
+ const rootStat = lstatSync(root);
88
+ if (rootStat.isSymbolicLink()) {
89
+ return { targets: [], skipped: [{ path: root, reason: 'symlink' }], truncated: false };
90
+ }
91
+ if (rootStat.isFile()) {
92
+ const base = basename(root);
93
+ const kind = isMcpConfig(root, base) ? 'mcp-config' : 'skill';
94
+ return { targets: [{ path: root, kind }], skipped: [], truncated: false };
95
+ }
96
+ const targets = [];
97
+ const skipped = [];
98
+ const stack = [root];
99
+ let truncated = false;
100
+ while (stack.length > 0) {
101
+ if (targets.length >= maxFiles) {
102
+ truncated = true;
103
+ break;
104
+ }
105
+ const dir = stack.pop();
106
+ let entries;
107
+ try {
108
+ entries = readdirSync(dir, { withFileTypes: true });
109
+ }
110
+ catch {
111
+ skipped.push({ path: dir, reason: 'unreadable' });
112
+ continue;
113
+ }
114
+ for (const entry of entries) {
115
+ if (targets.length >= maxFiles) {
116
+ truncated = true;
117
+ break;
118
+ }
119
+ const full = join(dir, entry.name);
120
+ const rel = relative(root, full);
121
+ const wouldBeTarget = isSkillFile(entry.name) || isMcpConfig(rel, entry.name);
122
+ // Never follow symlinks: avoids traversal cycles, is consistent across the
123
+ // supported Node range (Dirent.isDirectory() follows links on some), and
124
+ // refuses SymJack-style redirection. Surface link-targets as skipped.
125
+ if (entry.isSymbolicLink()) {
126
+ if (wouldBeTarget)
127
+ skipped.push({ path: full, reason: 'symlink' });
128
+ continue;
129
+ }
130
+ if (entry.isDirectory()) {
131
+ if (!SKIP_DIRS.has(entry.name))
132
+ stack.push(full);
133
+ continue;
134
+ }
135
+ if (isSkillFile(entry.name))
136
+ targets.push({ path: full, kind: 'skill' });
137
+ else if (isMcpConfig(rel, entry.name))
138
+ targets.push({ path: full, kind: 'mcp-config' });
139
+ }
140
+ }
141
+ return { targets, skipped, truncated };
142
+ }
143
+ /** The supply-chain pre-flight scanner. Construct once, reuse across bundles. */
144
+ export class NemoClawPreflight {
145
+ options;
146
+ engine;
147
+ blockRank;
148
+ maxFileBytes;
149
+ maxFiles;
150
+ loadPromise = null;
151
+ constructor(options) {
152
+ this.options = options;
153
+ this.engine = new ATREngine(options.rulesDir ? { rulesDir: options.rulesDir } : {});
154
+ this.blockRank = severityRank(options.minSeverity);
155
+ this.maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
156
+ this.maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
157
+ }
158
+ ensureLoaded() {
159
+ if (this.loadPromise === null)
160
+ this.loadPromise = this.engine.loadRules();
161
+ return this.loadPromise;
162
+ }
163
+ /**
164
+ * Scan one file's content in skill context. Returns the findings and a skip
165
+ * reason (null if the file was actually read and evaluated). Oversized and
166
+ * unreadable files are reported as skipped (fail-closed), never silently
167
+ * treated as clean.
168
+ */
169
+ scanFile(target) {
170
+ let content;
171
+ try {
172
+ if (statSync(target.path).size > this.maxFileBytes) {
173
+ return { findings: [], skip: 'too_large' };
174
+ }
175
+ content = readFileSync(target.path, 'utf8');
176
+ }
177
+ catch {
178
+ return { findings: [], skip: 'unreadable' };
179
+ }
180
+ if (!content.trim())
181
+ return { findings: [], skip: null };
182
+ // scanContext 'skill' makes every rule evaluate against the whole content,
183
+ // with the engine's compound gating controlling false positives — correct
184
+ // for both SKILL.md and MCP config text (exec commands, server
185
+ // registrations, symlink targets are inspected as content).
186
+ const event = {
187
+ type: 'tool_call',
188
+ timestamp: new Date().toISOString(),
189
+ content,
190
+ scanContext: 'skill',
191
+ };
192
+ const findings = this.engine
193
+ .evaluate(event)
194
+ .filter((m) => severityRank(m.rule.severity) >= this.blockRank)
195
+ .map((m) => this.toFinding(m, target.path));
196
+ return { findings, skip: null };
197
+ }
198
+ toFinding(match, sourcePath) {
199
+ const matchedPattern = match.matchedPatterns?.[0];
200
+ return {
201
+ 'atr.rule_id': match.rule.id,
202
+ 'atr.severity': match.rule.severity,
203
+ 'atr.category': match.rule.tags?.category ?? 'unknown',
204
+ 'atr.confidence': match.confidence,
205
+ 'atr.matched_value_redacted': this.options.forensic && matchedPattern ? matchedPattern : REDACTED,
206
+ 'atr.source_path': sourcePath,
207
+ };
208
+ }
209
+ /**
210
+ * Scan an agent bundle (directory or single file). Block on any finding at or
211
+ * above the threshold; otherwise report incomplete if any target could not be
212
+ * fully scanned (fail-closed); otherwise pass.
213
+ */
214
+ async scanBundle(root) {
215
+ await this.ensureLoaded();
216
+ if (!existsSync(root)) {
217
+ throw new Error(`path not found: ${root}`);
218
+ }
219
+ const collected = collectTargets(root, this.maxFiles);
220
+ const skipped = [...collected.skipped];
221
+ const findings = [];
222
+ let evaluated = 0;
223
+ for (const target of collected.targets) {
224
+ const { findings: f, skip } = this.scanFile(target);
225
+ if (skip)
226
+ skipped.push({ path: target.path, reason: skip });
227
+ else
228
+ evaluated++;
229
+ findings.push(...f);
230
+ }
231
+ if (collected.truncated) {
232
+ skipped.push({ path: root, reason: 'max_files_exceeded' });
233
+ }
234
+ const decision = findings.length > 0 ? 'block' : skipped.length > 0 ? 'incomplete' : 'pass';
235
+ return { decision, scanned: evaluated, skipped, findings };
236
+ }
237
+ }
238
+ // ── CLI wiring (provisioning gate the supervisor / CI runs) ──────────────────
239
+ /** Build options from the ATR_* environment contract. */
240
+ export function optionsFromEnv() {
241
+ const minRaw = (process.env['ATR_MIN_SEVERITY'] ?? DEFAULT_MIN_SEVERITY).toLowerCase();
242
+ const minSeverity = SEVERITY_RANK[minRaw] !== undefined ? minRaw : DEFAULT_MIN_SEVERITY;
243
+ const forensicRaw = process.env['ATR_FORENSIC_MODE'];
244
+ return {
245
+ minSeverity,
246
+ rulesDir: process.env['ATR_RULES_DIR'] ?? process.env['ATR_CORPUS_PATH'] ?? undefined,
247
+ forensic: forensicRaw === '1' || forensicRaw === 'true',
248
+ };
249
+ }
250
+ function exitCodeFor(decision) {
251
+ return decision === 'block' ? 1 : decision === 'incomplete' ? 2 : 0;
252
+ }
253
+ async function main(argv) {
254
+ const target = argv.find((a) => !a.startsWith('-'));
255
+ if (!target) {
256
+ process.stderr.write('usage: nemoclaw-preflight <agent-bundle-dir> [--json]\n');
257
+ return 2;
258
+ }
259
+ const result = await new NemoClawPreflight(optionsFromEnv()).scanBundle(target);
260
+ if (argv.includes('--json')) {
261
+ process.stdout.write(JSON.stringify(result) + '\n');
262
+ return exitCodeFor(result.decision);
263
+ }
264
+ if (result.decision === 'block') {
265
+ process.stderr.write(`BLOCK: ${result.findings.length} finding(s) across ${result.scanned} file(s)\n`);
266
+ for (const f of result.findings) {
267
+ process.stderr.write(` ${f['atr.severity']} ${f['atr.rule_id']} ${f['atr.category']} ${f['atr.source_path']}\n`);
268
+ }
269
+ }
270
+ else if (result.decision === 'incomplete') {
271
+ process.stderr.write(`INCOMPLETE: scanned ${result.scanned} file(s); ${result.skipped.length} could not be scanned (treated as gate failure)\n`);
272
+ for (const s of result.skipped) {
273
+ process.stderr.write(` ${s.reason} ${s.path}\n`);
274
+ }
275
+ }
276
+ else {
277
+ process.stderr.write(`PASS: ${result.scanned} file(s) scanned, no findings at or above threshold\n`);
278
+ }
279
+ return exitCodeFor(result.decision);
280
+ }
281
+ const isDirectInvocation = Boolean(process.argv[1]) && import.meta.url === `file://${process.argv[1]}`;
282
+ if (isDirectInvocation) {
283
+ main(process.argv.slice(2))
284
+ .then((code) => process.exit(code))
285
+ .catch((err) => {
286
+ process.stderr.write(`nemoclaw-preflight: ${err instanceof Error ? err.message : String(err)}\n`);
287
+ process.exit(2);
288
+ });
289
+ }
290
+ //# sourceMappingURL=nemoclaw-preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nemoclaw-preflight.js","sourceRoot":"","sources":["../../src/adapters/nemoclaw-preflight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * OpenShell content-inspection filter — ATR reference binding for NVIDIA OpenShell.
3
+ *
4
+ * Implements the external content-filter contract proposed in
5
+ * NVIDIA/OpenShell#1272 ("L7 content inspection hooks — scriptable
6
+ * request/response filtering"): the OpenShell *supervisor* (outside the agent
7
+ * sandbox) runs this as a script against L7 request/response bodies. The body
8
+ * arrives on stdin; the script signals the verdict via exit code:
9
+ *
10
+ * exit 0 -> allow (no rule at/above the deny threshold matched)
11
+ * exit 1 -> deny (the verdict JSON, with reasons, is written to stdout)
12
+ * exit 2 -> internal error (supervisor applies its on_timeout/on_error policy)
13
+ *
14
+ * Two run modes:
15
+ * one-shot (default): read the whole body from stdin, evaluate, exit.
16
+ * Matches the #1272 contract literally. Cold-starts the engine per call.
17
+ * serve (--serve): read NDJSON requests line-by-line from stdin and write
18
+ * one NDJSON verdict per line. Loads the rule set ONCE, so per-request
19
+ * latency is just regex evaluation (sub-ms). Production path for a
20
+ * persistent guard service (cf. NVIDIA/OpenShell#1733 RFC 0005). A bad
21
+ * line (or an engine error) yields a per-line error verdict and the
22
+ * stream continues — one malformed body cannot take the inspector offline.
23
+ *
24
+ * Output is the canonical ATR detection event (atr.* fields per the engine
25
+ * INTERFACE-CONTRACT). Matched raw values are redacted by default
26
+ * (ATR_FORENSIC_MODE=1 to retain), satisfying the "audit evidence without
27
+ * storing raw sensitive values" requirement of the egress-middleware RFC (#1733).
28
+ *
29
+ * Environment (aligned with engines/{go,typescript}/INTERFACE-CONTRACT.md):
30
+ * ATR_RULES_DIR | ATR_CORPUS_PATH rule directory (default: bundled rules)
31
+ * ATR_MIN_SEVERITY deny at or above this severity (default high)
32
+ * ATR_FORENSIC_MODE "1"/"true" to retain raw matched values
33
+ * ATR_MAX_BODY_BYTES stdin hard cap (default 524288)
34
+ *
35
+ * Adds NO new dependency: reuses the canonical ATREngine. It does not decide
36
+ * routing (that is OpenShell's Privacy Router, #1043) — it only inspects content
37
+ * and returns allow/deny, per the #1272 separation of concerns.
38
+ *
39
+ * @module agent-threat-rules/openshell-filter
40
+ */
41
+ /** L7 traffic direction, mirroring OpenShell #1272 `content_filters[].direction`. */
42
+ export type Direction = 'request' | 'response';
43
+ /** Verdict decision returned to the supervisor. */
44
+ export type Decision = 'allow' | 'deny';
45
+ export interface FilterOptions {
46
+ /** Inbound response (agent will consume) vs outbound request (agent is sending). */
47
+ readonly direction: Direction;
48
+ /** Deny when a matched rule's severity is at or above this (default "high"). */
49
+ readonly minSeverity: string;
50
+ /** Directory of ATR rule YAML files. Omit to use the rules bundled with the package. */
51
+ readonly rulesDir?: string;
52
+ /** When true, include the raw matched value instead of redacting it. */
53
+ readonly forensic: boolean;
54
+ }
55
+ /**
56
+ * A single finding, shaped after the canonical ATR detection event
57
+ * (engines/go/INTERFACE-CONTRACT.md). Only the subset an inline content filter
58
+ * can populate is emitted; the supervisor enriches agent/session/service fields.
59
+ */
60
+ export interface Finding {
61
+ readonly 'atr.rule_id': string;
62
+ readonly 'atr.severity': string;
63
+ readonly 'atr.category': string;
64
+ readonly 'atr.confidence': number;
65
+ readonly 'atr.matched_field': string;
66
+ readonly 'atr.matched_value_redacted': string;
67
+ }
68
+ export interface FilterVerdict {
69
+ readonly decision: Decision;
70
+ readonly findings: readonly Finding[];
71
+ }
72
+ /**
73
+ * The ATR content inspector. Construct once, reuse across many bodies (the
74
+ * `serve` path does exactly this so the rule set loads a single time).
75
+ */
76
+ export declare class OpenShellFilter {
77
+ private readonly options;
78
+ private readonly engine;
79
+ private readonly denyRank;
80
+ private loadPromise;
81
+ constructor(options: FilterOptions);
82
+ /** Load rules exactly once, even under concurrent first calls. */
83
+ private ensureLoaded;
84
+ /**
85
+ * Inspect one body. Returns a deny verdict when any rule at or above the
86
+ * configured deny threshold matches; otherwise allow. Never throws on
87
+ * benign/empty input.
88
+ */
89
+ inspect(body: string): Promise<FilterVerdict>;
90
+ }
91
+ /** Parse the required --direction flag. Throws (fail-closed) on missing/invalid. */
92
+ export declare function parseDirection(arg: string | undefined): Direction;
93
+ /** Build FilterOptions from argv + the ATR_* environment contract. */
94
+ export declare function optionsFromEnv(argv: readonly string[]): FilterOptions;
95
+ //# sourceMappingURL=openshell-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openshell-filter.d.ts","sourceRoot":"","sources":["../../src/adapters/openshell-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAMH,qFAAqF;AACrF,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAE/C,mDAAmD;AACnD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAmBxC,MAAM,WAAW,aAAa;IAC5B,oFAAoF;IACpF,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,4BAA4B,EAAE,MAAM,CAAC;CAC/C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;CACvC;AAmCD;;;GAGG;AACH,qBAAa,eAAe;IAKd,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,WAAW,CAAgC;gBAEtB,OAAO,EAAE,aAAa;IAKnD,kEAAkE;IAClE,OAAO,CAAC,YAAY;IAKpB;;;;OAIG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;CAmBpD;AAID,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAGjE;AAOD,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CAUrE"}