fullcourtdefense-cli 1.36.0 → 1.36.2
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/dist/actionPolicyEngine.d.ts +23 -0
- package/dist/actionPolicyEngine.js +58 -19
- package/dist/approvalPrompt.d.ts +15 -0
- package/dist/approvalPrompt.js +157 -20
- package/dist/askDialog.d.ts +2 -0
- package/dist/askDialog.js +15 -1
- package/dist/askHeader.d.ts +81 -0
- package/dist/askHeader.js +573 -0
- package/dist/blockExplanation.d.ts +9 -0
- package/dist/blockExplanation.js +14 -0
- package/dist/callerEnv.d.ts +1 -1
- package/dist/callerEnv.js +14 -0
- package/dist/cloudGrant.d.ts +46 -0
- package/dist/cloudGrant.js +420 -0
- package/dist/cloudGrantTools.d.ts +97 -0
- package/dist/cloudGrantTools.js +1112 -0
- package/dist/commandNarrative.d.ts +8 -0
- package/dist/commandNarrative.js +222 -49
- package/dist/commands/cmdGuard.js +1 -1
- package/dist/commands/daemon.js +14 -0
- package/dist/commands/deterministicGuard.d.ts +8 -2
- package/dist/commands/deterministicGuard.js +219 -55
- package/dist/commands/discover.js +37 -14
- package/dist/commands/discoverAgentFiles.d.ts +2 -0
- package/dist/commands/discoverAgentFiles.js +1 -0
- package/dist/commands/discoverPaths.d.ts +4 -0
- package/dist/commands/discoverPaths.js +19 -1
- package/dist/commands/discoverProjectRoots.d.ts +72 -0
- package/dist/commands/discoverProjectRoots.js +327 -0
- package/dist/commands/discoverProxy.d.ts +1 -1
- package/dist/commands/discoverProxy.js +1 -1
- package/dist/commands/discoverSecrets.d.ts +44 -0
- package/dist/commands/discoverSecrets.js +158 -14
- package/dist/commands/hook.js +10 -7
- package/dist/commands/mcpGateway.js +9 -7
- package/dist/commands/posixShellGuard.js +1 -1
- package/dist/commands/shellGuard.js +70 -25
- package/dist/commands/uninstallAll.d.ts +1 -7
- package/dist/commands/uninstallAll.js +6 -45
- package/dist/config.d.ts +1 -1
- package/dist/config.js +3 -3
- package/dist/credentialGrant.d.ts +48 -0
- package/dist/credentialGrant.js +29 -18
- package/dist/credentialStore.js +1 -1
- package/dist/devConfirm.d.ts +44 -3
- package/dist/devConfirm.js +166 -30
- package/dist/distress.d.ts +2 -0
- package/dist/distress.js +2 -0
- package/dist/grantTargets.d.ts +21 -0
- package/dist/grantTargets.js +235 -0
- package/dist/localSafetySnapshot.js +2 -0
- package/dist/msiProductCode.d.ts +10 -0
- package/dist/msiProductCode.js +124 -0
- package/dist/notify.js +1 -0
- package/dist/selfTest.d.ts +1 -1
- package/dist/selfTest.js +1 -1
- package/dist/selfUpdate.d.ts +51 -2
- package/dist/selfUpdate.js +217 -19
- package/dist/shellAst.d.ts +2 -0
- package/dist/shellAst.js +7 -1
- package/dist/shellAstLoader.js +12 -1
- package/dist/version.json +1 -1
- package/package.json +6 -1
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.normalizedPath = normalizedPath;
|
|
37
37
|
exports.pathIsWithin = pathIsWithin;
|
|
38
38
|
exports.isExcludedPath = isExcludedPath;
|
|
39
|
+
exports.truncationReason = truncationReason;
|
|
39
40
|
exports.uniqueNormalizedPaths = uniqueNormalizedPaths;
|
|
40
41
|
exports.compactScanRoots = compactScanRoots;
|
|
41
42
|
exports.claudeDesktopMsixCandidates = claudeDesktopMsixCandidates;
|
|
@@ -67,6 +68,15 @@ function pathIsWithin(candidate, root) {
|
|
|
67
68
|
function isExcludedPath(candidate, excludedRoots = []) {
|
|
68
69
|
return excludedRoots.some(root => root.trim() && pathIsWithin(candidate, root));
|
|
69
70
|
}
|
|
71
|
+
function truncationReason(depthLimited, countLimited) {
|
|
72
|
+
if (depthLimited && countLimited)
|
|
73
|
+
return 'depth_and_count';
|
|
74
|
+
if (depthLimited)
|
|
75
|
+
return 'depth';
|
|
76
|
+
if (countLimited)
|
|
77
|
+
return 'count';
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
70
80
|
function uniqueNormalizedPaths(values) {
|
|
71
81
|
const out = new Map();
|
|
72
82
|
for (const value of values) {
|
|
@@ -353,7 +363,15 @@ function scanRootsForProjectConfigs(roots, options = {}) {
|
|
|
353
363
|
queue.push({ dir: path.join(dir, name), depth: depth + 1 });
|
|
354
364
|
}
|
|
355
365
|
}
|
|
356
|
-
return {
|
|
366
|
+
return {
|
|
367
|
+
candidates,
|
|
368
|
+
scannedDirs,
|
|
369
|
+
excludedDirs,
|
|
370
|
+
truncated: depthLimited || queue.length > 0,
|
|
371
|
+
truncatedReason: truncationReason(depthLimited, queue.length > 0),
|
|
372
|
+
maxDepth,
|
|
373
|
+
maxDirs,
|
|
374
|
+
};
|
|
357
375
|
}
|
|
358
376
|
/** Non-dev home folders that only add noise to the console folder picker. */
|
|
359
377
|
const CATALOG_SKIP_HOME_DIRS = new Set([
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project folders the installed AI clients say the developer works in. A
|
|
3
|
+
* developer's real work often sits far below the home folder (OneDrive
|
|
4
|
+
* Desktop, nested client folders) with no `.cursor` / `.claude` directory in
|
|
5
|
+
* it, so the bounded home walk never reaches it. The clients' own project
|
|
6
|
+
* lists are the ground truth:
|
|
7
|
+
*
|
|
8
|
+
* - Claude Code: `~/.claude.json` → `projects` keys, and the `cwd` recorded
|
|
9
|
+
* in `~/.claude/projects/<encoded-path>/*.jsonl` (the folder name encoding
|
|
10
|
+
* is lossy, so the recorded cwd is read instead of decoding the name);
|
|
11
|
+
* - Cursor / VS Code: `User/workspaceStorage/<id>/workspace.json` `folder` URIs;
|
|
12
|
+
* - Codex: `[projects."<path>"]` tables with `trust_level = "trusted"`.
|
|
13
|
+
*
|
|
14
|
+
* Only paths leave this module — never file contents. Every read is size-
|
|
15
|
+
* and count-capped; network shares, the home folder itself (and its
|
|
16
|
+
* ancestors), drive roots and operating-system folders are never returned.
|
|
17
|
+
*/
|
|
18
|
+
export type AiClientRootSource = 'claude_code_project' | 'cursor_workspace' | 'vscode_workspace' | 'codex_trusted_project';
|
|
19
|
+
export interface AiClientProjectRoot {
|
|
20
|
+
path: string;
|
|
21
|
+
source: AiClientRootSource;
|
|
22
|
+
}
|
|
23
|
+
export interface AiClientProjectRootsResult {
|
|
24
|
+
roots: AiClientProjectRoot[];
|
|
25
|
+
stats: {
|
|
26
|
+
candidates: number;
|
|
27
|
+
skippedMissing: number;
|
|
28
|
+
skippedUnsafe: number;
|
|
29
|
+
skippedNetwork: number;
|
|
30
|
+
skippedDisabled: number;
|
|
31
|
+
capped: number;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export declare const AI_CLIENT_ROOT_LIMITS: {
|
|
35
|
+
maxRoots: number;
|
|
36
|
+
maxWorkspaceDirs: number;
|
|
37
|
+
maxClaudeTranscriptDirs: number;
|
|
38
|
+
transcriptHeadBytes: number;
|
|
39
|
+
maxSmallFileBytes: number;
|
|
40
|
+
};
|
|
41
|
+
/** True when a path is an operating-system folder (daemon/scheduled-task cwd), never a real project. */
|
|
42
|
+
export declare function isOsSystemFolder(dir: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): boolean;
|
|
43
|
+
export declare function isNetworkPath(value: string): boolean;
|
|
44
|
+
export declare function claudeTranscriptProjectRoots(home: string): string[];
|
|
45
|
+
/** `folder` of each Cursor / VS Code workspace.json; multi-root and remote workspaces are skipped. */
|
|
46
|
+
export declare function workspaceStorageRoots(storageDir: string): string[];
|
|
47
|
+
/** `[projects."<path>"]` tables of a Codex config.toml whose `trust_level` is `trusted`. */
|
|
48
|
+
export declare function codexTrustedProjectRoots(configToml: string): string[];
|
|
49
|
+
/** Raw candidates per client, in client order (no filtering yet). */
|
|
50
|
+
export declare function readAiClientProjectCandidates(home?: string, env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): Record<AiClientRootSource, string[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Filter + dedupe + cap the raw candidates. Round-robin across clients so one
|
|
53
|
+
* client with hundreds of old workspaces cannot crowd out the others.
|
|
54
|
+
*/
|
|
55
|
+
export declare function selectAiClientProjectRoots(candidates: Partial<Record<AiClientRootSource, string[]>>, options?: {
|
|
56
|
+
home?: string;
|
|
57
|
+
disabledRoots?: string[];
|
|
58
|
+
/** Network shares are skipped unless they sit inside one of these (admin-picked) roots. */
|
|
59
|
+
allowedNetworkRoots?: string[];
|
|
60
|
+
maxRoots?: number;
|
|
61
|
+
env?: NodeJS.ProcessEnv;
|
|
62
|
+
platform?: NodeJS.Platform;
|
|
63
|
+
isDirectory?: (dir: string) => boolean;
|
|
64
|
+
}): AiClientProjectRootsResult;
|
|
65
|
+
/** Read every installed AI client's project list and return the safe, existing roots. */
|
|
66
|
+
export declare function collectAiClientProjectRoots(options?: {
|
|
67
|
+
home?: string;
|
|
68
|
+
env?: NodeJS.ProcessEnv;
|
|
69
|
+
platform?: NodeJS.Platform;
|
|
70
|
+
disabledRoots?: string[];
|
|
71
|
+
allowedNetworkRoots?: string[];
|
|
72
|
+
}): AiClientProjectRootsResult;
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AI_CLIENT_ROOT_LIMITS = void 0;
|
|
37
|
+
exports.isOsSystemFolder = isOsSystemFolder;
|
|
38
|
+
exports.isNetworkPath = isNetworkPath;
|
|
39
|
+
exports.claudeTranscriptProjectRoots = claudeTranscriptProjectRoots;
|
|
40
|
+
exports.workspaceStorageRoots = workspaceStorageRoots;
|
|
41
|
+
exports.codexTrustedProjectRoots = codexTrustedProjectRoots;
|
|
42
|
+
exports.readAiClientProjectCandidates = readAiClientProjectCandidates;
|
|
43
|
+
exports.selectAiClientProjectRoots = selectAiClientProjectRoots;
|
|
44
|
+
exports.collectAiClientProjectRoots = collectAiClientProjectRoots;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const url_1 = require("url");
|
|
49
|
+
const discoverAgentFiles_1 = require("./discoverAgentFiles");
|
|
50
|
+
const discoverPaths_1 = require("./discoverPaths");
|
|
51
|
+
exports.AI_CLIENT_ROOT_LIMITS = {
|
|
52
|
+
maxRoots: 50,
|
|
53
|
+
maxWorkspaceDirs: 300,
|
|
54
|
+
maxClaudeTranscriptDirs: 200,
|
|
55
|
+
transcriptHeadBytes: 64 * 1024,
|
|
56
|
+
maxSmallFileBytes: 256 * 1024,
|
|
57
|
+
};
|
|
58
|
+
/** True when a path is an operating-system folder (daemon/scheduled-task cwd), never a real project. */
|
|
59
|
+
function isOsSystemFolder(dir, platform = process.platform, env = process.env) {
|
|
60
|
+
if (platform === 'win32') {
|
|
61
|
+
const normalized = path.win32.resolve(dir).toLowerCase();
|
|
62
|
+
const windir = (env.SystemRoot || 'C:\\Windows').toLowerCase();
|
|
63
|
+
return normalized === windir || normalized.startsWith(`${windir}\\`) || /^[a-z]:\\$/.test(normalized);
|
|
64
|
+
}
|
|
65
|
+
const normalized = path.posix.resolve(dir);
|
|
66
|
+
return normalized === '/' || ['/usr', '/bin', '/sbin', '/etc', '/var'].some(root => normalized === root || normalized.startsWith(`${root}/`));
|
|
67
|
+
}
|
|
68
|
+
/** Program and system data folders: never a developer project, and never worth a walk. */
|
|
69
|
+
function isSystemLocation(dir, env, platform) {
|
|
70
|
+
if (isOsSystemFolder(dir, platform, env))
|
|
71
|
+
return true;
|
|
72
|
+
if (platform === 'win32') {
|
|
73
|
+
const programDirs = [env.ProgramFiles || 'C:\\Program Files', env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', env.ProgramData || 'C:\\ProgramData'];
|
|
74
|
+
return programDirs.some(root => (0, discoverPaths_1.pathIsWithin)(dir, root));
|
|
75
|
+
}
|
|
76
|
+
return ['/System', '/Library', '/Applications', '/opt/homebrew', '/proc', '/sys', '/dev'].some(root => (0, discoverPaths_1.pathIsWithin)(dir, root));
|
|
77
|
+
}
|
|
78
|
+
function isNetworkPath(value) {
|
|
79
|
+
return /^[\\/]{2}/.test(value);
|
|
80
|
+
}
|
|
81
|
+
function readSmallFile(file, maxBytes = exports.AI_CLIENT_ROOT_LIMITS.maxSmallFileBytes) {
|
|
82
|
+
try {
|
|
83
|
+
const stat = fs.statSync(file);
|
|
84
|
+
if (!stat.isFile() || stat.size > maxBytes)
|
|
85
|
+
return undefined;
|
|
86
|
+
return fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, '');
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function readHead(file, bytes) {
|
|
93
|
+
let fd;
|
|
94
|
+
try {
|
|
95
|
+
fd = fs.openSync(file, 'r');
|
|
96
|
+
const buffer = Buffer.alloc(bytes);
|
|
97
|
+
const read = fs.readSync(fd, buffer, 0, bytes, 0);
|
|
98
|
+
return buffer.subarray(0, read).toString('utf8');
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
if (fd !== undefined)
|
|
105
|
+
try {
|
|
106
|
+
fs.closeSync(fd);
|
|
107
|
+
}
|
|
108
|
+
catch { /* ignore */ }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Subdirectories of `dir`, most recently modified first, at most `limit`. */
|
|
112
|
+
function recentSubdirs(dir, limit) {
|
|
113
|
+
let names;
|
|
114
|
+
try {
|
|
115
|
+
names = fs.readdirSync(dir);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const dirs = [];
|
|
121
|
+
for (const name of names) {
|
|
122
|
+
const full = path.join(dir, name);
|
|
123
|
+
try {
|
|
124
|
+
const stat = fs.statSync(full);
|
|
125
|
+
if (stat.isDirectory())
|
|
126
|
+
dirs.push({ full, mtime: stat.mtimeMs });
|
|
127
|
+
}
|
|
128
|
+
catch { /* vanished or unreadable */ }
|
|
129
|
+
}
|
|
130
|
+
return dirs.sort((a, b) => b.mtime - a.mtime).slice(0, limit).map(entry => entry.full);
|
|
131
|
+
}
|
|
132
|
+
/** The `cwd` Claude Code recorded in the newest session transcript of one project folder. */
|
|
133
|
+
function claudeTranscriptCwd(projectDir) {
|
|
134
|
+
let newest;
|
|
135
|
+
try {
|
|
136
|
+
for (const name of fs.readdirSync(projectDir)) {
|
|
137
|
+
if (!name.endsWith('.jsonl'))
|
|
138
|
+
continue;
|
|
139
|
+
const file = path.join(projectDir, name);
|
|
140
|
+
const mtime = fs.statSync(file).mtimeMs;
|
|
141
|
+
if (!newest || mtime > newest.mtime)
|
|
142
|
+
newest = { file, mtime };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
if (!newest)
|
|
149
|
+
return undefined;
|
|
150
|
+
const head = readHead(newest.file, exports.AI_CLIENT_ROOT_LIMITS.transcriptHeadBytes);
|
|
151
|
+
const match = head?.match(/"cwd"\s*:\s*("(?:\\.|[^"\\])*")/);
|
|
152
|
+
if (!match)
|
|
153
|
+
return undefined;
|
|
154
|
+
try {
|
|
155
|
+
const cwd = JSON.parse(match[1]);
|
|
156
|
+
return typeof cwd === 'string' && cwd ? cwd : undefined;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function claudeTranscriptProjectRoots(home) {
|
|
163
|
+
const out = [];
|
|
164
|
+
for (const dir of recentSubdirs(path.join(home, '.claude', 'projects'), exports.AI_CLIENT_ROOT_LIMITS.maxClaudeTranscriptDirs)) {
|
|
165
|
+
const cwd = claudeTranscriptCwd(dir);
|
|
166
|
+
if (cwd)
|
|
167
|
+
out.push(cwd);
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
/** `folder` of each Cursor / VS Code workspace.json; multi-root and remote workspaces are skipped. */
|
|
172
|
+
function workspaceStorageRoots(storageDir) {
|
|
173
|
+
const out = [];
|
|
174
|
+
for (const dir of recentSubdirs(storageDir, exports.AI_CLIENT_ROOT_LIMITS.maxWorkspaceDirs)) {
|
|
175
|
+
const content = readSmallFile(path.join(dir, 'workspace.json'), 64 * 1024);
|
|
176
|
+
if (!content)
|
|
177
|
+
continue;
|
|
178
|
+
try {
|
|
179
|
+
const folder = JSON.parse(content)?.folder;
|
|
180
|
+
if (typeof folder !== 'string' || !/^file:\/\//i.test(folder))
|
|
181
|
+
continue;
|
|
182
|
+
out.push((0, url_1.fileURLToPath)(folder));
|
|
183
|
+
}
|
|
184
|
+
catch { /* malformed JSON or URI */ }
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
function parseTomlKey(value) {
|
|
189
|
+
const trimmed = value.trim();
|
|
190
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
191
|
+
try {
|
|
192
|
+
return JSON.parse(trimmed);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
199
|
+
return trimmed.slice(1, -1);
|
|
200
|
+
return trimmed || undefined;
|
|
201
|
+
}
|
|
202
|
+
/** `[projects."<path>"]` tables of a Codex config.toml whose `trust_level` is `trusted`. */
|
|
203
|
+
function codexTrustedProjectRoots(configToml) {
|
|
204
|
+
const out = [];
|
|
205
|
+
let current;
|
|
206
|
+
let trusted = false;
|
|
207
|
+
const flush = () => { if (current && trusted)
|
|
208
|
+
out.push(current); };
|
|
209
|
+
for (const rawLine of configToml.split(/\r?\n/)) {
|
|
210
|
+
const line = rawLine.trim();
|
|
211
|
+
if (!line || line.startsWith('#'))
|
|
212
|
+
continue;
|
|
213
|
+
if (line.startsWith('[')) {
|
|
214
|
+
flush();
|
|
215
|
+
const header = line.match(/^\[\s*projects\.((?:"(?:\\.|[^"\\])*")|(?:'[^']*'))\s*\]$/);
|
|
216
|
+
current = header ? parseTomlKey(header[1]) : undefined;
|
|
217
|
+
trusted = false;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (!current)
|
|
221
|
+
continue;
|
|
222
|
+
const kv = line.match(/^trust_level\s*=\s*(.+?)\s*(?:#.*)?$/);
|
|
223
|
+
if (kv)
|
|
224
|
+
trusted = parseTomlKey(kv[1]) === 'trusted';
|
|
225
|
+
}
|
|
226
|
+
flush();
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
function appConfigDir(appName, home, env, platform) {
|
|
230
|
+
if (platform === 'win32')
|
|
231
|
+
return path.join(env.APPDATA || path.join(home, 'AppData', 'Roaming'), appName);
|
|
232
|
+
if (platform === 'darwin')
|
|
233
|
+
return path.join(home, 'Library', 'Application Support', appName);
|
|
234
|
+
return path.join(env.XDG_CONFIG_HOME || path.join(home, '.config'), appName);
|
|
235
|
+
}
|
|
236
|
+
/** Raw candidates per client, in client order (no filtering yet). */
|
|
237
|
+
function readAiClientProjectCandidates(home = os.homedir(), env = process.env, platform = process.platform) {
|
|
238
|
+
const codexHome = env.CODEX_HOME || path.join(home, '.codex');
|
|
239
|
+
const codexConfig = readSmallFile(path.join(codexHome, 'config.toml'), 1024 * 1024);
|
|
240
|
+
return {
|
|
241
|
+
claude_code_project: [...(0, discoverAgentFiles_1.claudeProjectRoots)(home), ...claudeTranscriptProjectRoots(home)],
|
|
242
|
+
cursor_workspace: workspaceStorageRoots(path.join(appConfigDir('Cursor', home, env, platform), 'User', 'workspaceStorage')),
|
|
243
|
+
vscode_workspace: workspaceStorageRoots(path.join(appConfigDir('Code', home, env, platform), 'User', 'workspaceStorage')),
|
|
244
|
+
codex_trusted_project: codexConfig ? codexTrustedProjectRoots(codexConfig) : [],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const SOURCE_ORDER = ['claude_code_project', 'cursor_workspace', 'vscode_workspace', 'codex_trusted_project'];
|
|
248
|
+
/**
|
|
249
|
+
* Filter + dedupe + cap the raw candidates. Round-robin across clients so one
|
|
250
|
+
* client with hundreds of old workspaces cannot crowd out the others.
|
|
251
|
+
*/
|
|
252
|
+
function selectAiClientProjectRoots(candidates, options = {}) {
|
|
253
|
+
const home = options.home ?? os.homedir();
|
|
254
|
+
const env = options.env ?? process.env;
|
|
255
|
+
const platform = options.platform ?? process.platform;
|
|
256
|
+
const maxRoots = options.maxRoots ?? exports.AI_CLIENT_ROOT_LIMITS.maxRoots;
|
|
257
|
+
const isDirectory = options.isDirectory ?? ((dir) => {
|
|
258
|
+
try {
|
|
259
|
+
return fs.statSync(dir).isDirectory();
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
const stats = { candidates: 0, skippedMissing: 0, skippedUnsafe: 0, skippedNetwork: 0, skippedDisabled: 0, capped: 0 };
|
|
266
|
+
const roots = [];
|
|
267
|
+
const seen = new Set();
|
|
268
|
+
const queues = SOURCE_ORDER.map(source => ({ source, items: [...(candidates[source] || [])] }));
|
|
269
|
+
const accept = (raw, source) => {
|
|
270
|
+
if (typeof raw !== 'string' || !raw.trim())
|
|
271
|
+
return;
|
|
272
|
+
const value = raw.trim();
|
|
273
|
+
if (!path.isAbsolute(value) && !isNetworkPath(value)) {
|
|
274
|
+
stats.skippedUnsafe += 1;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const resolved = path.resolve(value);
|
|
278
|
+
const key = (0, discoverPaths_1.normalizedPath)(resolved);
|
|
279
|
+
if (seen.has(key))
|
|
280
|
+
return;
|
|
281
|
+
seen.add(key);
|
|
282
|
+
stats.candidates += 1;
|
|
283
|
+
if (isNetworkPath(value) && !(options.allowedNetworkRoots || []).some(root => (0, discoverPaths_1.pathIsWithin)(resolved, root))) {
|
|
284
|
+
stats.skippedNetwork += 1;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
// The home folder itself (or any ancestor, e.g. C:\Users) is already the default
|
|
288
|
+
// root; drive roots and system folders would turn a project walk into a disk walk.
|
|
289
|
+
if ((0, discoverPaths_1.pathIsWithin)(home, resolved) || path.parse(resolved).root === resolved || isSystemLocation(resolved, env, platform)) {
|
|
290
|
+
stats.skippedUnsafe += 1;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if ((0, discoverPaths_1.isExcludedPath)(resolved, options.disabledRoots || [])) {
|
|
294
|
+
stats.skippedDisabled += 1;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (!isDirectory(resolved)) {
|
|
298
|
+
stats.skippedMissing += 1;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (roots.length >= maxRoots) {
|
|
302
|
+
stats.capped += 1;
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
roots.push({ path: resolved, source });
|
|
306
|
+
};
|
|
307
|
+
while (queues.some(queue => queue.items.length > 0)) {
|
|
308
|
+
for (const queue of queues) {
|
|
309
|
+
const next = queue.items.shift();
|
|
310
|
+
if (next !== undefined)
|
|
311
|
+
accept(next, queue.source);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return { roots, stats };
|
|
315
|
+
}
|
|
316
|
+
/** Read every installed AI client's project list and return the safe, existing roots. */
|
|
317
|
+
function collectAiClientProjectRoots(options = {}) {
|
|
318
|
+
const home = options.home ?? os.homedir();
|
|
319
|
+
const env = options.env ?? process.env;
|
|
320
|
+
const platform = options.platform ?? process.platform;
|
|
321
|
+
try {
|
|
322
|
+
return selectAiClientProjectRoots(readAiClientProjectCandidates(home, env, platform), { ...options, home, env, platform });
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return { roots: [], stats: { candidates: 0, skippedMissing: 0, skippedUnsafe: 0, skippedNetwork: 0, skippedDisabled: 0, capped: 0 } };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -36,7 +36,7 @@ export declare function extractServerMap(parsed: any): Record<string, any>;
|
|
|
36
36
|
* Every entry named `serverName` across this machine's AI-client MCP configs
|
|
37
37
|
* (Cursor, Claude Code incl. per-project maps in ~/.claude.json, Codex TOML,
|
|
38
38
|
* VS Code, Windsurf, Gemini, ...). Matches the exact name or the sanitized
|
|
39
|
-
* alias clients such as Codex expose (`
|
|
39
|
+
* alias clients such as Codex expose (`contoso-bank` -> `contoso_bank`).
|
|
40
40
|
* Unreadable / unparsable files are skipped, never thrown.
|
|
41
41
|
*/
|
|
42
42
|
export declare function findClientMcpServers(serverName: string, cwd?: string, clientKey?: string): ProxyClassifiableServer[];
|
|
@@ -108,7 +108,7 @@ function sanitizedServerAlias(name) {
|
|
|
108
108
|
* Every entry named `serverName` across this machine's AI-client MCP configs
|
|
109
109
|
* (Cursor, Claude Code incl. per-project maps in ~/.claude.json, Codex TOML,
|
|
110
110
|
* VS Code, Windsurf, Gemini, ...). Matches the exact name or the sanitized
|
|
111
|
-
* alias clients such as Codex expose (`
|
|
111
|
+
* alias clients such as Codex expose (`contoso-bank` -> `contoso_bank`).
|
|
112
112
|
* Unreadable / unparsable files are skipped, never thrown.
|
|
113
113
|
*/
|
|
114
114
|
function findClientMcpServers(serverName, cwd = process.cwd(), clientKey) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ScanTruncationReason } from './discoverPaths';
|
|
1
2
|
export type SecretSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
2
3
|
export type SecretCategory = 'env_file' | 'credential_store' | 'config' | 'shell_history' | 'ssh_key' | 'other';
|
|
3
4
|
export interface SecretFinding {
|
|
@@ -26,16 +27,59 @@ export interface SecretsScanResult {
|
|
|
26
27
|
stats?: {
|
|
27
28
|
candidateFiles: number;
|
|
28
29
|
excludedFiles: number;
|
|
30
|
+
cloudPlaceholdersSkipped?: number;
|
|
29
31
|
};
|
|
30
32
|
truncated?: {
|
|
31
33
|
candidateFiles: boolean;
|
|
32
34
|
findings: boolean;
|
|
33
35
|
};
|
|
36
|
+
/** Which limit cut the candidate file walk short; absent when it finished. */
|
|
37
|
+
truncatedReasons?: {
|
|
38
|
+
candidateFiles?: ScanTruncationReason;
|
|
39
|
+
};
|
|
34
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Whether ~/.docker/config.json holds credentials itself. `auths` entries that
|
|
43
|
+
* only name a registry are metadata when `credsStore` / `credHelpers` keep the
|
|
44
|
+
* secret in the OS store (Docker Desktop on Windows: `credsStore: desktop` =
|
|
45
|
+
* Windows Credential Manager). Counts only; never returns a value.
|
|
46
|
+
*/
|
|
47
|
+
export declare function dockerConfigCredentialFacts(content: string): {
|
|
48
|
+
inlineRegistries: number;
|
|
49
|
+
usesCredentialStore: boolean;
|
|
50
|
+
};
|
|
51
|
+
export declare function isCloudPlaceholderAttributes(attributes: number): boolean;
|
|
52
|
+
/** True for paths inside a OneDrive folder (by env root or a "OneDrive…" path segment). */
|
|
53
|
+
export declare function isCloudSyncedPath(filePath: string, env?: NodeJS.ProcessEnv): boolean;
|
|
54
|
+
export type FileAttributeProbe = (files: string[]) => Array<number | undefined> | null;
|
|
55
|
+
/**
|
|
56
|
+
* One PowerShell call reads the attributes of every candidate (no file is
|
|
57
|
+
* opened, so nothing is downloaded). Paths go in base64 on stdin so non-ASCII
|
|
58
|
+
* folder names survive the console code page; one integer per line comes back
|
|
59
|
+
* in input order. Returns null when PowerShell is unavailable or times out.
|
|
60
|
+
*/
|
|
61
|
+
export declare const probeWindowsFileAttributes: FileAttributeProbe;
|
|
62
|
+
/**
|
|
63
|
+
* Drop OneDrive online-only placeholders before any read. Only files under a
|
|
64
|
+
* OneDrive folder are probed; a failed probe keeps every file (the per-file
|
|
65
|
+
* size cap still bounds what is read).
|
|
66
|
+
*/
|
|
67
|
+
export declare function dropCloudPlaceholders(files: string[], options?: {
|
|
68
|
+
platform?: NodeJS.Platform;
|
|
69
|
+
env?: NodeJS.ProcessEnv;
|
|
70
|
+
probe?: FileAttributeProbe;
|
|
71
|
+
}): {
|
|
72
|
+
files: string[];
|
|
73
|
+
skipped: number;
|
|
74
|
+
};
|
|
35
75
|
export declare function scanSecrets(options?: {
|
|
36
76
|
cwd?: string;
|
|
37
77
|
maxEnvDepth?: number;
|
|
38
78
|
extraRoots?: string[];
|
|
39
79
|
disabledRoots?: string[];
|
|
40
80
|
configFiles?: string[];
|
|
81
|
+
/** Test seams for the OneDrive placeholder check; production uses the real platform/env/probe. */
|
|
82
|
+
platform?: NodeJS.Platform;
|
|
83
|
+
env?: NodeJS.ProcessEnv;
|
|
84
|
+
probeFileAttributes?: FileAttributeProbe;
|
|
41
85
|
}): SecretsScanResult;
|