fullcourtdefense-cli 1.14.15 → 1.14.16
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.
|
@@ -314,7 +314,7 @@ function pushDiscoveredServer(out, name, cfgRaw, source, configPath) {
|
|
|
314
314
|
function parseConfigFile(filePath, source) {
|
|
315
315
|
let raw;
|
|
316
316
|
try {
|
|
317
|
-
raw = fs.readFileSync(filePath, 'utf-8');
|
|
317
|
+
raw = fs.readFileSync(filePath, 'utf-8').replace(/^\uFEFF/, '');
|
|
318
318
|
}
|
|
319
319
|
catch {
|
|
320
320
|
return [];
|
|
@@ -806,12 +806,16 @@ async function discoverCommand(args, config) {
|
|
|
806
806
|
const scanRoots = (0, discoverPaths_1.resolveScanRoots)(args.scanRoot);
|
|
807
807
|
const sweep = scanRoots.length > 0 ? (0, discoverPaths_1.scanRootsForProjectConfigs)(scanRoots) : { candidates: [], scannedDirs: 0 };
|
|
808
808
|
const sweepProjectRoots = deriveProjectRoots(sweep.candidates);
|
|
809
|
+
const knownConfigCandidates = candidateConfigPaths(cwd, args.extraPath);
|
|
810
|
+
const secretConfigFiles = [...knownConfigCandidates, ...sweep.candidates]
|
|
811
|
+
.map(candidate => path.resolve(candidate.path))
|
|
812
|
+
.filter(file => fs.existsSync(file));
|
|
809
813
|
if (scanRoots.length > 0 && !silent) {
|
|
810
814
|
console.log(`${COLOR.gray}Scan-root sweep: checked ${sweep.scannedDirs} folder(s) under ${scanRoots.length} root(s) — found ${sweep.candidates.length} project config(s) across ${sweepProjectRoots.length} project folder(s).${COLOR.reset}`);
|
|
811
815
|
}
|
|
812
816
|
const scanRootConfigPaths = new Set();
|
|
813
817
|
if (runMcp) {
|
|
814
|
-
const candidates =
|
|
818
|
+
const candidates = [...knownConfigCandidates];
|
|
815
819
|
const known = new Set(candidates.map(c => path.resolve(c.path).toLowerCase()));
|
|
816
820
|
for (const candidate of sweep.candidates) {
|
|
817
821
|
const key = path.resolve(candidate.path).toLowerCase();
|
|
@@ -849,7 +853,7 @@ async function discoverCommand(args, config) {
|
|
|
849
853
|
}
|
|
850
854
|
}
|
|
851
855
|
const secrets = surfaces.has('secrets') || surfaces.has('posture')
|
|
852
|
-
? (0, discoverSecrets_1.scanSecrets)({ cwd, extraRoots: sweepProjectRoots })
|
|
856
|
+
? (0, discoverSecrets_1.scanSecrets)({ cwd, extraRoots: sweepProjectRoots, configFiles: secretConfigFiles })
|
|
853
857
|
: undefined;
|
|
854
858
|
const agentFiles = surfaces.has('agent-files') || surfaces.has('posture')
|
|
855
859
|
? (0, discoverAgentFiles_1.scanAgentFiles)({ cwd, extraRoots: sweepProjectRoots })
|
|
@@ -57,13 +57,15 @@ const SECRET_PATTERNS = [
|
|
|
57
57
|
{ re: /\bgithub_pat_[a-zA-Z0-9_]{20,}\b/, provider: 'github', severity: 'critical', title: 'GitHub fine-grained PAT', impact: 'scoped access to your repositories' },
|
|
58
58
|
{ re: /\bglpat-[a-zA-Z0-9_-]{20,}\b/, provider: 'gitlab', severity: 'critical', title: 'GitLab personal access token', impact: 'read/write access to your GitLab projects' },
|
|
59
59
|
{ re: /\bAKIA[0-9A-Z]{16}\b/, provider: 'aws', severity: 'critical', title: 'AWS access key ID', impact: 'AWS API access (paired with its secret key)' },
|
|
60
|
-
{ re: /\baws_secret_access_key\s*[=:]\s*['"]?[A-Za-z0-9/+=]{40}\b/i, provider: 'aws', severity: 'critical', title: 'AWS secret access key', impact: 'full AWS API access for the paired key ID' },
|
|
60
|
+
{ re: /\baws_secret_access_key\b['"]?\s*[=:]\s*['"]?[A-Za-z0-9/+=]{40}\b/i, provider: 'aws', severity: 'critical', title: 'AWS secret access key', impact: 'full AWS API access for the paired key ID' },
|
|
61
61
|
{ re: /\bAIza[0-9A-Za-z_-]{35}\b/, provider: 'google', severity: 'critical', title: 'Google API key', impact: 'Google Cloud API usage billed to your project' },
|
|
62
62
|
{ re: /\bxox[baprs]-[a-zA-Z0-9-]{10,}\b/, provider: 'slack', severity: 'high', title: 'Slack token', impact: 'read/post messages as your Slack app or user' },
|
|
63
63
|
{ re: /\bhooks\.slack\.com\/services\/T[a-zA-Z0-9]+\/B[a-zA-Z0-9]+\/[a-zA-Z0-9]+\b/, provider: 'slack', severity: 'high', title: 'Slack incoming webhook URL', impact: 'post arbitrary messages into your Slack channel' },
|
|
64
64
|
{ re: /\bsk_live_[0-9a-zA-Z]{24,}\b/, provider: 'stripe', severity: 'critical', title: 'Stripe live secret key', impact: 'charges, refunds, and customer data in live mode' },
|
|
65
65
|
{ re: /\brk_live_[0-9a-zA-Z]{24,}\b/, provider: 'stripe', severity: 'critical', title: 'Stripe restricted key', impact: 'scoped live-mode payment operations' },
|
|
66
66
|
{ re: /\bSG\.[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}\b/, provider: 'sendgrid', severity: 'high', title: 'SendGrid API key', impact: 'send email from your verified domains (phishing risk)' },
|
|
67
|
+
{ re: /\bsbp_[a-zA-Z0-9_-]{20,}\b/, provider: 'supabase', severity: 'high', title: 'Supabase access token', impact: 'manage Supabase projects and data allowed by this token' },
|
|
68
|
+
{ re: /\bfc-[a-fA-F0-9]{24,}\b/, provider: 'firecrawl', severity: 'medium', title: 'Firecrawl API key', impact: 'use web crawling/extraction quota billed to your account' },
|
|
67
69
|
{ re: /\bSK[0-9a-fA-F]{32}\b/, provider: 'twilio', severity: 'high', title: 'Twilio API key SID', impact: 'send SMS/calls billed to your account' },
|
|
68
70
|
{ re: /\bhf_[a-zA-Z0-9]{30,}\b/, provider: 'huggingface', severity: 'high', title: 'HuggingFace token', impact: 'access to your private models and datasets' },
|
|
69
71
|
{ re: /\bnpm_[a-zA-Z0-9]{36,}\b/, provider: 'npm', severity: 'high', title: 'npm access token', impact: 'publish packages as you (supply-chain risk)' },
|
|
@@ -98,6 +100,8 @@ const PROVIDER_REMEDIATION = {
|
|
|
98
100
|
gcp_adc: 'Revoke the local ADC refresh token (`gcloud auth application-default revoke`) if unneeded, then re-auth with least-privilege accounts.',
|
|
99
101
|
netrc: 'Remove plaintext passwords from .netrc or replace them with token helpers / OS keychain storage.',
|
|
100
102
|
bearer: 'Rotate the token and avoid pasting bearer tokens into shell history or config files.',
|
|
103
|
+
supabase: 'Revoke this token in the Supabase dashboard and replace it with a scoped secret stored outside MCP config files.',
|
|
104
|
+
firecrawl: 'Rotate this API key in Firecrawl and store it in a secret manager or environment injected by the runtime.',
|
|
101
105
|
url_basic_auth: 'Rotate the password/token embedded in the URL and replace it with environment or secret-manager lookup.',
|
|
102
106
|
database: 'Change the database password and move the connection string to a secret manager; restrict the DB user\u2019s privileges.',
|
|
103
107
|
};
|
|
@@ -301,7 +305,7 @@ function readTextFile(filePath) {
|
|
|
301
305
|
const stat = fs.statSync(filePath);
|
|
302
306
|
if (!stat.isFile() || stat.size > MAX_FILE_BYTES)
|
|
303
307
|
return null;
|
|
304
|
-
return fs.readFileSync(filePath, 'utf-8');
|
|
308
|
+
return fs.readFileSync(filePath, 'utf-8').replace(/^\uFEFF/, '');
|
|
305
309
|
}
|
|
306
310
|
catch {
|
|
307
311
|
return null;
|
|
@@ -447,6 +451,19 @@ function scanSecrets(options = {}) {
|
|
|
447
451
|
scannedFiles += 1;
|
|
448
452
|
scanTextLines(envFile, content, 'env_file', findings, seen);
|
|
449
453
|
}
|
|
454
|
+
// MCP/client config files are already discovered by the MCP inventory pass.
|
|
455
|
+
// Scan only those known files for embedded env/header/arg secrets; this catches
|
|
456
|
+
// real-world mistakes like GitHub/Slack tokens in MCP `env`, DB URLs in `args`,
|
|
457
|
+
// and Bearer tokens in remote MCP headers without expanding the filesystem scan.
|
|
458
|
+
for (const configFile of Array.from(new Set(options.configFiles ?? []))) {
|
|
459
|
+
if (findings.length >= MAX_FINDINGS)
|
|
460
|
+
break;
|
|
461
|
+
const content = readTextFile(configFile);
|
|
462
|
+
if (!content)
|
|
463
|
+
continue;
|
|
464
|
+
scannedFiles += 1;
|
|
465
|
+
scanTextLines(configFile, content, 'config', findings, seen);
|
|
466
|
+
}
|
|
450
467
|
findings.sort((a, b) => {
|
|
451
468
|
const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
452
469
|
return order[a.severity] - order[b.severity] || a.filePath.localeCompare(b.filePath);
|
package/dist/version.json
CHANGED