fullcourtdefense-cli 1.21.18 → 1.21.20

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.
@@ -7,6 +7,8 @@ export interface DaemonArgs extends ProtectAllArgs {
7
7
  /** Suppress OS toasts (still logs). */
8
8
  quiet?: string;
9
9
  }
10
+ /** Arguments shared by first-boot, stale catch-up, and web-triggered scans. */
11
+ export declare function daemonDiscoverSweepArgs(): string[];
10
12
  /** Whether the daemon has been registered to start automatically. */
11
13
  export declare function isDaemonAutostartInstalled(): boolean;
12
14
  export declare function daemonCommand(args: DaemonArgs, config: BotGuardConfig): Promise<void>;
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.daemonDiscoverSweepArgs = daemonDiscoverSweepArgs;
36
37
  exports.isDaemonAutostartInstalled = isDaemonAutostartInstalled;
37
38
  exports.daemonCommand = daemonCommand;
38
39
  const fs = __importStar(require("fs"));
@@ -142,12 +143,19 @@ function stopPid(pid) {
142
143
  function logFile() {
143
144
  return path.join(daemonDir(), 'daemon.log');
144
145
  }
145
- /** Spawn `discover --upload --surface all --silent` and wait for it to finish. */
146
+ /** Arguments shared by first-boot, stale catch-up, and web-triggered scans. */
147
+ function daemonDiscoverSweepArgs() {
148
+ return ['discover', '--upload', '--surface', 'all', '--deep', 'true', '--silent', '--scan-root', 'auto'];
149
+ }
150
+ /** Spawn a full known-client discovery + posture sweep and wait for it to finish. */
146
151
  function runDiscoverSweep(timeoutMs = 300_000) {
147
152
  return new Promise((resolve, reject) => {
148
153
  // --scan-root auto sweeps the default dev roots; admin-selected folders from
149
154
  // the console are merged in by discover itself (cached runtime bundle).
150
- const child = (0, child_process_1.spawn)(process.execPath, [cliEntry(), 'discover', '--upload', '--surface', 'all', '--silent', '--scan-root', 'auto'], {
155
+ // --deep inventories tools from known configured clients (Claude Desktop,
156
+ // Cursor, etc.). We intentionally do NOT pass --deep-roots, so arbitrary
157
+ // stdio servers merely found in other project trees are never auto-spawned.
158
+ const child = (0, child_process_1.spawn)(process.execPath, [cliEntry(), ...daemonDiscoverSweepArgs()], {
151
159
  windowsHide: true,
152
160
  stdio: 'ignore',
153
161
  // Daemon/scheduled-task cwd is C:\WINDOWS\system32 — scan from the user's
@@ -840,13 +840,13 @@ async function discoverCommand(args, config) {
840
840
  const { extraScanRoots: adminScanRoots, disabledScanRoots } = (0, runtimeConfig_1.getCachedScanRootOverrides)();
841
841
  const normalizedDisabledRoots = (0, discoverPaths_1.uniqueNormalizedPaths)(disabledScanRoots);
842
842
  const isDisabledRoot = (root) => (0, discoverPaths_1.isExcludedPath)(root, normalizedDisabledRoots);
843
- const scanRoots = (0, discoverPaths_1.uniqueNormalizedPaths)([...(0, discoverPaths_1.resolveScanRoots)(args.scanRoot), ...adminScanRoots])
843
+ const scanRoots = (0, discoverPaths_1.compactScanRoots)([...(0, discoverPaths_1.resolveScanRoots)(args.scanRoot), ...adminScanRoots])
844
844
  .filter(root => !isDisabledRoot(root));
845
845
  const sweep = scanRoots.length > 0
846
846
  ? (0, discoverPaths_1.scanRootsForProjectConfigs)(scanRoots, { excludedRoots: normalizedDisabledRoots })
847
847
  : { candidates: [], scannedDirs: 0, excludedDirs: 0, truncated: false, maxDepth: 4, maxDirs: 25000 };
848
848
  // Admin roots are scanned for env/secrets even when no MCP config lives there.
849
- const sweepProjectRoots = (0, discoverPaths_1.uniqueNormalizedPaths)([...deriveProjectRoots(sweep.candidates), ...adminScanRoots])
849
+ const sweepProjectRoots = (0, discoverPaths_1.compactScanRoots)([...deriveProjectRoots(sweep.candidates), ...adminScanRoots])
850
850
  .filter(root => !isDisabledRoot(root));
851
851
  const knownConfigCandidates = candidateConfigPaths(cwd, args.extraPath)
852
852
  .filter(candidate => !isDisabledRoot(candidate.path));
@@ -914,17 +914,19 @@ async function discoverCommand(args, config) {
914
914
  const home = os.homedir();
915
915
  // Effective roots the posture scan covered — proof of coverage for the console.
916
916
  // Admin-removed defaults are excluded here too, so the console mirrors reality.
917
- const defaultEnvRoots = [home, cwd, path.join(home, 'dev'), path.join(home, 'repos'), path.join(home, 'projects'), path.join(home, 'Documents'), path.join(home, 'code')]
918
- .map(root => path.resolve(root));
917
+ const defaultEnvRoots = (0, discoverPaths_1.compactScanRoots)([home, cwd, path.join(home, 'dev'), path.join(home, 'repos'), path.join(home, 'projects'), path.join(home, 'Documents'), path.join(home, 'code')]
918
+ .map(root => path.resolve(root)));
919
919
  const adminRootSet = new Set(adminScanRoots.map(root => path.resolve(root).toLowerCase()));
920
- const scannedRoots = [
920
+ const scannedRootCandidates = [
921
921
  ...[...new Set(defaultEnvRoots)].filter(root => fs.existsSync(root) && !isDisabledRoot(root)).map(root => ({ path: root, source: 'default' })),
922
922
  ...adminScanRoots.map(root => path.resolve(root)).filter(root => fs.existsSync(root)).map(root => ({ path: root, source: 'admin' })),
923
923
  ...(0, discoverPaths_1.resolveScanRoots)(args.scanRoot)
924
924
  .map(root => path.resolve(root))
925
925
  .filter(root => fs.existsSync(root) && !adminRootSet.has(root.toLowerCase()) && !isDisabledRoot(root))
926
926
  .map(root => ({ path: root, source: 'default' })),
927
- ].filter((entry, index, list) => list.findIndex(other => other.path.toLowerCase() === entry.path.toLowerCase()) === index);
927
+ ];
928
+ const compactedScannedPaths = (0, discoverPaths_1.compactScanRoots)(scannedRootCandidates.map(entry => entry.path));
929
+ const scannedRoots = compactedScannedPaths.map(root => scannedRootCandidates.find(entry => path.resolve(entry.path).toLowerCase() === path.resolve(root).toLowerCase()));
928
930
  // Shallow folder tree for the console folder picker (names only, no contents).
929
931
  const folderCatalog = surfaces.has('posture') || surfaces.has('secrets')
930
932
  ? (0, discoverPaths_1.buildFolderCatalog)({ extraRoots: adminScanRoots })
@@ -14,6 +14,11 @@ export declare function normalizedPath(value: string): string;
14
14
  export declare function pathIsWithin(candidate: string, root: string): boolean;
15
15
  export declare function isExcludedPath(candidate: string, excludedRoots?: string[]): boolean;
16
16
  export declare function uniqueNormalizedPaths(values: string[]): string[];
17
+ /**
18
+ * Keep only the shallowest non-overlapping scan roots. If C:\dev is present,
19
+ * C:\dev\repo is redundant: the bounded parent walk already covers it.
20
+ */
21
+ export declare function compactScanRoots(values: string[]): string[];
17
22
  export declare function claudeDesktopMsixCandidates(): ClaudeDesktopConfigCandidate[];
18
23
  export declare function claudeDesktopConfigCandidates(): ClaudeDesktopConfigCandidate[];
19
24
  export declare function claudeDesktopLikelyInstalled(): boolean;
@@ -37,6 +37,7 @@ exports.normalizedPath = normalizedPath;
37
37
  exports.pathIsWithin = pathIsWithin;
38
38
  exports.isExcludedPath = isExcludedPath;
39
39
  exports.uniqueNormalizedPaths = uniqueNormalizedPaths;
40
+ exports.compactScanRoots = compactScanRoots;
40
41
  exports.claudeDesktopMsixCandidates = claudeDesktopMsixCandidates;
41
42
  exports.claudeDesktopConfigCandidates = claudeDesktopConfigCandidates;
42
43
  exports.claudeDesktopLikelyInstalled = claudeDesktopLikelyInstalled;
@@ -75,6 +76,22 @@ function uniqueNormalizedPaths(values) {
75
76
  }
76
77
  return [...out.values()];
77
78
  }
79
+ /**
80
+ * Keep only the shallowest non-overlapping scan roots. If C:\dev is present,
81
+ * C:\dev\repo is redundant: the bounded parent walk already covers it.
82
+ */
83
+ function compactScanRoots(values) {
84
+ const roots = uniqueNormalizedPaths(values)
85
+ .sort((a, b) => normalizedPath(a).split(path.sep).length - normalizedPath(b).split(path.sep).length
86
+ || normalizedPath(a).length - normalizedPath(b).length);
87
+ const compacted = [];
88
+ for (const root of roots) {
89
+ if (compacted.some(parent => pathIsWithin(root, parent)))
90
+ continue;
91
+ compacted.push(root);
92
+ }
93
+ return compacted;
94
+ }
78
95
  function claudeDesktopDocumentedPath() {
79
96
  const home = os.homedir();
80
97
  if (process.platform === 'win32') {
@@ -269,7 +286,7 @@ function scanRootsForProjectConfigs(roots, options = {}) {
269
286
  let excludedDirs = 0;
270
287
  let depthLimited = false;
271
288
  const queue = [];
272
- for (const root of uniqueNormalizedPaths(roots)) {
289
+ for (const root of compactScanRoots(roots)) {
273
290
  try {
274
291
  const resolved = path.resolve(root);
275
292
  if (!isExcludedPath(resolved, options.excludedRoots) && fs.statSync(resolved).isDirectory())
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.21.18"
2
+ "version": "1.21.20"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.21.18",
3
+ "version": "1.21.20",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -32,9 +32,11 @@
32
32
  "test:posture-blast": "npm run build && node scripts/test-posture-blast-radius.js",
33
33
  "test:ping-e2e": "npm run build && node scripts/test-ping-e2e.js",
34
34
  "test:daemon-e2e": "npm run build && node scripts/test-daemon-e2e.js",
35
+ "test:daemon-discovery-args": "npm run build && node scripts/test-daemon-discovery-args.js",
35
36
  "test:machine-action-signing": "npm run build && node scripts/test-machine-action-signing.js",
36
37
  "test:integrity": "npm run build && node scripts/test-integrity.js",
37
38
  "test:posture-overhaul": "npm run build && node scripts/test-posture-overhaul.js",
39
+ "test:scan-root-compaction": "npm run build && node scripts/test-scan-root-compaction.js",
38
40
  "test:dpapi-config": "npm run build && node scripts/test-dpapi-config.js",
39
41
  "test:desktop-chat-guard": "npm run build && node scripts/test-desktop-chat-guard.js",
40
42
  "test:clipboard-scan": "npm run build && node scripts/test-clipboard-scan.js",