s3db.js 13.6.0 → 14.0.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/README.md +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +94 -49
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/api/utils/template-engine.js +77 -3
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/README.md +126 -126
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SecretsStage
|
|
3
|
+
*
|
|
4
|
+
* Secrets detection stage using multiple methods:
|
|
5
|
+
* - Gitleaks: Industry-standard secrets scanner
|
|
6
|
+
* - TruffleHog: Git repository secret scanning
|
|
7
|
+
* - Regex patterns: Common API keys, tokens, credentials
|
|
8
|
+
* - JS file analysis: Extract endpoints and potential secrets
|
|
9
|
+
*
|
|
10
|
+
* Scans:
|
|
11
|
+
* - JavaScript files from HTTP responses
|
|
12
|
+
* - Wayback/historical URLs
|
|
13
|
+
* - Git repositories (if accessible)
|
|
14
|
+
* - Configuration files
|
|
15
|
+
* - Environment variables in responses
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { exec } from 'child_process';
|
|
19
|
+
import { promisify } from 'util';
|
|
20
|
+
import { writeFile, unlink, mkdir } from 'fs/promises';
|
|
21
|
+
import { join } from 'path';
|
|
22
|
+
import { tmpdir } from 'os';
|
|
23
|
+
import { randomBytes } from 'crypto';
|
|
24
|
+
|
|
25
|
+
const execAsync = promisify(exec);
|
|
26
|
+
|
|
27
|
+
export class SecretsStage {
|
|
28
|
+
constructor(plugin) {
|
|
29
|
+
this.plugin = plugin;
|
|
30
|
+
this.timeout = 30000; // 30 second timeout for scans
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Execute secrets detection scan
|
|
35
|
+
* @param {Object} target - Target object with host property
|
|
36
|
+
* @param {Object} options - Scan options
|
|
37
|
+
* @returns {Object} Secrets findings
|
|
38
|
+
*/
|
|
39
|
+
async execute(target, options = {}) {
|
|
40
|
+
const result = {
|
|
41
|
+
status: 'ok',
|
|
42
|
+
findings: [],
|
|
43
|
+
summary: {
|
|
44
|
+
total: 0,
|
|
45
|
+
highSeverity: 0,
|
|
46
|
+
mediumSeverity: 0,
|
|
47
|
+
lowSeverity: 0,
|
|
48
|
+
byType: {}
|
|
49
|
+
},
|
|
50
|
+
scanners: {},
|
|
51
|
+
errors: {}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Check which scanners are available
|
|
56
|
+
const availableScanners = await this.checkAvailableScanners();
|
|
57
|
+
|
|
58
|
+
// Collect URLs to scan (from HTTP stage if available)
|
|
59
|
+
const urlsToScan = this.collectUrlsToScan(target, options);
|
|
60
|
+
|
|
61
|
+
// Run Gitleaks if available
|
|
62
|
+
if (availableScanners.gitleaks && options.gitleaks !== false) {
|
|
63
|
+
try {
|
|
64
|
+
const gitleaksFindings = await this.runGitleaks(urlsToScan, options);
|
|
65
|
+
result.scanners.gitleaks = gitleaksFindings;
|
|
66
|
+
result.findings.push(...gitleaksFindings.findings);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
result.errors.gitleaks = error.message;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Run regex-based pattern matching (always available)
|
|
73
|
+
if (options.patterns !== false) {
|
|
74
|
+
try {
|
|
75
|
+
const patternFindings = await this.runPatternMatching(urlsToScan, options);
|
|
76
|
+
result.scanners.patterns = patternFindings;
|
|
77
|
+
result.findings.push(...patternFindings.findings);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
result.errors.patterns = error.message;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Deduplicate and categorize findings
|
|
84
|
+
result.findings = this.deduplicateFindings(result.findings);
|
|
85
|
+
result.summary = this.buildSummary(result.findings);
|
|
86
|
+
|
|
87
|
+
if (result.findings.length === 0) {
|
|
88
|
+
result.status = 'clean';
|
|
89
|
+
} else if (result.summary.highSeverity > 0) {
|
|
90
|
+
result.status = 'critical';
|
|
91
|
+
} else if (result.summary.mediumSeverity > 0) {
|
|
92
|
+
result.status = 'warning';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
} catch (error) {
|
|
96
|
+
result.status = 'error';
|
|
97
|
+
result.message = error?.message || 'Secrets scan failed';
|
|
98
|
+
result.errors.scan = error?.message;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check which secret scanners are available
|
|
106
|
+
* @returns {Promise<Object>} Available scanners
|
|
107
|
+
*/
|
|
108
|
+
async checkAvailableScanners() {
|
|
109
|
+
const scanners = {
|
|
110
|
+
gitleaks: false,
|
|
111
|
+
trufflehog: false
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
await execAsync('which gitleaks', { timeout: 2000 });
|
|
116
|
+
scanners.gitleaks = true;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// Gitleaks not available
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await execAsync('which trufflehog', { timeout: 2000 });
|
|
123
|
+
scanners.trufflehog = true;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// TruffleHog not available
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return scanners;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Collect URLs to scan from target and previous stages
|
|
133
|
+
* @param {Object} target - Target object
|
|
134
|
+
* @param {Object} options - Scan options
|
|
135
|
+
* @returns {Array<string>} URLs to scan
|
|
136
|
+
*/
|
|
137
|
+
collectUrlsToScan(target, options) {
|
|
138
|
+
const urls = [];
|
|
139
|
+
|
|
140
|
+
// Add main target URL
|
|
141
|
+
const protocol = target.protocol || 'https';
|
|
142
|
+
const port = target.port ? `:${target.port}` : '';
|
|
143
|
+
urls.push(`${protocol}://${target.host}${port}`);
|
|
144
|
+
|
|
145
|
+
// Add common paths that might contain secrets
|
|
146
|
+
const commonPaths = [
|
|
147
|
+
'/',
|
|
148
|
+
'/robots.txt',
|
|
149
|
+
'/sitemap.xml',
|
|
150
|
+
'/.git/config',
|
|
151
|
+
'/.env',
|
|
152
|
+
'/config.json',
|
|
153
|
+
'/package.json',
|
|
154
|
+
'/composer.json',
|
|
155
|
+
'/app.js',
|
|
156
|
+
'/main.js',
|
|
157
|
+
'/bundle.js',
|
|
158
|
+
'/vendor.js'
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
for (const path of commonPaths) {
|
|
162
|
+
urls.push(`${protocol}://${target.host}${port}${path}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Add custom URLs if provided
|
|
166
|
+
if (options.urls && Array.isArray(options.urls)) {
|
|
167
|
+
urls.push(...options.urls);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return [...new Set(urls)]; // Deduplicate
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Run Gitleaks scanner
|
|
175
|
+
* @param {Array<string>} urls - URLs to scan
|
|
176
|
+
* @param {Object} options - Scan options
|
|
177
|
+
* @returns {Promise<Object>} Gitleaks findings
|
|
178
|
+
*/
|
|
179
|
+
async runGitleaks(urls, options) {
|
|
180
|
+
const findings = {
|
|
181
|
+
status: 'ok',
|
|
182
|
+
findings: [],
|
|
183
|
+
scannedUrls: urls.length
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Create temporary directory for scan
|
|
187
|
+
const tmpDir = join(tmpdir(), `gitleaks-${randomBytes(8).toString('hex')}`);
|
|
188
|
+
await mkdir(tmpDir, { recursive: true });
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
// Fetch content from URLs
|
|
192
|
+
const contentFiles = [];
|
|
193
|
+
|
|
194
|
+
for (const url of urls.slice(0, options.maxUrls || 20)) {
|
|
195
|
+
try {
|
|
196
|
+
const response = await this.fetchUrl(url);
|
|
197
|
+
if (response && response.body) {
|
|
198
|
+
const filename = join(tmpDir, `${randomBytes(4).toString('hex')}.txt`);
|
|
199
|
+
await writeFile(filename, response.body);
|
|
200
|
+
contentFiles.push({ filename, url });
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// Skip URLs that fail to fetch
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Run Gitleaks detect on each file
|
|
208
|
+
for (const { filename, url } of contentFiles) {
|
|
209
|
+
try {
|
|
210
|
+
const gitleaksOutput = await this.executeGitleaks(filename, options);
|
|
211
|
+
|
|
212
|
+
if (gitleaksOutput && gitleaksOutput.length > 0) {
|
|
213
|
+
for (const finding of gitleaksOutput) {
|
|
214
|
+
findings.findings.push({
|
|
215
|
+
type: finding.RuleID || 'unknown',
|
|
216
|
+
description: finding.Description || finding.RuleID,
|
|
217
|
+
severity: this.mapGitleaksSeverity(finding),
|
|
218
|
+
location: url,
|
|
219
|
+
line: finding.StartLine,
|
|
220
|
+
match: finding.Match || finding.Secret?.substring(0, 50),
|
|
221
|
+
file: finding.File,
|
|
222
|
+
scanner: 'gitleaks'
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// Continue with next file
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Cleanup temporary files
|
|
232
|
+
for (const { filename } of contentFiles) {
|
|
233
|
+
try {
|
|
234
|
+
await unlink(filename);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
// Ignore cleanup errors
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
} finally {
|
|
241
|
+
// Cleanup temporary directory
|
|
242
|
+
try {
|
|
243
|
+
await execAsync(`rm -rf "${tmpDir}"`);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
// Ignore cleanup errors
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return findings;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Execute Gitleaks command
|
|
254
|
+
* @param {string} filepath - File to scan
|
|
255
|
+
* @param {Object} options - Scan options
|
|
256
|
+
* @returns {Promise<Array>} Gitleaks findings
|
|
257
|
+
*/
|
|
258
|
+
async executeGitleaks(filepath, options) {
|
|
259
|
+
try {
|
|
260
|
+
const { stdout } = await execAsync(
|
|
261
|
+
`gitleaks detect --no-git --source "${filepath}" --report-format json --report-path /dev/stdout`,
|
|
262
|
+
{
|
|
263
|
+
timeout: this.timeout,
|
|
264
|
+
maxBuffer: 5 * 1024 * 1024 // 5MB buffer
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (!stdout || stdout.trim() === '') {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return JSON.parse(stdout);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
// Gitleaks exits with code 1 if leaks found
|
|
275
|
+
if (error.stdout) {
|
|
276
|
+
try {
|
|
277
|
+
return JSON.parse(error.stdout);
|
|
278
|
+
} catch (parseError) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Map Gitleaks severity to our scale
|
|
288
|
+
* @param {Object} finding - Gitleaks finding
|
|
289
|
+
* @returns {string} Severity level
|
|
290
|
+
*/
|
|
291
|
+
mapGitleaksSeverity(finding) {
|
|
292
|
+
// Gitleaks doesn't provide severity, so we classify by rule type
|
|
293
|
+
const highSeverityRules = [
|
|
294
|
+
'aws-access-token',
|
|
295
|
+
'aws-secret-key',
|
|
296
|
+
'github-pat',
|
|
297
|
+
'github-oauth',
|
|
298
|
+
'private-key',
|
|
299
|
+
'slack-token',
|
|
300
|
+
'stripe-api-key'
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
const ruleId = (finding.RuleID || '').toLowerCase();
|
|
304
|
+
|
|
305
|
+
if (highSeverityRules.some(rule => ruleId.includes(rule))) {
|
|
306
|
+
return 'high';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (ruleId.includes('api') || ruleId.includes('token') || ruleId.includes('key')) {
|
|
310
|
+
return 'medium';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return 'low';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Run regex-based pattern matching
|
|
318
|
+
* @param {Array<string>} urls - URLs to scan
|
|
319
|
+
* @param {Object} options - Scan options
|
|
320
|
+
* @returns {Promise<Object>} Pattern findings
|
|
321
|
+
*/
|
|
322
|
+
async runPatternMatching(urls, options) {
|
|
323
|
+
const findings = {
|
|
324
|
+
status: 'ok',
|
|
325
|
+
findings: [],
|
|
326
|
+
scannedUrls: 0
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Common secret patterns
|
|
330
|
+
const patterns = [
|
|
331
|
+
{
|
|
332
|
+
name: 'AWS Access Key',
|
|
333
|
+
regex: /AKIA[0-9A-Z]{16}/g,
|
|
334
|
+
severity: 'high',
|
|
335
|
+
description: 'AWS Access Key ID found'
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: 'AWS Secret Key',
|
|
339
|
+
regex: /aws(.{0,20})?['\"][0-9a-zA-Z\/+]{40}['\"]?/gi,
|
|
340
|
+
severity: 'high',
|
|
341
|
+
description: 'Possible AWS Secret Access Key'
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
name: 'GitHub Token',
|
|
345
|
+
regex: /gh[pousr]_[0-9a-zA-Z]{36}/g,
|
|
346
|
+
severity: 'high',
|
|
347
|
+
description: 'GitHub Personal Access Token'
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
name: 'Generic API Key',
|
|
351
|
+
regex: /api[_-]?key['\"]?\s*[:=]\s*['\"]?([0-9a-zA-Z\-_]{20,})['\"]?/gi,
|
|
352
|
+
severity: 'medium',
|
|
353
|
+
description: 'Generic API key pattern'
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: 'Slack Token',
|
|
357
|
+
regex: /xox[baprs]-[0-9a-zA-Z\-]+/g,
|
|
358
|
+
severity: 'high',
|
|
359
|
+
description: 'Slack token found'
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: 'Stripe API Key',
|
|
363
|
+
regex: /sk_live_[0-9a-zA-Z]{24,}/g,
|
|
364
|
+
severity: 'high',
|
|
365
|
+
description: 'Stripe live API key'
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: 'Private Key',
|
|
369
|
+
regex: /-----BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-----/g,
|
|
370
|
+
severity: 'high',
|
|
371
|
+
description: 'Private key detected'
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: 'JWT Token',
|
|
375
|
+
regex: /eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g,
|
|
376
|
+
severity: 'medium',
|
|
377
|
+
description: 'JWT token found'
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: 'Database URL',
|
|
381
|
+
regex: /(postgres|mysql|mongodb):\/\/[^\s]+/gi,
|
|
382
|
+
severity: 'medium',
|
|
383
|
+
description: 'Database connection string'
|
|
384
|
+
}
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
// Scan URLs
|
|
388
|
+
for (const url of urls.slice(0, options.maxUrls || 20)) {
|
|
389
|
+
try {
|
|
390
|
+
const response = await this.fetchUrl(url);
|
|
391
|
+
|
|
392
|
+
if (response && response.body) {
|
|
393
|
+
findings.scannedUrls++;
|
|
394
|
+
|
|
395
|
+
// Test each pattern
|
|
396
|
+
for (const pattern of patterns) {
|
|
397
|
+
const matches = response.body.matchAll(pattern.regex);
|
|
398
|
+
|
|
399
|
+
for (const match of matches) {
|
|
400
|
+
findings.findings.push({
|
|
401
|
+
type: pattern.name,
|
|
402
|
+
description: pattern.description,
|
|
403
|
+
severity: pattern.severity,
|
|
404
|
+
location: url,
|
|
405
|
+
match: match[0].substring(0, 100), // Truncate long matches
|
|
406
|
+
context: this.extractContext(response.body, match.index, 50),
|
|
407
|
+
scanner: 'regex-patterns'
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
// Continue with next URL
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return findings;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Fetch URL content
|
|
422
|
+
* @param {string} url - URL to fetch
|
|
423
|
+
* @returns {Promise<Object>} Response with body
|
|
424
|
+
*/
|
|
425
|
+
async fetchUrl(url) {
|
|
426
|
+
try {
|
|
427
|
+
const { stdout, stderr } = await execAsync(
|
|
428
|
+
`curl -sL -m 10 "${url}"`,
|
|
429
|
+
{
|
|
430
|
+
timeout: 15000,
|
|
431
|
+
maxBuffer: 2 * 1024 * 1024 // 2MB max response
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
body: stdout,
|
|
437
|
+
error: stderr
|
|
438
|
+
};
|
|
439
|
+
} catch (error) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Extract context around a match
|
|
446
|
+
* @param {string} text - Full text
|
|
447
|
+
* @param {number} index - Match index
|
|
448
|
+
* @param {number} contextLength - Characters before/after
|
|
449
|
+
* @returns {string} Context string
|
|
450
|
+
*/
|
|
451
|
+
extractContext(text, index, contextLength) {
|
|
452
|
+
const start = Math.max(0, index - contextLength);
|
|
453
|
+
const end = Math.min(text.length, index + contextLength);
|
|
454
|
+
|
|
455
|
+
let context = text.substring(start, end);
|
|
456
|
+
|
|
457
|
+
if (start > 0) context = '...' + context;
|
|
458
|
+
if (end < text.length) context = context + '...';
|
|
459
|
+
|
|
460
|
+
return context.replace(/\s+/g, ' ').trim();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Deduplicate findings by match content
|
|
465
|
+
* @param {Array} findings - All findings
|
|
466
|
+
* @returns {Array} Deduplicated findings
|
|
467
|
+
*/
|
|
468
|
+
deduplicateFindings(findings) {
|
|
469
|
+
const seen = new Set();
|
|
470
|
+
const unique = [];
|
|
471
|
+
|
|
472
|
+
for (const finding of findings) {
|
|
473
|
+
const key = `${finding.type}:${finding.match}`;
|
|
474
|
+
|
|
475
|
+
if (!seen.has(key)) {
|
|
476
|
+
seen.add(key);
|
|
477
|
+
unique.push(finding);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return unique;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Build summary statistics
|
|
486
|
+
* @param {Array} findings - All findings
|
|
487
|
+
* @returns {Object} Summary object
|
|
488
|
+
*/
|
|
489
|
+
buildSummary(findings) {
|
|
490
|
+
const summary = {
|
|
491
|
+
total: findings.length,
|
|
492
|
+
highSeverity: 0,
|
|
493
|
+
mediumSeverity: 0,
|
|
494
|
+
lowSeverity: 0,
|
|
495
|
+
byType: {}
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
for (const finding of findings) {
|
|
499
|
+
// Count by severity
|
|
500
|
+
if (finding.severity === 'high') {
|
|
501
|
+
summary.highSeverity++;
|
|
502
|
+
} else if (finding.severity === 'medium') {
|
|
503
|
+
summary.mediumSeverity++;
|
|
504
|
+
} else {
|
|
505
|
+
summary.lowSeverity++;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Count by type
|
|
509
|
+
summary.byType[finding.type] = (summary.byType[finding.type] || 0) + 1;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return summary;
|
|
513
|
+
}
|
|
514
|
+
}
|