@shakecodeslikecray/whiterose 1.0.12 → 2.0.0
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/cli/index.js +937 -24
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +145 -1
- package/dist/index.js +160 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -702,7 +702,7 @@ ${pass.whatToFind.map((w) => `- ${w}`).join("\n")}
|
|
|
702
702
|
${pass.traceInstructions}
|
|
703
703
|
|
|
704
704
|
## ENTRY POINTS TO START FROM
|
|
705
|
-
${pass.entryPointPatterns.map((
|
|
705
|
+
${pass.entryPointPatterns.map((p10) => `- ${p10}`).join("\n")}
|
|
706
706
|
|
|
707
707
|
## EXAMPLE VULNERABILITY
|
|
708
708
|
This is what a real finding looks like for this pass:
|
|
@@ -1242,12 +1242,12 @@ ${entryPointsSection}
|
|
|
1242
1242
|
${staticSection}
|
|
1243
1243
|
## WHAT TO LOOK FOR
|
|
1244
1244
|
|
|
1245
|
-
${pass.searchPatterns.map((
|
|
1245
|
+
${pass.searchPatterns.map((p10) => `- ${p10}`).join("\n")}
|
|
1246
1246
|
|
|
1247
1247
|
## SEARCH STRATEGY
|
|
1248
1248
|
|
|
1249
1249
|
Use these grep patterns to find potential issues:
|
|
1250
|
-
${pass.grepPatterns.map((
|
|
1250
|
+
${pass.grepPatterns.map((p10) => `\`${p10}\``).join("\n")}
|
|
1251
1251
|
|
|
1252
1252
|
## METHODOLOGY
|
|
1253
1253
|
|
|
@@ -1314,6 +1314,67 @@ IMPORTANT:
|
|
|
1314
1314
|
- If unsure, report it with confidence="low"
|
|
1315
1315
|
- Aim for thoroughness - finding 10 potential issues is better than finding 0 confirmed bugs`;
|
|
1316
1316
|
}
|
|
1317
|
+
function buildCustomPassPrompt(customPass, ctx) {
|
|
1318
|
+
const { projectType, framework, language, totalFiles } = ctx;
|
|
1319
|
+
const staticSection = ctx.staticFindings?.length ? `
|
|
1320
|
+
## STATIC ANALYSIS SIGNALS
|
|
1321
|
+
${ctx.staticFindings.slice(0, 15).map((f) => `- ${f.tool}: ${f.file}:${f.line} - ${f.message}`).join("\n")}
|
|
1322
|
+
` : "";
|
|
1323
|
+
return `You are a security specialist performing a TARGETED analysis: ${customPass.id.toUpperCase()}.
|
|
1324
|
+
|
|
1325
|
+
## YOUR SINGLE MISSION
|
|
1326
|
+
${customPass.description}
|
|
1327
|
+
|
|
1328
|
+
## PROJECT CONTEXT
|
|
1329
|
+
- Type: ${projectType}
|
|
1330
|
+
- Framework: ${framework || "Unknown"}
|
|
1331
|
+
- Language: ${language}
|
|
1332
|
+
- Size: ${totalFiles} files
|
|
1333
|
+
${staticSection}
|
|
1334
|
+
## METHODOLOGY
|
|
1335
|
+
|
|
1336
|
+
1. Search the codebase for patterns relevant to: ${customPass.category}
|
|
1337
|
+
2. Read files that match and analyze for the specific issues described above
|
|
1338
|
+
3. Trace data flow to confirm the issue is real
|
|
1339
|
+
4. Report all confirmed and suspected issues
|
|
1340
|
+
|
|
1341
|
+
## REPORTING FORMAT
|
|
1342
|
+
|
|
1343
|
+
When you find an issue:
|
|
1344
|
+
|
|
1345
|
+
<json>
|
|
1346
|
+
{
|
|
1347
|
+
"type": "bug",
|
|
1348
|
+
"data": {
|
|
1349
|
+
"file": "src/path/to/file.ts",
|
|
1350
|
+
"line": 42,
|
|
1351
|
+
"endLine": 45,
|
|
1352
|
+
"title": "Short description of the issue",
|
|
1353
|
+
"description": "Explanation of why this is problematic and potential impact.",
|
|
1354
|
+
"kind": "bug|smell",
|
|
1355
|
+
"category": "${customPass.category}",
|
|
1356
|
+
"severity": "critical|high|medium|low",
|
|
1357
|
+
"confidence": "high|medium|low",
|
|
1358
|
+
"evidence": [
|
|
1359
|
+
"Evidence point 1",
|
|
1360
|
+
"Evidence point 2"
|
|
1361
|
+
],
|
|
1362
|
+
"suggestedFix": "Optional: how to fix it"
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
</json>
|
|
1366
|
+
|
|
1367
|
+
Progress updates:
|
|
1368
|
+
###SCANNING:path/to/file.ts
|
|
1369
|
+
|
|
1370
|
+
When done:
|
|
1371
|
+
###COMPLETE
|
|
1372
|
+
|
|
1373
|
+
## BEGIN
|
|
1374
|
+
|
|
1375
|
+
Search for ${customPass.id} patterns. Read files that match. Report issues as you find them.
|
|
1376
|
+
This is a TARGETED pass - focus exclusively on: ${customPass.description}`;
|
|
1377
|
+
}
|
|
1317
1378
|
|
|
1318
1379
|
// src/providers/prompts/constants.ts
|
|
1319
1380
|
var PROJECT_TYPES_PROMPT = `PROJECT TYPE OPTIONS (pick the best fit):
|
|
@@ -1736,7 +1797,7 @@ var SCAN_PASSES = [
|
|
|
1736
1797
|
}
|
|
1737
1798
|
];
|
|
1738
1799
|
function getPassConfig(name) {
|
|
1739
|
-
return SCAN_PASSES.find((
|
|
1800
|
+
return SCAN_PASSES.find((p10) => p10.name === name);
|
|
1740
1801
|
}
|
|
1741
1802
|
|
|
1742
1803
|
// src/core/flow-analyzer.ts
|
|
@@ -2226,7 +2287,7 @@ const user = await User.create(req.body); // No validation! role:'admin' accept
|
|
|
2226
2287
|
}
|
|
2227
2288
|
];
|
|
2228
2289
|
function getFlowPassConfig(name) {
|
|
2229
|
-
return FLOW_PASSES.find((
|
|
2290
|
+
return FLOW_PASSES.find((p10) => p10.name === name);
|
|
2230
2291
|
}
|
|
2231
2292
|
|
|
2232
2293
|
// src/core/utils.ts
|
|
@@ -2279,38 +2340,68 @@ var CoreScanner = class {
|
|
|
2279
2340
|
const startTime = Date.now();
|
|
2280
2341
|
this.passErrors = [];
|
|
2281
2342
|
const pipeline = getFullAnalysisPipeline();
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
const
|
|
2343
|
+
let unitPasses = pipeline[0].passes;
|
|
2344
|
+
let integrationPasses = pipeline[1].passes;
|
|
2345
|
+
let e2ePasses = pipeline[2].passes;
|
|
2346
|
+
const rp = context.riskProfile;
|
|
2347
|
+
const skippedNames = new Set(rp?.skippedPasses.map((s) => s.passName) || []);
|
|
2348
|
+
const customUnitPasses = rp?.customPasses.filter((p10) => p10.phase === "unit") || [];
|
|
2349
|
+
const customIntegrationPasses = rp?.customPasses.filter((p10) => p10.phase === "integration") || [];
|
|
2350
|
+
const customE2EPasses = rp?.customPasses.filter((p10) => p10.phase === "e2e") || [];
|
|
2351
|
+
if (skippedNames.size > 0) {
|
|
2352
|
+
unitPasses = unitPasses.filter((p10) => !skippedNames.has(p10));
|
|
2353
|
+
integrationPasses = integrationPasses.filter((p10) => !skippedNames.has(p10));
|
|
2354
|
+
e2ePasses = e2ePasses.filter((p10) => !skippedNames.has(p10));
|
|
2355
|
+
}
|
|
2356
|
+
const totalStandard = unitPasses.length + integrationPasses.length + e2ePasses.length;
|
|
2357
|
+
const totalCustom = customUnitPasses.length + customIntegrationPasses.length + customE2EPasses.length;
|
|
2358
|
+
const totalPasses = totalStandard + totalCustom;
|
|
2286
2359
|
this.report(`
|
|
2287
2360
|
\u2550\u2550\u2550\u2550 CORE SCANNER (PIPELINE MODE) \u2550\u2550\u2550\u2550`);
|
|
2288
2361
|
this.report(` Provider: ${this.executor.name}`);
|
|
2289
|
-
this.report(` Passes: ${totalPasses} (${unitPasses.length} unit \u2192 ${integrationPasses.length} integration \u2192 ${e2ePasses.length} E2E)`);
|
|
2362
|
+
this.report(` Passes: ${totalPasses} (${unitPasses.length + customUnitPasses.length} unit \u2192 ${integrationPasses.length + customIntegrationPasses.length} integration \u2192 ${e2ePasses.length + customE2EPasses.length} E2E)`);
|
|
2363
|
+
if (totalCustom > 0) {
|
|
2364
|
+
this.report(` Risk profile: ${totalCustom} custom passes, ${skippedNames.size} skipped`);
|
|
2365
|
+
}
|
|
2290
2366
|
this.report(` Findings flow: Unit \u2192 Integration \u2192 E2E`);
|
|
2291
2367
|
let globalBugIndex = 0;
|
|
2292
2368
|
this.report(`
|
|
2293
2369
|
\u2550\u2550\u2550\u2550 PHASE 1: UNIT ANALYSIS \u2550\u2550\u2550\u2550`);
|
|
2294
2370
|
this.report(` Looking for: injection, null refs, auth bypass, etc.`);
|
|
2371
|
+
const customUnitJobs = this.buildCustomPassJobs(context, customUnitPasses);
|
|
2372
|
+
const customUnitFindings = await this.runPassBatch(customUnitJobs, cwd, context.files, globalBugIndex);
|
|
2373
|
+
this.boostCustomPassConfidence(customUnitFindings);
|
|
2374
|
+
globalBugIndex += customUnitFindings.length;
|
|
2295
2375
|
const unitJobs = this.buildUnitPassJobs(context, unitPasses);
|
|
2296
|
-
const
|
|
2297
|
-
globalBugIndex +=
|
|
2376
|
+
const standardUnitFindings = await this.runPassBatch(unitJobs, cwd, context.files, globalBugIndex);
|
|
2377
|
+
globalBugIndex += standardUnitFindings.length;
|
|
2378
|
+
const unitFindings = [...customUnitFindings, ...standardUnitFindings];
|
|
2298
2379
|
this.report(` Phase 1 complete: ${unitFindings.length} findings`);
|
|
2299
2380
|
this.report(`
|
|
2300
2381
|
\u2550\u2550\u2550\u2550 PHASE 2: INTEGRATION ANALYSIS \u2550\u2550\u2550\u2550`);
|
|
2301
2382
|
this.report(` Building on ${unitFindings.length} unit findings`);
|
|
2302
2383
|
this.report(` Looking for: auth flows, data flows, trust boundaries`);
|
|
2384
|
+
const customIntJobs = this.buildCustomPassJobs(context, customIntegrationPasses);
|
|
2385
|
+
const customIntFindings = await this.runPassBatch(customIntJobs, cwd, context.files, globalBugIndex);
|
|
2386
|
+
this.boostCustomPassConfidence(customIntFindings);
|
|
2387
|
+
globalBugIndex += customIntFindings.length;
|
|
2303
2388
|
const integrationJobs = this.buildIntegrationPassJobs(context, integrationPasses, unitFindings);
|
|
2304
|
-
const
|
|
2305
|
-
globalBugIndex +=
|
|
2389
|
+
const standardIntFindings = await this.runPassBatch(integrationJobs, cwd, context.files, globalBugIndex);
|
|
2390
|
+
globalBugIndex += standardIntFindings.length;
|
|
2391
|
+
const integrationFindings = [...customIntFindings, ...standardIntFindings];
|
|
2306
2392
|
this.report(` Phase 2 complete: ${integrationFindings.length} findings`);
|
|
2307
2393
|
this.report(`
|
|
2308
2394
|
\u2550\u2550\u2550\u2550 PHASE 3: E2E ANALYSIS \u2550\u2550\u2550\u2550`);
|
|
2309
2395
|
this.report(` Building on ${unitFindings.length} unit + ${integrationFindings.length} integration findings`);
|
|
2310
2396
|
this.report(` Looking for: attack chains, privilege escalation, session bugs`);
|
|
2397
|
+
const customE2EJobs = this.buildCustomPassJobs(context, customE2EPasses);
|
|
2398
|
+
const customE2EFindings = await this.runPassBatch(customE2EJobs, cwd, context.files, globalBugIndex);
|
|
2399
|
+
this.boostCustomPassConfidence(customE2EFindings);
|
|
2400
|
+
globalBugIndex += customE2EFindings.length;
|
|
2311
2401
|
const allPreviousFindings = [...unitFindings, ...integrationFindings];
|
|
2312
2402
|
const e2eJobs = this.buildE2EPassJobs(context, e2ePasses, allPreviousFindings);
|
|
2313
|
-
const
|
|
2403
|
+
const standardE2EFindings = await this.runPassBatch(e2eJobs, cwd, context.files, globalBugIndex);
|
|
2404
|
+
const e2eFindings = [...customE2EFindings, ...standardE2EFindings];
|
|
2314
2405
|
this.report(` Phase 3 complete: ${e2eFindings.length} findings`);
|
|
2315
2406
|
this.report(`
|
|
2316
2407
|
\u2550\u2550\u2550\u2550 POST-PROCESSING \u2550\u2550\u2550\u2550`);
|
|
@@ -2344,7 +2435,7 @@ var CoreScanner = class {
|
|
|
2344
2435
|
const batchNum = Math.floor(i / this.config.batchSize) + 1;
|
|
2345
2436
|
const totalBatches = Math.ceil(passes.length / this.config.batchSize);
|
|
2346
2437
|
this.report(`
|
|
2347
|
-
[Batch ${batchNum}/${totalBatches}] ${batch.map((
|
|
2438
|
+
[Batch ${batchNum}/${totalBatches}] ${batch.map((p10) => p10.name).join(", ")}`);
|
|
2348
2439
|
const batchPromises = batch.map(async (pass) => {
|
|
2349
2440
|
this.progress.onPassStart?.(pass.name);
|
|
2350
2441
|
try {
|
|
@@ -2616,6 +2707,37 @@ var CoreScanner = class {
|
|
|
2616
2707
|
}
|
|
2617
2708
|
return jobs;
|
|
2618
2709
|
}
|
|
2710
|
+
/**
|
|
2711
|
+
* Build custom pass jobs from risk profile
|
|
2712
|
+
*/
|
|
2713
|
+
buildCustomPassJobs(context, customPasses) {
|
|
2714
|
+
const jobs = [];
|
|
2715
|
+
const { understanding, staticResults } = context;
|
|
2716
|
+
for (const customPass of customPasses) {
|
|
2717
|
+
jobs.push({
|
|
2718
|
+
name: customPass.id,
|
|
2719
|
+
prompt: buildCustomPassPrompt(customPass, {
|
|
2720
|
+
pass: { name: customPass.id, category: customPass.category, description: customPass.description},
|
|
2721
|
+
projectType: understanding.summary.type,
|
|
2722
|
+
framework: understanding.summary.framework || "",
|
|
2723
|
+
language: understanding.summary.language,
|
|
2724
|
+
totalFiles: understanding.structure.totalFiles,
|
|
2725
|
+
staticFindings: staticResults
|
|
2726
|
+
})
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
return jobs;
|
|
2730
|
+
}
|
|
2731
|
+
/**
|
|
2732
|
+
* Boost confidence for custom pass findings (targeted = higher signal-to-noise)
|
|
2733
|
+
*/
|
|
2734
|
+
boostCustomPassConfidence(bugs) {
|
|
2735
|
+
for (const bug of bugs) {
|
|
2736
|
+
if (bug.confidence.overall === "medium") {
|
|
2737
|
+
bug.confidence.overall = "high";
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2619
2741
|
buildQuickScanPrompt(context) {
|
|
2620
2742
|
const { understanding, staticResults, files } = context;
|
|
2621
2743
|
const staticSignals = staticResults.length > 0 ? `
|
|
@@ -3814,6 +3936,33 @@ var CodebaseUnderstanding = z.object({
|
|
|
3814
3936
|
packages: z.array(z.string()).optional()
|
|
3815
3937
|
})
|
|
3816
3938
|
});
|
|
3939
|
+
var DomainType = z.string();
|
|
3940
|
+
var RiskLevel = z.enum(["critical", "high", "medium"]);
|
|
3941
|
+
var HotPath = z.object({
|
|
3942
|
+
file: z.string(),
|
|
3943
|
+
reason: z.string(),
|
|
3944
|
+
riskLevel: RiskLevel
|
|
3945
|
+
});
|
|
3946
|
+
var CustomPassConfig = z.object({
|
|
3947
|
+
id: z.string(),
|
|
3948
|
+
phase: z.enum(["unit", "integration", "e2e"]),
|
|
3949
|
+
category: z.string(),
|
|
3950
|
+
description: z.string()
|
|
3951
|
+
});
|
|
3952
|
+
var SkipReason = z.object({
|
|
3953
|
+
passName: z.string(),
|
|
3954
|
+
reason: z.string()
|
|
3955
|
+
});
|
|
3956
|
+
var RiskProfile = z.object({
|
|
3957
|
+
version: z.string(),
|
|
3958
|
+
generatedAt: z.string().datetime(),
|
|
3959
|
+
domains: z.array(DomainType),
|
|
3960
|
+
sensitiveDataTypes: z.array(z.string()),
|
|
3961
|
+
externalDependencies: z.array(z.string()),
|
|
3962
|
+
hotPaths: z.array(HotPath),
|
|
3963
|
+
customPasses: z.array(CustomPassConfig),
|
|
3964
|
+
skippedPasses: z.array(SkipReason)
|
|
3965
|
+
});
|
|
3817
3966
|
var FileHash = z.object({
|
|
3818
3967
|
path: z.string(),
|
|
3819
3968
|
hash: z.string(),
|
|
@@ -3858,8 +4007,6 @@ z.object({
|
|
|
3858
4007
|
summary: ScanSummary,
|
|
3859
4008
|
meta: ScanMeta.optional()
|
|
3860
4009
|
});
|
|
3861
|
-
|
|
3862
|
-
// src/core/config.ts
|
|
3863
4010
|
async function loadConfig(cwd) {
|
|
3864
4011
|
const configPath = join(cwd, ".whiterose", "config.yml");
|
|
3865
4012
|
if (!existsSync(configPath)) {
|
|
@@ -5344,7 +5491,7 @@ function functionExists(content, functionName) {
|
|
|
5344
5491
|
new RegExp(`\\blet\\s+${escaped}\\s*=\\s*`),
|
|
5345
5492
|
new RegExp(`\\b${escaped}\\s*\\(`)
|
|
5346
5493
|
];
|
|
5347
|
-
return patterns.some((
|
|
5494
|
+
return patterns.some((p10) => p10.test(content));
|
|
5348
5495
|
}
|
|
5349
5496
|
function makeIntentBug(cwd, contract, reason) {
|
|
5350
5497
|
return {
|
|
@@ -5611,6 +5758,25 @@ async function scanCommand(paths, options) {
|
|
|
5611
5758
|
p3.log.info(`Incremental scan: ${filesToScan.length} changed files`);
|
|
5612
5759
|
}
|
|
5613
5760
|
}
|
|
5761
|
+
let riskProfile;
|
|
5762
|
+
if (!isQuickScan) {
|
|
5763
|
+
const riskProfilePath = join(whiterosePath, "risk-profile.json");
|
|
5764
|
+
if (existsSync(riskProfilePath)) {
|
|
5765
|
+
try {
|
|
5766
|
+
const rpContent = JSON.parse(readFileSync(riskProfilePath, "utf-8"));
|
|
5767
|
+
const rpResult = RiskProfile.safeParse(rpContent);
|
|
5768
|
+
if (rpResult.success) {
|
|
5769
|
+
riskProfile = rpResult.data;
|
|
5770
|
+
if (!isQuiet) {
|
|
5771
|
+
const customCount = riskProfile.customPasses.length;
|
|
5772
|
+
const skipCount = riskProfile.skippedPasses.length;
|
|
5773
|
+
console.log(chalk3.dim("\u2502") + ` Risk profile: ${chalk3.cyan(customCount)} custom passes, ${chalk3.cyan(skipCount)} skipped`);
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
5776
|
+
} catch {
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
}
|
|
5614
5780
|
let staticResults;
|
|
5615
5781
|
if (!isQuiet) {
|
|
5616
5782
|
console.log(chalk3.dim("\u2502") + " Running static analysis (tsc, eslint)...");
|
|
@@ -5666,7 +5832,8 @@ async function scanCommand(paths, options) {
|
|
|
5666
5832
|
files: filesToScan,
|
|
5667
5833
|
understanding,
|
|
5668
5834
|
staticResults: staticResults || [],
|
|
5669
|
-
config
|
|
5835
|
+
config,
|
|
5836
|
+
riskProfile
|
|
5670
5837
|
});
|
|
5671
5838
|
}
|
|
5672
5839
|
console.log();
|
|
@@ -5700,7 +5867,8 @@ async function scanCommand(paths, options) {
|
|
|
5700
5867
|
files: filesToScan,
|
|
5701
5868
|
understanding,
|
|
5702
5869
|
staticResults: staticResults || [],
|
|
5703
|
-
config
|
|
5870
|
+
config,
|
|
5871
|
+
riskProfile
|
|
5704
5872
|
});
|
|
5705
5873
|
}
|
|
5706
5874
|
if (options.ci && scanner.hasPassErrors()) {
|
|
@@ -5861,10 +6029,10 @@ async function scanCommand(paths, options) {
|
|
|
5861
6029
|
} else {
|
|
5862
6030
|
const outputDir = join(cwd, "whiterose-output");
|
|
5863
6031
|
const reportsDir = join(whiterosePath, "reports");
|
|
5864
|
-
const { mkdirSync:
|
|
6032
|
+
const { mkdirSync: mkdirSync5 } = await import('fs');
|
|
5865
6033
|
try {
|
|
5866
|
-
|
|
5867
|
-
|
|
6034
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
6035
|
+
mkdirSync5(reportsDir, { recursive: true });
|
|
5868
6036
|
} catch {
|
|
5869
6037
|
}
|
|
5870
6038
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -7934,6 +8102,750 @@ async function clearCommand(options) {
|
|
|
7934
8102
|
p3.outro(chalk3.green("Done"));
|
|
7935
8103
|
}
|
|
7936
8104
|
|
|
8105
|
+
// src/core/risk-profiler.ts
|
|
8106
|
+
var DOMAINS = {
|
|
8107
|
+
// ═══════════════════════════════════════════════════════════
|
|
8108
|
+
// IDENTITY & ACCESS
|
|
8109
|
+
// ═══════════════════════════════════════════════════════════
|
|
8110
|
+
"auth": {
|
|
8111
|
+
keywords: ["auth", "login", "signin", "sign-in", "signup", "sign-up", "register", "password", "credential", "authenticate", "identity"],
|
|
8112
|
+
knownDeps: ["bcrypt", "bcryptjs", "argon2", "passport", "next-auth", "@auth/core", "lucia", "better-auth", "@supabase/auth-helpers-nextjs"],
|
|
8113
|
+
depSegments: ["auth", "login", "credential", "identity"],
|
|
8114
|
+
pathPatterns: [/auth[/\\]/, /login[/\\]/, /signin[/\\]/, /signup[/\\]/, /register[/\\]/],
|
|
8115
|
+
passes: [
|
|
8116
|
+
{ id: "credential-handling", phase: "unit", category: "auth-bypass", description: "Check credential handling safety. Look for: passwords stored in plaintext or weak hashes (MD5, SHA1), missing salt in hashing, timing-safe comparison not used for password/token verification, credentials logged or included in error messages." },
|
|
8117
|
+
{ id: "brute-force-protection", phase: "integration", category: "auth-bypass", description: "Check for brute force protection on authentication endpoints. Look for: login endpoints without rate limiting, missing account lockout after failed attempts, no CAPTCHA or progressive delays, enumeration possible via different error messages for valid vs invalid usernames." }
|
|
8118
|
+
]
|
|
8119
|
+
},
|
|
8120
|
+
"session": {
|
|
8121
|
+
keywords: ["session", "jwt", "token", "cookie", "refresh token"],
|
|
8122
|
+
knownDeps: ["jsonwebtoken", "jose", "express-session", "cookie-session", "iron-session", "koa-session", "fastify-secure-session", "connect-redis", "connect-mongo"],
|
|
8123
|
+
depSegments: ["session", "jwt", "token"],
|
|
8124
|
+
pathPatterns: [/sessions?[/\\]/, /tokens?[/\\]/],
|
|
8125
|
+
passes: [
|
|
8126
|
+
{ id: "session-fixation", phase: "integration", category: "auth-bypass", description: "Check for session fixation vulnerabilities. Look for: session ID not regenerated after login, pre-authentication session IDs reused after authentication, session tokens set before credential verification, session cookies missing Secure/HttpOnly/SameSite flags." },
|
|
8127
|
+
{ id: "token-lifecycle", phase: "e2e", category: "auth-bypass", description: "Trace full token lifecycle from creation to invalidation. Look for: tokens that never expire, logout not invalidating server-side sessions, refresh tokens not rotated on use, JWT with weak or missing signature verification, tokens stored in localStorage (XSS accessible)." }
|
|
8128
|
+
]
|
|
8129
|
+
},
|
|
8130
|
+
"oauth": {
|
|
8131
|
+
keywords: ["oauth", "openid", "oidc", "sso", "saml", "single sign-on", "social login", "federated"],
|
|
8132
|
+
knownDeps: ["oauth2-server", "openid-client", "grant", "simple-oauth2", "passport-google-oauth20", "passport-github2", "passport-facebook", "@auth0/nextjs-auth0", "next-auth"],
|
|
8133
|
+
depSegments: ["oauth", "openid", "oidc", "saml", "sso"],
|
|
8134
|
+
pathPatterns: [/oauth[/\\]/, /sso[/\\]/, /openid[/\\]/],
|
|
8135
|
+
passes: [
|
|
8136
|
+
{ id: "oauth-redirect-validation", phase: "integration", category: "auth-bypass", description: "Check OAuth redirect URI validation. Look for: redirect_uri not validated against allowlist, partial matching allowing subdomain takeover (example.com.evil.com), open redirects in OAuth callback flow, missing state parameter for CSRF protection, PKCE not implemented for public clients." }
|
|
8137
|
+
]
|
|
8138
|
+
},
|
|
8139
|
+
"access-control": {
|
|
8140
|
+
keywords: ["authorization", "rbac", "role", "permission", "acl", "access control", "privilege", "policy", "guard", "middleware"],
|
|
8141
|
+
knownDeps: ["casl", "@casl/ability", "accesscontrol", "casbin", "node-casbin", "role-acl"],
|
|
8142
|
+
depSegments: ["rbac", "acl", "permission", "policy", "guard"],
|
|
8143
|
+
pathPatterns: [/permissions?[/\\]/, /roles?[/\\]/, /policies[/\\]/, /guards?[/\\]/, /middleware[/\\]/, /authorization[/\\]/],
|
|
8144
|
+
passes: [
|
|
8145
|
+
{ id: "horizontal-access", phase: "integration", category: "auth-bypass", description: "Check for horizontal privilege escalation (IDOR). Look for: resource access using user-supplied IDs without ownership verification, missing tenant/user scoping on database queries, API endpoints that accept arbitrary user/resource IDs without checking the requester owns them." },
|
|
8146
|
+
{ id: "vertical-escalation", phase: "e2e", category: "auth-bypass", description: "Check for vertical privilege escalation. Look for: role field modifiable via mass assignment, role checks using client-supplied data, admin endpoints with inconsistent permission checks across layers, permission cached and not re-validated after role change." }
|
|
8147
|
+
]
|
|
8148
|
+
},
|
|
8149
|
+
"multi-tenancy": {
|
|
8150
|
+
keywords: ["tenant", "multi-tenant", "organization", "workspace", "team", "namespace"],
|
|
8151
|
+
knownDeps: [],
|
|
8152
|
+
depSegments: ["tenant", "multitenancy"],
|
|
8153
|
+
pathPatterns: [/tenants?[/\\]/, /organizations?[/\\]/, /workspaces?[/\\]/],
|
|
8154
|
+
passes: [
|
|
8155
|
+
{ id: "tenant-isolation", phase: "integration", category: "auth-bypass", description: "Check tenant data isolation. Look for: database queries without tenant ID filter, cross-tenant data leakage via shared caches, tenant ID from user input instead of authenticated session, missing tenant context in background jobs/queues, shared resources without tenant scoping." }
|
|
8156
|
+
]
|
|
8157
|
+
},
|
|
8158
|
+
"mfa": {
|
|
8159
|
+
keywords: ["mfa", "two-factor", "2fa", "totp", "otp", "authenticator", "verification code"],
|
|
8160
|
+
knownDeps: ["otplib", "speakeasy", "notp", "node-2fa"],
|
|
8161
|
+
depSegments: ["otp", "totp", "mfa", "2fa"],
|
|
8162
|
+
pathPatterns: [/mfa[/\\]/, /2fa[/\\]/, /otp[/\\]/, /verification[/\\]/],
|
|
8163
|
+
passes: [
|
|
8164
|
+
{ id: "mfa-bypass", phase: "integration", category: "auth-bypass", description: "Check for MFA bypass vulnerabilities. Look for: MFA step skippable by directly accessing post-MFA endpoints, OTP codes not rate-limited (brute-forceable), backup codes not single-use, MFA enrollment not required for sensitive operations, TOTP window too large." }
|
|
8165
|
+
]
|
|
8166
|
+
},
|
|
8167
|
+
// ═══════════════════════════════════════════════════════════
|
|
8168
|
+
// DATA LAYER
|
|
8169
|
+
// ═══════════════════════════════════════════════════════════
|
|
8170
|
+
"database": {
|
|
8171
|
+
keywords: ["database", "sql", "nosql", "orm", "migration", "repository", "persistence", "data layer", "data store"],
|
|
8172
|
+
knownDeps: ["prisma", "@prisma/client", "typeorm", "sequelize", "knex", "mongoose", "drizzle-orm", "pg", "mysql2", "better-sqlite3", "mongodb", "neo4j-driver", "cassandra-driver", "couchbase", "dynamodb", "@aws-sdk/client-dynamodb", "mikro-orm", "objection", "bookshelf", "waterline", "massive"],
|
|
8173
|
+
depSegments: ["db", "sql", "mongo", "postgres", "mysql", "sqlite", "dynamo", "cassandra", "couch", "neo4j", "orm"],
|
|
8174
|
+
pathPatterns: [/database[/\\]/, /db[/\\]/, /models?[/\\]/, /migrations?[/\\]/, /repositories?[/\\]/, /entities[/\\]/, /schemas?[/\\]/],
|
|
8175
|
+
passes: [
|
|
8176
|
+
{ id: "query-safety", phase: "unit", category: "injection", description: "Check for unsafe database query construction. Look for: string concatenation/interpolation in SQL queries, raw queries with user input, ORM methods that accept raw SQL fragments (e.g., Sequelize.literal, knex.raw), missing parameterization, NoSQL injection via object injection in MongoDB queries ($gt, $ne operators from user input)." },
|
|
8177
|
+
{ id: "mass-assignment", phase: "unit", category: "data-validation", description: "Check for mass assignment vulnerabilities. Look for: Object.assign(model, req.body), spread operator on user input into models, ORM create/update with unfiltered user input, missing field allowlists/denylists on model operations." }
|
|
8178
|
+
]
|
|
8179
|
+
},
|
|
8180
|
+
"cache": {
|
|
8181
|
+
keywords: ["cache", "caching", "memoize", "ttl", "invalidation"],
|
|
8182
|
+
knownDeps: ["ioredis", "redis", "node-cache", "lru-cache", "keyv", "catbox", "cache-manager", "memcached"],
|
|
8183
|
+
depSegments: ["cache", "redis", "memcache"],
|
|
8184
|
+
pathPatterns: [/cache[/\\]/, /caching[/\\]/],
|
|
8185
|
+
passes: [
|
|
8186
|
+
{ id: "cache-poisoning", phase: "integration", category: "injection", description: "Check for cache poisoning vulnerabilities. Look for: cache keys derived from user-controlled headers (Host, X-Forwarded-Host), cached responses including user-specific data served to other users, missing cache key scoping by user/session, cache stampede allowing timing attacks." }
|
|
8187
|
+
]
|
|
8188
|
+
},
|
|
8189
|
+
"search": {
|
|
8190
|
+
keywords: ["search", "full-text", "indexing", "autocomplete", "typeahead", "facet"],
|
|
8191
|
+
knownDeps: ["@elastic/elasticsearch", "algoliasearch", "meilisearch", "typesense", "lunr", "flexsearch", "solr-client", "minisearch"],
|
|
8192
|
+
depSegments: ["search", "elastic", "solr"],
|
|
8193
|
+
pathPatterns: [/search[/\\]/, /indexing[/\\]/],
|
|
8194
|
+
passes: [
|
|
8195
|
+
{ id: "search-injection", phase: "unit", category: "injection", description: "Check for search query injection. Look for: user input passed directly to Elasticsearch/Lucene query DSL, unescaped special characters in search queries (*, ?, ~), search queries that can access unauthorized indices or fields, autocomplete endpoints leaking private data." }
|
|
8196
|
+
]
|
|
8197
|
+
},
|
|
8198
|
+
"serialization": {
|
|
8199
|
+
keywords: ["serialize", "deserialize", "marshal", "unmarshal", "parse", "protobuf", "msgpack", "avro"],
|
|
8200
|
+
knownDeps: ["protobufjs", "msgpack5", "@msgpack/msgpack", "avsc", "bson", "flatbuffers", "js-yaml", "yaml", "fast-xml-parser", "class-transformer"],
|
|
8201
|
+
depSegments: ["serial", "proto", "msgpack", "avro", "marshal"],
|
|
8202
|
+
pathPatterns: [/serializ[/\\]/, /marshal[/\\]/, /proto[/\\]/],
|
|
8203
|
+
passes: [
|
|
8204
|
+
{ id: "deserialization-safety", phase: "unit", category: "injection", description: "Check for unsafe deserialization. Look for: eval() or Function() used for deserialization, YAML.load (unsafe) instead of YAML.safeLoad, JSON.parse of untrusted input without schema validation, prototype pollution via __proto__ or constructor.prototype in parsed objects, object spread of deserialized untrusted data." }
|
|
8205
|
+
]
|
|
8206
|
+
},
|
|
8207
|
+
"storage": {
|
|
8208
|
+
keywords: ["storage", "object storage", "blob", "bucket", "file system", "artifact"],
|
|
8209
|
+
knownDeps: ["@aws-sdk/client-s3", "@google-cloud/storage", "@azure/storage-blob", "minio", "cloudinary"],
|
|
8210
|
+
depSegments: ["storage", "s3", "blob", "bucket", "minio"],
|
|
8211
|
+
pathPatterns: [/storage[/\\]/, /blobs?[/\\]/, /buckets?[/\\]/, /artifacts?[/\\]/],
|
|
8212
|
+
passes: [
|
|
8213
|
+
{ id: "storage-access-control", phase: "integration", category: "auth-bypass", description: "Check cloud storage access controls. Look for: pre-signed URLs with excessive expiry times, public bucket/container access misconfiguration, missing ACL checks on file download endpoints, user-controlled storage paths allowing access to other users files, SSRF via storage URL construction." }
|
|
8214
|
+
]
|
|
8215
|
+
},
|
|
8216
|
+
// ═══════════════════════════════════════════════════════════
|
|
8217
|
+
// COMMUNICATION
|
|
8218
|
+
// ═══════════════════════════════════════════════════════════
|
|
8219
|
+
"api": {
|
|
8220
|
+
keywords: ["api", "rest", "endpoint", "route", "controller", "handler", "middleware", "http"],
|
|
8221
|
+
knownDeps: ["express", "fastify", "@nestjs/core", "koa", "hapi", "@hapi/hapi", "restify", "polka", "h3", "hono", "elysia", "@trpc/server", "tsoa", "routing-controllers"],
|
|
8222
|
+
depSegments: ["express", "fastify", "koa", "hapi", "server", "router", "http"],
|
|
8223
|
+
pathPatterns: [/api[/\\]/, /routes?[/\\]/, /controllers?[/\\]/, /endpoints?[/\\]/, /handlers?[/\\]/],
|
|
8224
|
+
passes: [
|
|
8225
|
+
{ id: "api-input-validation", phase: "unit", category: "data-validation", description: "Check API input validation completeness. Look for: req.body/query/params used without schema validation, numeric parameters parsed without NaN/range checks, array inputs without length limits, nested object inputs without depth limits, missing Content-Type enforcement." }
|
|
8226
|
+
]
|
|
8227
|
+
},
|
|
8228
|
+
"graphql": {
|
|
8229
|
+
keywords: ["graphql", "gql", "schema", "resolver", "mutation", "subscription"],
|
|
8230
|
+
knownDeps: ["graphql", "apollo-server", "@apollo/server", "apollo-server-express", "graphql-yoga", "mercurius", "type-graphql", "nexus", "pothos", "@graphql-tools/schema", "graphql-ws"],
|
|
8231
|
+
depSegments: ["graphql", "gql", "apollo"],
|
|
8232
|
+
pathPatterns: [/graphql[/\\]/, /gql[/\\]/, /resolvers?[/\\]/, /mutations?[/\\]/],
|
|
8233
|
+
passes: [
|
|
8234
|
+
{ id: "graphql-depth-limiting", phase: "unit", category: "resource-leak", description: "Check for GraphQL denial of service vectors. Look for: missing query depth limiting, missing query complexity analysis, no limit on batch/alias operations, introspection enabled in production, missing pagination limits on list fields, N+1 query patterns in resolvers without dataloader." },
|
|
8235
|
+
{ id: "graphql-authorization", phase: "integration", category: "auth-bypass", description: "Check GraphQL authorization. Look for: resolvers without auth directives/guards, sensitive fields queryable without permission checks, mutations accessible without proper role verification, subscription channels leaking data across users." }
|
|
8236
|
+
]
|
|
8237
|
+
},
|
|
8238
|
+
"grpc": {
|
|
8239
|
+
keywords: ["grpc", "protobuf", "rpc", "proto", "service definition"],
|
|
8240
|
+
knownDeps: ["@grpc/grpc-js", "@grpc/proto-loader", "grpc", "protobufjs", "ts-proto", "nice-grpc", "@connectrpc/connect"],
|
|
8241
|
+
depSegments: ["grpc", "proto"],
|
|
8242
|
+
pathPatterns: [/grpc[/\\]/, /protos?[/\\]/, /rpc[/\\]/],
|
|
8243
|
+
passes: [
|
|
8244
|
+
{ id: "grpc-validation", phase: "unit", category: "data-validation", description: "Check gRPC input validation. Look for: missing field validation beyond protobuf types (length, range, format), unary calls without deadline/timeout, streaming RPCs without message size limits, missing TLS/mTLS configuration, reflection service enabled in production." }
|
|
8245
|
+
]
|
|
8246
|
+
},
|
|
8247
|
+
"realtime": {
|
|
8248
|
+
keywords: ["realtime", "real-time", "websocket", "socket", "live", "sse", "server-sent events", "long-polling", "pub/sub", "pubsub"],
|
|
8249
|
+
knownDeps: ["socket.io", "ws", "pusher", "ably", "@supabase/realtime-js", "sockjs", "engine.io", "faye", "primus", "@fastify/websocket", "graphql-ws"],
|
|
8250
|
+
depSegments: ["socket", "realtime", "sse", "pubsub", "websocket"],
|
|
8251
|
+
pathPatterns: [/realtime[/\\]/, /websockets?[/\\]/, /sockets?[/\\]/, /live[/\\]/, /events?[/\\]/, /sse[/\\]/],
|
|
8252
|
+
passes: [
|
|
8253
|
+
{ id: "realtime-auth", phase: "integration", category: "auth-bypass", description: "Check realtime connection authentication. Look for: WebSocket/SSE connections accepted without auth verification, missing per-message authorization (auth checked only on connect), missing origin validation on WebSocket upgrade, event subscriptions allowing access to other users channels." },
|
|
8254
|
+
{ id: "event-injection", phase: "unit", category: "injection", description: "Check for event injection in realtime systems. Look for: user-controlled event names passed to emit/broadcast, unvalidated payloads broadcast to other clients, missing input sanitization on WebSocket messages, client able to trigger server-side events by name." }
|
|
8255
|
+
]
|
|
8256
|
+
},
|
|
8257
|
+
"email": {
|
|
8258
|
+
keywords: ["email sending", "smtp", "mailer", "newsletter", "transactional email", "email service", "email template", "send email", "email notification"],
|
|
8259
|
+
knownDeps: ["nodemailer", "@sendgrid/mail", "postmark", "mailgun-js", "@aws-sdk/client-ses", "resend", "react-email", "mjml", "email-templates", "bull-board"],
|
|
8260
|
+
depSegments: ["mail", "email", "smtp", "sendgrid", "postmark"],
|
|
8261
|
+
pathPatterns: [/emails?[/\\]/, /mailers?[/\\]/, /newsletters?[/\\]/],
|
|
8262
|
+
passes: [
|
|
8263
|
+
{ id: "email-header-injection", phase: "unit", category: "injection", description: "Check for email header injection. Look for: user input in email headers (To, From, CC, BCC, Subject) without newline stripping, template injection in email bodies (SSTI), user-controlled URLs in email templates (phishing), missing SPF/DKIM/DMARC consideration, email content including unsanitized user data." }
|
|
8264
|
+
]
|
|
8265
|
+
},
|
|
8266
|
+
"webhook": {
|
|
8267
|
+
keywords: ["webhook", "callback url", "event notification", "http callback"],
|
|
8268
|
+
knownDeps: ["svix", "@octokit/webhooks", "stripe"],
|
|
8269
|
+
depSegments: ["webhook", "callback"],
|
|
8270
|
+
pathPatterns: [/webhooks?[/\\]/, /callbacks?[/\\]/],
|
|
8271
|
+
passes: [
|
|
8272
|
+
{ id: "webhook-verification", phase: "integration", category: "auth-bypass", description: "Check webhook security. Look for: incoming webhooks without signature verification (HMAC, asymmetric), webhook endpoints without IP allowlisting or authentication, missing replay protection (no timestamp validation), SSRF via outgoing webhook URLs controlled by users, webhook payloads processed without validation." }
|
|
8273
|
+
]
|
|
8274
|
+
},
|
|
8275
|
+
"queue": {
|
|
8276
|
+
keywords: ["queue", "job", "worker", "background job", "task queue", "message broker", "consumer", "producer"],
|
|
8277
|
+
knownDeps: ["bull", "bullmq", "amqplib", "kafkajs", "@aws-sdk/client-sqs", "bee-queue", "agenda", "pg-boss", "faktory-worker", "celery-node", "rsmq"],
|
|
8278
|
+
depSegments: ["queue", "worker", "amqp", "kafka", "sqs", "rabbit", "bull"],
|
|
8279
|
+
pathPatterns: [/queues?[/\\]/, /jobs?[/\\]/, /workers?[/\\]/, /consumers?[/\\]/, /producers?[/\\]/],
|
|
8280
|
+
passes: [
|
|
8281
|
+
{ id: "queue-message-safety", phase: "unit", category: "injection", description: "Check queue message safety. Look for: deserialization of untrusted queue messages without schema validation, job data used in shell commands or SQL queries without sanitization, missing dead-letter queue handling, job retry without idempotency, user-controlled job types or queue names enabling unauthorized actions." }
|
|
8282
|
+
]
|
|
8283
|
+
},
|
|
8284
|
+
"notification": {
|
|
8285
|
+
keywords: ["notification", "push notification", "alert", "fcm", "apns"],
|
|
8286
|
+
knownDeps: ["firebase-admin", "web-push", "@aws-sdk/client-sns", "onesignal-node", "expo-server-sdk", "apn", "node-pushnotifications"],
|
|
8287
|
+
depSegments: ["notification", "push", "fcm", "apns", "sns"],
|
|
8288
|
+
pathPatterns: [/notifications?[/\\]/, /push[/\\]/, /alerts?[/\\]/],
|
|
8289
|
+
passes: [
|
|
8290
|
+
{ id: "notification-auth", phase: "integration", category: "auth-bypass", description: "Check notification subscription authorization. Look for: push notification subscriptions without user authentication, ability to subscribe to other users notification channels, notification payloads containing sensitive data sent to unverified devices, missing subscription ownership validation on unsubscribe." }
|
|
8291
|
+
]
|
|
8292
|
+
},
|
|
8293
|
+
// ═══════════════════════════════════════════════════════════
|
|
8294
|
+
// SECURITY INFRASTRUCTURE
|
|
8295
|
+
// ═══════════════════════════════════════════════════════════
|
|
8296
|
+
"crypto": {
|
|
8297
|
+
keywords: ["encrypt", "decrypt", "cipher", "hash", "hmac", "signing", "signature", "key management", "kms", "vault"],
|
|
8298
|
+
knownDeps: ["crypto-js", "tweetnacl", "libsodium-wrappers", "@aws-sdk/client-kms", "@google-cloud/kms", "node-forge", "openpgp", "sodium-native", "jose"],
|
|
8299
|
+
depSegments: ["crypto", "cipher", "encrypt", "hash", "kms", "vault", "sodium", "nacl"],
|
|
8300
|
+
pathPatterns: [/crypto[/\\]/, /encryption[/\\]/, /keys?[/\\]/, /vault[/\\]/, /certs?[/\\]/],
|
|
8301
|
+
passes: [
|
|
8302
|
+
{ id: "weak-crypto", phase: "unit", category: "secrets-exposure", description: "Check for weak cryptographic practices. Look for: MD5/SHA1 used for security purposes (password hashing, signatures), ECB mode encryption, static/hardcoded IVs or salts, Math.random() for security-sensitive values, insufficient key lengths (RSA < 2048, AES < 128), deprecated algorithms (DES, 3DES, RC4)." },
|
|
8303
|
+
{ id: "key-management", phase: "integration", category: "secrets-exposure", description: "Check key management practices. Look for: encryption keys hardcoded in source, keys stored in environment variables without KMS wrapping, missing key rotation mechanism, private keys committed to repository, symmetric keys shared across services, keys logged or included in error messages." }
|
|
8304
|
+
]
|
|
8305
|
+
},
|
|
8306
|
+
"cors": {
|
|
8307
|
+
keywords: ["cors", "cross-origin", "origin policy", "access-control-allow"],
|
|
8308
|
+
knownDeps: ["cors", "@fastify/cors", "@koa/cors"],
|
|
8309
|
+
depSegments: ["cors"],
|
|
8310
|
+
pathPatterns: [/cors[/\\]/],
|
|
8311
|
+
passes: [
|
|
8312
|
+
{ id: "cors-misconfiguration", phase: "unit", category: "auth-bypass", description: "Check CORS configuration safety. Look for: Access-Control-Allow-Origin set to wildcard (*) with credentials, origin reflected without allowlist validation, Access-Control-Allow-Methods too permissive, Access-Control-Max-Age too long, missing Vary: Origin header when origin is dynamic, regex-based origin matching with bypasses." }
|
|
8313
|
+
]
|
|
8314
|
+
},
|
|
8315
|
+
"csrf": {
|
|
8316
|
+
keywords: ["csrf", "xsrf", "cross-site request forgery", "anti-forgery", "csrf token"],
|
|
8317
|
+
knownDeps: ["csurf", "csrf-csrf", "lusca", "csrf"],
|
|
8318
|
+
depSegments: ["csrf", "xsrf"],
|
|
8319
|
+
pathPatterns: [/csrf[/\\]/],
|
|
8320
|
+
passes: [
|
|
8321
|
+
{ id: "csrf-protection", phase: "integration", category: "auth-bypass", description: "Check CSRF protection on state-changing endpoints. Look for: POST/PUT/DELETE endpoints without CSRF token validation, CSRF tokens not bound to user session, SameSite cookie attribute not set, token transmitted in URL query parameter (leaked via Referer), missing CSRF protection on logout/password-change/email-change endpoints." }
|
|
8322
|
+
]
|
|
8323
|
+
},
|
|
8324
|
+
"redirect": {
|
|
8325
|
+
keywords: ["redirect", "redirect_uri", "return_url", "next_url", "callback"],
|
|
8326
|
+
knownDeps: [],
|
|
8327
|
+
depSegments: [],
|
|
8328
|
+
pathPatterns: [],
|
|
8329
|
+
passes: [
|
|
8330
|
+
{ id: "open-redirect", phase: "unit", category: "injection", description: "Check for open redirect vulnerabilities. Look for: user-controlled redirect URLs without allowlist validation, URL parsing that can be bypassed (e.g., //evil.com, /\\evil.com, /%09/evil.com), redirect after login using unvalidated return_url parameter, JavaScript protocol in redirect URLs (javascript:)." }
|
|
8331
|
+
]
|
|
8332
|
+
},
|
|
8333
|
+
"rate-limiting": {
|
|
8334
|
+
keywords: ["rate limit", "throttle", "throttling", "rate-limit", "quota", "ddos protection"],
|
|
8335
|
+
knownDeps: ["express-rate-limit", "rate-limiter-flexible", "bottleneck", "@fastify/rate-limit", "limiter", "p-throttle", "p-limit"],
|
|
8336
|
+
depSegments: ["ratelimit", "throttle", "limiter"],
|
|
8337
|
+
pathPatterns: [/rate-limit[/\\]/, /throttl[/\\]/, /limiters?[/\\]/],
|
|
8338
|
+
passes: [
|
|
8339
|
+
{ id: "rate-limit-bypass", phase: "integration", category: "auth-bypass", description: "Check rate limiting effectiveness. Look for: rate limits based on IP only (bypassable via proxies/rotating IPs), missing rate limits on login/password-reset/OTP-verification endpoints, rate limit state in local memory (not shared across instances), X-Forwarded-For header trusted without validation, rate limit key not including API key or user ID." }
|
|
8340
|
+
]
|
|
8341
|
+
},
|
|
8342
|
+
"config": {
|
|
8343
|
+
keywords: ["config", "configuration", "environment", "settings", "env var", "dotenv"],
|
|
8344
|
+
knownDeps: ["dotenv", "config", "convict", "envalid", "@nestjs/config", "env-var", "nconf"],
|
|
8345
|
+
depSegments: ["config", "dotenv", "env"],
|
|
8346
|
+
pathPatterns: [/config[/\\]/, /settings[/\\]/],
|
|
8347
|
+
passes: [
|
|
8348
|
+
{ id: "config-exposure", phase: "unit", category: "secrets-exposure", description: "Check configuration security. Look for: default credentials in config files, debug mode enabled by default, secrets with fallback values in code, config files with overly permissive file permissions, environment-specific configs committed to repository, API keys or database URLs in client-side bundles." }
|
|
8349
|
+
]
|
|
8350
|
+
},
|
|
8351
|
+
"logging": {
|
|
8352
|
+
keywords: ["logging", "logger", "audit trail", "audit log", "access log", "activity log", "structured logging", "log aggregation"],
|
|
8353
|
+
knownDeps: ["winston", "pino", "bunyan", "morgan", "log4js", "signale", "tslog", "roarr", "loglevel"],
|
|
8354
|
+
depSegments: ["log", "logger", "logging", "audit"],
|
|
8355
|
+
pathPatterns: [/loggers?[/\\]/, /logging[/\\]/, /audit[/\\]/],
|
|
8356
|
+
passes: [
|
|
8357
|
+
{ id: "log-injection", phase: "unit", category: "injection", description: "Check for log injection and data leakage. Look for: user input written to logs without sanitization (newline injection for log forging), PII/passwords/tokens/credit-card numbers in log output, stack traces with sensitive info logged in production, ANSI escape sequences in log output (terminal injection)." }
|
|
8358
|
+
]
|
|
8359
|
+
},
|
|
8360
|
+
// ═══════════════════════════════════════════════════════════
|
|
8361
|
+
// CONTENT & FILES
|
|
8362
|
+
// ═══════════════════════════════════════════════════════════
|
|
8363
|
+
"file-upload": {
|
|
8364
|
+
keywords: ["upload", "file upload", "multipart", "attachment", "media upload", "image upload", "document upload"],
|
|
8365
|
+
knownDeps: ["multer", "busboy", "formidable", "multiparty", "@fastify/multipart", "express-fileupload"],
|
|
8366
|
+
depSegments: ["upload", "multipart"],
|
|
8367
|
+
pathPatterns: [/uploads?[/\\]/, /attachments?[/\\]/, /media[/\\]/],
|
|
8368
|
+
passes: [
|
|
8369
|
+
{ id: "upload-safety", phase: "unit", category: "injection", description: "Check file upload safety. Look for: file type validation using extension only (not magic bytes/MIME), no file size limits, uploaded files stored with user-controlled names (path traversal), executable files accepted (.php, .jsp, .aspx, .sh), no antivirus/malware scanning, files served from same origin as application." },
|
|
8370
|
+
{ id: "path-traversal-upload", phase: "integration", category: "injection", description: "Check for path traversal in file operations. Look for: user-controlled file paths passed to fs.readFile/writeFile/unlink, directory traversal via ../.. in filenames, symlink following in upload/download directories, zip slip in archive extraction, file path construction from user input without canonicalization." }
|
|
8371
|
+
]
|
|
8372
|
+
},
|
|
8373
|
+
"templating": {
|
|
8374
|
+
keywords: ["template", "render", "view engine", "handlebars", "mustache", "ejs", "pug", "jinja", "server-side rendering"],
|
|
8375
|
+
knownDeps: ["ejs", "pug", "handlebars", "mustache", "nunjucks", "eta", "liquidjs", "marko", "hbs", "consolidate"],
|
|
8376
|
+
depSegments: ["template", "ejs", "pug", "handlebars", "mustache", "nunjucks"],
|
|
8377
|
+
pathPatterns: [/templates?[/\\]/, /views?[/\\]/, /layouts?[/\\]/, /partials?[/\\]/],
|
|
8378
|
+
passes: [
|
|
8379
|
+
{ id: "ssti-check", phase: "unit", category: "injection", description: "Check for Server-Side Template Injection (SSTI). Look for: user input directly embedded in template strings before rendering, template compilation with user-controlled template source, sandbox escape in template engines, client-side template injection in Angular/Vue, eval-like constructs in template expressions." }
|
|
8380
|
+
]
|
|
8381
|
+
},
|
|
8382
|
+
"xml": {
|
|
8383
|
+
keywords: ["xml", "soap", "wsdl", "xslt", "xpath", "rss", "atom", "svg"],
|
|
8384
|
+
knownDeps: ["fast-xml-parser", "xml2js", "xmlbuilder", "xmldom", "libxmljs", "soap", "xpath", "sax", "cheerio"],
|
|
8385
|
+
depSegments: ["xml", "soap", "xslt", "xpath"],
|
|
8386
|
+
pathPatterns: [/xml[/\\]/, /soap[/\\]/],
|
|
8387
|
+
passes: [
|
|
8388
|
+
{ id: "xxe-check", phase: "unit", category: "injection", description: "Check for XML External Entity (XXE) injection. Look for: XML parsers with external entity processing enabled, DTD processing not disabled, XSLT processing of user-supplied stylesheets, XPath queries with user input, billion laughs/entity expansion not limited, SVG files parsed without sanitization." }
|
|
8389
|
+
]
|
|
8390
|
+
},
|
|
8391
|
+
"pdf-generation": {
|
|
8392
|
+
keywords: ["pdf", "document generation", "report generation", "invoice generation", "receipt"],
|
|
8393
|
+
knownDeps: ["puppeteer", "pdfkit", "jspdf", "pdfmake", "html-pdf", "wkhtmltopdf", "@react-pdf/renderer", "pdf-lib", "chromiumly"],
|
|
8394
|
+
depSegments: ["pdf", "pdfkit", "wkhtml"],
|
|
8395
|
+
pathPatterns: [/pdf[/\\]/, /reports?[/\\]/, /documents?[/\\]/],
|
|
8396
|
+
passes: [
|
|
8397
|
+
{ id: "pdf-ssrf", phase: "integration", category: "injection", description: "Check for SSRF via PDF generation. Look for: HTML-to-PDF converters rendering user-supplied HTML (can access internal URLs via <img>, <link>, <iframe>), user-controlled URLs in PDF generation (fetch internal resources), CSS @import/@font-face with user-controlled URLs, JavaScript execution in headless browser PDF generation." }
|
|
8398
|
+
]
|
|
8399
|
+
},
|
|
8400
|
+
"image-processing": {
|
|
8401
|
+
keywords: ["image processing", "image resize", "thumbnail", "image manipulation", "image conversion"],
|
|
8402
|
+
knownDeps: ["sharp", "jimp", "gm", "imagemagick", "image-size", "@squoosh/lib", "canvas", "pica", "blurhash"],
|
|
8403
|
+
depSegments: ["image", "sharp", "jimp", "gm", "imagemagick", "canvas", "thumbnail"],
|
|
8404
|
+
pathPatterns: [/images?[/\\]/, /thumbnails?[/\\]/, /processing[/\\]/],
|
|
8405
|
+
passes: [
|
|
8406
|
+
{ id: "image-processing-safety", phase: "unit", category: "injection", description: "Check image processing safety. Look for: ImageMagick/GraphicsMagick command injection via filenames, SVG processing enabling script execution, image bombs (decompression bombs) without size/dimension limits, EXIF data containing scripts not stripped, SSRF via image URL fetching, image processing of untrusted formats without sandboxing." }
|
|
8407
|
+
]
|
|
8408
|
+
},
|
|
8409
|
+
"cms": {
|
|
8410
|
+
keywords: ["cms", "content management", "headless cms", "blog", "article", "editorial"],
|
|
8411
|
+
knownDeps: ["strapi", "@strapi/strapi", "ghost", "keystone", "@keystone-6/core", "sanity", "contentful", "directus", "payload"],
|
|
8412
|
+
depSegments: ["cms", "strapi", "ghost", "keystone", "contentful", "sanity"],
|
|
8413
|
+
pathPatterns: [/cms[/\\]/, /content[/\\]/, /articles?[/\\]/, /posts?[/\\]/, /editorial[/\\]/],
|
|
8414
|
+
passes: [
|
|
8415
|
+
{ id: "cms-xss", phase: "unit", category: "injection", description: "Check for XSS in CMS content rendering. Look for: rich text/markdown rendered without sanitization (dangerouslySetInnerHTML, v-html), user-generated content displayed without HTML encoding, custom embed codes executed without sandboxing, WYSIWYG editor output rendered raw, missing Content-Security-Policy for user content pages." }
|
|
8416
|
+
]
|
|
8417
|
+
},
|
|
8418
|
+
// ═══════════════════════════════════════════════════════════
|
|
8419
|
+
// BUSINESS LOGIC
|
|
8420
|
+
// ═══════════════════════════════════════════════════════════
|
|
8421
|
+
"payments": {
|
|
8422
|
+
keywords: ["payment", "checkout", "billing", "charge", "refund", "invoice", "transaction", "payout", "settlement"],
|
|
8423
|
+
knownDeps: ["stripe", "@stripe/stripe-js", "@stripe/react-stripe-js", "braintree", "paypal-rest-sdk", "@paypal/checkout-server-sdk", "square", "adyen-api", "razorpay", "mollie-api-node", "coinbase-commerce-node", "paddle-sdk"],
|
|
8424
|
+
depSegments: ["payment", "pay", "billing", "checkout", "stripe", "braintree", "paypal", "razorpay"],
|
|
8425
|
+
pathPatterns: [/payments?[/\\]/, /billing[/\\]/, /checkout[/\\]/, /transactions?[/\\]/, /invoices?[/\\]/],
|
|
8426
|
+
passes: [
|
|
8427
|
+
{ id: "decimal-precision", phase: "unit", category: "logic-error", description: "Check for floating-point arithmetic on monetary values. Look for: Number type for prices/amounts (IEEE 754 precision loss), Math.round on currency calculations, division before multiplication causing rounding errors, missing use of integer cents or Decimal/BigNumber libraries, currency conversion with floating-point." },
|
|
8428
|
+
{ id: "idempotency-check", phase: "integration", category: "logic-error", description: "Verify payment endpoints use idempotency keys. Look for: POST endpoints that charge money without idempotency key validation, missing duplicate payment detection, retry logic that could double-charge, webhook handlers that reprocess already-completed payments." },
|
|
8429
|
+
{ id: "race-condition-financial", phase: "e2e", category: "concurrency", description: "Find race conditions in financial operations. Look for: balance check-then-debit without database transactions/locks, concurrent transfers that could overdraw, non-atomic read-modify-write on account balances, coupon/discount applied multiple times concurrently, parallel refund requests for same order." }
|
|
8430
|
+
]
|
|
8431
|
+
},
|
|
8432
|
+
"e-commerce": {
|
|
8433
|
+
keywords: ["e-commerce", "ecommerce", "shopping cart", "catalog", "product", "inventory", "order", "storefront", "marketplace"],
|
|
8434
|
+
knownDeps: ["shopify-api-node", "@shopify/shopify-api", "medusa-core", "saleor", "commercetools"],
|
|
8435
|
+
depSegments: ["shop", "commerce", "cart", "catalog", "inventory", "storefront"],
|
|
8436
|
+
pathPatterns: [/shop[/\\]/, /cart[/\\]/, /products?[/\\]/, /orders?[/\\]/, /catalog[/\\]/, /inventory[/\\]/],
|
|
8437
|
+
passes: [
|
|
8438
|
+
{ id: "price-manipulation", phase: "integration", category: "logic-error", description: "Check for price manipulation vulnerabilities. Look for: product price sent from client and used directly (not re-fetched from server), discount/coupon amount validated client-side only, total calculated on frontend and trusted by backend, negative quantity or price accepted, race condition between price check and order placement." },
|
|
8439
|
+
{ id: "inventory-race", phase: "e2e", category: "concurrency", description: "Check for inventory race conditions. Look for: stock check-then-decrement without atomic operation, concurrent purchases of last item in stock, overselling via parallel API requests, missing reservation/hold mechanism during checkout flow." }
|
|
8440
|
+
]
|
|
8441
|
+
},
|
|
8442
|
+
"subscription": {
|
|
8443
|
+
keywords: ["subscription", "recurring", "plan", "tier", "billing cycle", "trial", "upgrade", "downgrade"],
|
|
8444
|
+
knownDeps: [],
|
|
8445
|
+
depSegments: ["subscription", "billing", "plan"],
|
|
8446
|
+
pathPatterns: [/subscriptions?[/\\]/, /plans?[/\\]/, /tiers?[/\\]/],
|
|
8447
|
+
passes: [
|
|
8448
|
+
{ id: "subscription-bypass", phase: "integration", category: "auth-bypass", description: "Check for subscription/plan bypass. Look for: premium features accessible without active subscription check, plan limits enforced client-side only, trial period manipulation (re-registration), downgrade not revoking access to higher-tier features, subscription status cached and not re-verified." }
|
|
8449
|
+
]
|
|
8450
|
+
},
|
|
8451
|
+
"workflow": {
|
|
8452
|
+
keywords: ["workflow", "state machine", "pipeline", "approval", "process", "step", "transition"],
|
|
8453
|
+
knownDeps: ["xstate", "bull", "temporal-sdk", "@temporalio/client", "inngest", "trigger.dev"],
|
|
8454
|
+
depSegments: ["workflow", "state-machine", "pipeline", "xstate"],
|
|
8455
|
+
pathPatterns: [/workflows?[/\\]/, /pipelines?[/\\]/, /state-machines?[/\\]/, /processes?[/\\]/],
|
|
8456
|
+
passes: [
|
|
8457
|
+
{ id: "state-machine-bypass", phase: "integration", category: "logic-error", description: "Check for state machine/workflow bypass. Look for: direct API access to skip workflow steps (e.g., complete order without payment), missing validation of state transitions (can go from any state to any state), authorization not checked on each transition, concurrent transitions causing invalid states, rollback not cleaning up side effects." }
|
|
8458
|
+
]
|
|
8459
|
+
},
|
|
8460
|
+
"feature-flags": {
|
|
8461
|
+
keywords: ["feature flag", "feature toggle", "feature gate", "experiment", "a/b test", "canary", "rollout"],
|
|
8462
|
+
knownDeps: ["launchdarkly-node-server-sdk", "@unleash/proxy-client-react", "flagsmith", "growthbook", "@growthbook/growthbook", "posthog-node", "@happykit/flags", "flipt-node"],
|
|
8463
|
+
depSegments: ["feature", "flag", "toggle", "experiment"],
|
|
8464
|
+
pathPatterns: [/feature-flags?[/\\]/, /flags?[/\\]/, /toggles?[/\\]/, /experiments?[/\\]/],
|
|
8465
|
+
passes: [
|
|
8466
|
+
{ id: "feature-flag-bypass", phase: "unit", category: "auth-bypass", description: "Check for feature flag bypass. Look for: flag values settable via query parameters or headers, flag evaluation using client-supplied user context without validation, admin features hidden behind flags but not behind auth, flag configuration exposed to client, default flag values granting access." }
|
|
8467
|
+
]
|
|
8468
|
+
},
|
|
8469
|
+
// ═══════════════════════════════════════════════════════════
|
|
8470
|
+
// DATA MOVEMENT
|
|
8471
|
+
// ═══════════════════════════════════════════════════════════
|
|
8472
|
+
"export": {
|
|
8473
|
+
keywords: ["export", "download", "csv export", "excel", "spreadsheet", "report export", "data dump"],
|
|
8474
|
+
knownDeps: ["exceljs", "xlsx", "json2csv", "csv-writer", "papaparse", "archiver", "jszip"],
|
|
8475
|
+
depSegments: ["export", "csv", "excel", "xlsx"],
|
|
8476
|
+
pathPatterns: [/exports?[/\\]/, /downloads?[/\\]/],
|
|
8477
|
+
passes: [
|
|
8478
|
+
{ id: "export-injection", phase: "unit", category: "injection", description: "Check for injection in data exports. Look for: CSV injection via formulas (=CMD(), +cmd, @SUM) in cell values, Excel macro injection, user data included in exports without sanitization, export endpoints without authorization (data exfiltration), large export without pagination causing DoS, export containing more data than user should access." }
|
|
8479
|
+
]
|
|
8480
|
+
},
|
|
8481
|
+
"import": {
|
|
8482
|
+
keywords: ["import", "upload csv", "bulk import", "data import", "ingest", "data migration"],
|
|
8483
|
+
knownDeps: ["csv-parse", "papaparse", "xlsx", "exceljs", "multer", "fast-csv"],
|
|
8484
|
+
depSegments: ["import", "ingest", "csv-parse"],
|
|
8485
|
+
pathPatterns: [/imports?[/\\]/, /ingest[/\\]/, /migrations?[/\\]/],
|
|
8486
|
+
passes: [
|
|
8487
|
+
{ id: "import-safety", phase: "unit", category: "injection", description: "Check import/ingestion safety. Look for: CSV/Excel parsing without row limits (DoS via huge files), imported data used in queries without sanitization, missing validation of imported data format/schema, zip bomb in archive imports, imported file paths used in file operations (path traversal), deserialization of imported objects." }
|
|
8488
|
+
]
|
|
8489
|
+
},
|
|
8490
|
+
// ═══════════════════════════════════════════════════════════
|
|
8491
|
+
// ADVANCED
|
|
8492
|
+
// ═══════════════════════════════════════════════════════════
|
|
8493
|
+
"ml-ai": {
|
|
8494
|
+
keywords: ["machine learning", "artificial intelligence", "llm", "ai model", "ai agent", "inference", "prediction", "embedding", "vector database", "prompt engineering", "chatbot", "rag pipeline", "large language model", "generative ai"],
|
|
8495
|
+
knownDeps: ["@anthropic-ai/sdk", "openai", "@google/generative-ai", "langchain", "@langchain/core", "tensorflow", "@tensorflow/tfjs", "onnxruntime-node", "transformers", "pinecone", "@pinecone-database/pinecone", "chromadb", "weaviate-ts-client", "replicate", "cohere-ai", "ai"],
|
|
8496
|
+
depSegments: ["ml", "ai", "llm", "openai", "anthropic", "langchain", "vector", "embedding", "tensorflow", "onnx"],
|
|
8497
|
+
pathPatterns: [/ml[/\\]/, /\bai[/\\]/, /inference[/\\]/, /embeddings?[/\\]/, /llm[/\\]/, /rag[/\\]/],
|
|
8498
|
+
passes: [
|
|
8499
|
+
{ id: "prompt-injection", phase: "unit", category: "injection", description: "Check for prompt injection in LLM/AI features. Look for: user input concatenated directly into LLM prompts without sanitization, missing system prompt boundaries, tool/function calls executed without user confirmation, model outputs used in SQL/command/template rendering without validation, embedding search returning unauthorized content, RAG context leaking across users." },
|
|
8500
|
+
{ id: "ai-output-safety", phase: "integration", category: "injection", description: "Check AI output safety. Look for: LLM responses rendered as HTML without sanitization (XSS), model output used in database queries, AI-generated code executed without sandboxing, model hallucinations used for access control decisions, PII from training data leaked in responses, unlimited token generation (cost/DoS)." }
|
|
8501
|
+
]
|
|
8502
|
+
},
|
|
8503
|
+
"proxy": {
|
|
8504
|
+
keywords: ["proxy", "reverse proxy", "api gateway", "load balancer", "ingress", "edge"],
|
|
8505
|
+
knownDeps: ["http-proxy", "http-proxy-middleware", "express-http-proxy", "@fastify/http-proxy", "redbird", "node-http-proxy"],
|
|
8506
|
+
depSegments: ["proxy", "gateway"],
|
|
8507
|
+
pathPatterns: [/proxy[/\\]/, /gateway[/\\]/],
|
|
8508
|
+
passes: [
|
|
8509
|
+
{ id: "proxy-ssrf", phase: "integration", category: "injection", description: "Check for SSRF via proxy functionality. Look for: user-controlled URLs passed to proxy/fetch without allowlist, internal service URLs accessible via proxy endpoint, DNS rebinding attacks via proxy, HTTP header injection in proxied requests (CRLF), request smuggling via inconsistent parsing between proxy and backend." }
|
|
8510
|
+
]
|
|
8511
|
+
},
|
|
8512
|
+
"cron": {
|
|
8513
|
+
keywords: ["cron", "scheduled", "scheduler", "periodic", "timer", "recurring task", "background task"],
|
|
8514
|
+
knownDeps: ["node-cron", "cron", "node-schedule", "agenda", "bree", "croner", "toad-scheduler"],
|
|
8515
|
+
depSegments: ["cron", "scheduler", "schedule"],
|
|
8516
|
+
pathPatterns: [/cron[/\\]/, /schedulers?[/\\]/, /scheduled[/\\]/, /tasks?[/\\]/],
|
|
8517
|
+
passes: [
|
|
8518
|
+
{ id: "cron-safety", phase: "unit", category: "injection", description: "Check scheduled task safety. Look for: cron job commands constructed from user input, scheduled tasks running with elevated privileges unnecessarily, cron schedule modifiable by non-admin users, missing locking for distributed cron (duplicate execution), long-running cron tasks without timeout, cron jobs accessing stale credentials/tokens." }
|
|
8519
|
+
]
|
|
8520
|
+
},
|
|
8521
|
+
"admin": {
|
|
8522
|
+
keywords: ["admin", "backoffice", "back-office", "dashboard", "management panel", "control panel", "superuser"],
|
|
8523
|
+
knownDeps: ["adminjs", "@adminjs/express", "react-admin", "ra-data-simple-rest"],
|
|
8524
|
+
depSegments: ["admin", "backoffice", "dashboard"],
|
|
8525
|
+
pathPatterns: [/admin[/\\]/, /backoffice[/\\]/, /dashboard[/\\]/, /panel[/\\]/],
|
|
8526
|
+
passes: [
|
|
8527
|
+
{ id: "privilege-boundary", phase: "integration", category: "auth-bypass", description: "Verify admin privilege boundaries. Look for: admin endpoints accessible without role verification, admin actions not re-verified at service layer (only checked at route level), internal admin APIs callable from public network, admin impersonation feature without audit logging, admin tools exposing raw database queries." }
|
|
8528
|
+
]
|
|
8529
|
+
},
|
|
8530
|
+
"analytics": {
|
|
8531
|
+
keywords: ["analytics", "tracking", "metrics", "telemetry", "usage", "event tracking"],
|
|
8532
|
+
knownDeps: ["mixpanel", "segment", "@segment/analytics-node", "posthog-js", "posthog-node", "amplitude-js", "@amplitude/analytics-node", "plausible-tracker", "umami"],
|
|
8533
|
+
depSegments: ["analytics", "tracking", "metrics", "telemetry"],
|
|
8534
|
+
pathPatterns: [/analytics[/\\]/, /tracking[/\\]/, /metrics[/\\]/, /telemetry[/\\]/],
|
|
8535
|
+
passes: [
|
|
8536
|
+
{ id: "analytics-pii", phase: "unit", category: "secrets-exposure", description: "Check for PII in analytics tracking. Look for: user email/name/IP sent to analytics services, session tokens or auth headers included in tracking events, form input values tracked without sanitization, URL parameters containing sensitive data sent to analytics, analytics scripts loading third-party code without integrity checks." }
|
|
8537
|
+
]
|
|
8538
|
+
}
|
|
8539
|
+
};
|
|
8540
|
+
var CRITICAL_DOMAINS = /* @__PURE__ */ new Set([
|
|
8541
|
+
"auth",
|
|
8542
|
+
"session",
|
|
8543
|
+
"oauth",
|
|
8544
|
+
"access-control",
|
|
8545
|
+
"mfa",
|
|
8546
|
+
"payments",
|
|
8547
|
+
"crypto",
|
|
8548
|
+
"multi-tenancy"
|
|
8549
|
+
]);
|
|
8550
|
+
var SKIP_RULES = [
|
|
8551
|
+
{
|
|
8552
|
+
passName: "secrets-exposure",
|
|
8553
|
+
condition: (ctx) => {
|
|
8554
|
+
const hasEnvFiles = ctx.projectFiles.some((f) => /\.env($|\.)/.test(f));
|
|
8555
|
+
const hasConfigFiles = ctx.projectFiles.some((f) => /config\.(json|yml|yaml|toml)$/.test(f));
|
|
8556
|
+
return !hasEnvFiles && !hasConfigFiles;
|
|
8557
|
+
},
|
|
8558
|
+
reason: "No env files or config files detected in project"
|
|
8559
|
+
},
|
|
8560
|
+
{
|
|
8561
|
+
passName: "auth-bypass",
|
|
8562
|
+
condition: (ctx) => {
|
|
8563
|
+
return !hasAnyDomain(ctx.domains, ["auth", "session", "oauth", "access-control", "mfa"]);
|
|
8564
|
+
},
|
|
8565
|
+
reason: "No auth/session/access-control domain detected"
|
|
8566
|
+
},
|
|
8567
|
+
{
|
|
8568
|
+
passName: "auth-flow-trace",
|
|
8569
|
+
condition: (ctx) => {
|
|
8570
|
+
return !hasAnyDomain(ctx.domains, ["auth", "session", "oauth", "access-control", "mfa"]);
|
|
8571
|
+
},
|
|
8572
|
+
reason: "No auth/session/access-control domain detected"
|
|
8573
|
+
},
|
|
8574
|
+
{
|
|
8575
|
+
passName: "session-lifecycle-trace",
|
|
8576
|
+
condition: (ctx) => {
|
|
8577
|
+
return !hasAnyDomain(ctx.domains, ["session", "auth", "oauth"]);
|
|
8578
|
+
},
|
|
8579
|
+
reason: "No session/auth/oauth domain detected"
|
|
8580
|
+
},
|
|
8581
|
+
{
|
|
8582
|
+
passName: "api-contract-verification",
|
|
8583
|
+
condition: (ctx) => {
|
|
8584
|
+
const hasOpenApi = ctx.projectFiles.some(
|
|
8585
|
+
(f) => /openapi\.(json|yml|yaml)$/.test(f) || /swagger\.(json|yml|yaml)$/.test(f) || /api-spec\.(json|yml|yaml)$/.test(f)
|
|
8586
|
+
);
|
|
8587
|
+
return !hasOpenApi;
|
|
8588
|
+
},
|
|
8589
|
+
reason: "No OpenAPI/Swagger specification files detected"
|
|
8590
|
+
}
|
|
8591
|
+
];
|
|
8592
|
+
function hasAnyDomain(detected, needed) {
|
|
8593
|
+
return needed.some((d) => detected.includes(d));
|
|
8594
|
+
}
|
|
8595
|
+
var SENSITIVE_DATA_PATTERNS = [
|
|
8596
|
+
{ type: "PII", keywords: ["email", "phone", "address", "name", "ssn", "social security", "date of birth", "national id"], domainIndicators: ["analytics"] },
|
|
8597
|
+
{ type: "financial", keywords: ["credit card", "bank", "account number", "routing", "payment", "transaction", "balance", "billing"], domainIndicators: ["payments", "e-commerce", "subscription"] },
|
|
8598
|
+
{ type: "health", keywords: ["medical", "health", "patient", "diagnosis", "prescription", "hipaa", "ehr", "clinical"], domainIndicators: [] },
|
|
8599
|
+
{ type: "credentials", keywords: ["password", "api key", "secret key", "credential", "access key", "private key"], domainIndicators: ["auth", "crypto", "config"] },
|
|
8600
|
+
{ type: "authentication-tokens", keywords: ["token", "jwt", "session id", "bearer", "refresh token", "oauth token"], domainIndicators: ["session", "oauth"] },
|
|
8601
|
+
{ type: "location", keywords: ["gps", "latitude", "longitude", "geolocation", "location tracking", "coordinates"], domainIndicators: [] },
|
|
8602
|
+
{ type: "biometric", keywords: ["fingerprint", "face id", "biometric", "retina", "voice print"], domainIndicators: ["mfa"] },
|
|
8603
|
+
{ type: "legal", keywords: ["gdpr", "ccpa", "compliance", "consent", "data retention", "right to deletion", "dpa"], domainIndicators: [] }
|
|
8604
|
+
];
|
|
8605
|
+
function generateRiskProfile(understanding, projectFiles, packageJsonDeps) {
|
|
8606
|
+
const deps = packageJsonDeps || understanding.dependencies || {};
|
|
8607
|
+
const descriptionLower = understanding.summary.description.toLowerCase();
|
|
8608
|
+
const featureNames = understanding.features.map((f) => f.name.toLowerCase());
|
|
8609
|
+
const featureDescriptions = understanding.features.map((f) => f.description.toLowerCase());
|
|
8610
|
+
const relatedFiles = understanding.features.flatMap((f) => f.relatedFiles || []);
|
|
8611
|
+
const domains = detectDomains(descriptionLower, featureNames, featureDescriptions, deps, projectFiles, relatedFiles);
|
|
8612
|
+
const sensitiveDataTypes = detectSensitiveData(descriptionLower, featureDescriptions, domains);
|
|
8613
|
+
const externalDependencies = Object.keys(deps).filter(
|
|
8614
|
+
(dep) => !dep.startsWith("@types/") && !dep.startsWith("eslint") && !dep.startsWith("vitest") && !dep.startsWith("typescript") && !dep.startsWith("prettier")
|
|
8615
|
+
);
|
|
8616
|
+
const hotPaths = detectHotPaths(projectFiles, relatedFiles, domains);
|
|
8617
|
+
const customPasses = generateCustomPasses(domains);
|
|
8618
|
+
const skippedPasses = determineSkippedPasses({
|
|
8619
|
+
domains,
|
|
8620
|
+
projectFiles,
|
|
8621
|
+
deps,
|
|
8622
|
+
understanding
|
|
8623
|
+
});
|
|
8624
|
+
return {
|
|
8625
|
+
version: "1",
|
|
8626
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8627
|
+
domains,
|
|
8628
|
+
sensitiveDataTypes,
|
|
8629
|
+
externalDependencies,
|
|
8630
|
+
hotPaths,
|
|
8631
|
+
customPasses,
|
|
8632
|
+
skippedPasses
|
|
8633
|
+
};
|
|
8634
|
+
}
|
|
8635
|
+
function matchesKeyword(text2, keyword) {
|
|
8636
|
+
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8637
|
+
if (keyword.length <= 3) {
|
|
8638
|
+
return new RegExp(`\\b${escaped}\\b`, "i").test(text2);
|
|
8639
|
+
}
|
|
8640
|
+
return new RegExp(`\\b${escaped}`, "i").test(text2);
|
|
8641
|
+
}
|
|
8642
|
+
function depNameSegments(depName) {
|
|
8643
|
+
return depName.replace(/^@/, "").split(/[-/._]/).filter((s) => s.length > 1).map((s) => s.toLowerCase());
|
|
8644
|
+
}
|
|
8645
|
+
function detectDomains(descriptionLower, featureNames, featureDescriptions, deps, projectFiles, relatedFiles) {
|
|
8646
|
+
const detected = /* @__PURE__ */ new Set();
|
|
8647
|
+
const depNames = Object.keys(deps);
|
|
8648
|
+
const allDepSegments = depNames.flatMap((d) => depNameSegments(d));
|
|
8649
|
+
const allFeatureText = [...featureNames, ...featureDescriptions];
|
|
8650
|
+
const allFiles = [...projectFiles, ...relatedFiles];
|
|
8651
|
+
for (const [domain, def] of Object.entries(DOMAINS)) {
|
|
8652
|
+
if (def.keywords.some((kw) => matchesKeyword(descriptionLower, kw))) {
|
|
8653
|
+
detected.add(domain);
|
|
8654
|
+
continue;
|
|
8655
|
+
}
|
|
8656
|
+
if (def.keywords.some((kw) => allFeatureText.some((ft) => matchesKeyword(ft, kw)))) {
|
|
8657
|
+
detected.add(domain);
|
|
8658
|
+
continue;
|
|
8659
|
+
}
|
|
8660
|
+
if (def.knownDeps.some((dep) => depNames.includes(dep))) {
|
|
8661
|
+
detected.add(domain);
|
|
8662
|
+
continue;
|
|
8663
|
+
}
|
|
8664
|
+
if (def.depSegments.length > 0 && def.depSegments.some((seg) => allDepSegments.includes(seg))) {
|
|
8665
|
+
detected.add(domain);
|
|
8666
|
+
continue;
|
|
8667
|
+
}
|
|
8668
|
+
if (def.pathPatterns.length > 0 && def.pathPatterns.some((pattern) => allFiles.some((f) => pattern.test(f)))) {
|
|
8669
|
+
detected.add(domain);
|
|
8670
|
+
continue;
|
|
8671
|
+
}
|
|
8672
|
+
}
|
|
8673
|
+
return Array.from(detected).sort();
|
|
8674
|
+
}
|
|
8675
|
+
function detectSensitiveData(descriptionLower, featureDescriptions, domains) {
|
|
8676
|
+
const detected = /* @__PURE__ */ new Set();
|
|
8677
|
+
const allText = [descriptionLower, ...featureDescriptions].join(" ");
|
|
8678
|
+
for (const pattern of SENSITIVE_DATA_PATTERNS) {
|
|
8679
|
+
if (pattern.keywords.some((kw) => matchesKeyword(allText, kw))) {
|
|
8680
|
+
detected.add(pattern.type);
|
|
8681
|
+
}
|
|
8682
|
+
if (pattern.domainIndicators.some((d) => domains.includes(d))) {
|
|
8683
|
+
detected.add(pattern.type);
|
|
8684
|
+
}
|
|
8685
|
+
}
|
|
8686
|
+
return Array.from(detected).sort();
|
|
8687
|
+
}
|
|
8688
|
+
function detectHotPaths(projectFiles, relatedFiles, domains) {
|
|
8689
|
+
const hotPaths = [];
|
|
8690
|
+
const allFiles = [.../* @__PURE__ */ new Set([...projectFiles, ...relatedFiles])];
|
|
8691
|
+
for (const file of allFiles) {
|
|
8692
|
+
const touchedDomains = [];
|
|
8693
|
+
for (const [domain, def] of Object.entries(DOMAINS)) {
|
|
8694
|
+
if (!domains.includes(domain)) continue;
|
|
8695
|
+
if (def.pathPatterns.length === 0) continue;
|
|
8696
|
+
if (def.pathPatterns.some((pattern) => pattern.test(file))) {
|
|
8697
|
+
touchedDomains.push(domain);
|
|
8698
|
+
}
|
|
8699
|
+
}
|
|
8700
|
+
if (touchedDomains.length >= 2) {
|
|
8701
|
+
const riskLevel = touchedDomains.some((d) => CRITICAL_DOMAINS.has(d)) ? "critical" : "high";
|
|
8702
|
+
hotPaths.push({
|
|
8703
|
+
file,
|
|
8704
|
+
reason: `Touches ${touchedDomains.join(" + ")} domains`,
|
|
8705
|
+
riskLevel
|
|
8706
|
+
});
|
|
8707
|
+
}
|
|
8708
|
+
}
|
|
8709
|
+
return hotPaths;
|
|
8710
|
+
}
|
|
8711
|
+
function generateCustomPasses(domains) {
|
|
8712
|
+
const passes = [];
|
|
8713
|
+
for (const domain of domains) {
|
|
8714
|
+
const def = DOMAINS[domain];
|
|
8715
|
+
if (def && def.passes.length > 0) {
|
|
8716
|
+
passes.push(...def.passes);
|
|
8717
|
+
}
|
|
8718
|
+
}
|
|
8719
|
+
return passes;
|
|
8720
|
+
}
|
|
8721
|
+
function determineSkippedPasses(ctx) {
|
|
8722
|
+
const skipped = [];
|
|
8723
|
+
for (const rule of SKIP_RULES) {
|
|
8724
|
+
if (rule.condition(ctx)) {
|
|
8725
|
+
skipped.push({
|
|
8726
|
+
passName: rule.passName,
|
|
8727
|
+
reason: rule.reason
|
|
8728
|
+
});
|
|
8729
|
+
}
|
|
8730
|
+
}
|
|
8731
|
+
return skipped;
|
|
8732
|
+
}
|
|
8733
|
+
|
|
8734
|
+
// src/cli/commands/profile.ts
|
|
8735
|
+
async function profileCommand(options) {
|
|
8736
|
+
const cwd = process.cwd();
|
|
8737
|
+
const whiterosePath = join(cwd, ".whiterose");
|
|
8738
|
+
if (!existsSync(whiterosePath)) {
|
|
8739
|
+
p3.log.error("whiterose is not initialized in this directory.");
|
|
8740
|
+
p3.log.info('Run "whiterose init" first.');
|
|
8741
|
+
process.exit(1);
|
|
8742
|
+
}
|
|
8743
|
+
const spinner6 = p3.spinner();
|
|
8744
|
+
spinner6.start("Loading codebase understanding...");
|
|
8745
|
+
const understanding = await loadUnderstanding(cwd);
|
|
8746
|
+
if (!understanding) {
|
|
8747
|
+
spinner6.stop("No understanding found");
|
|
8748
|
+
p3.log.error('No understanding.json found. Run "whiterose init" or "whiterose refresh" first.');
|
|
8749
|
+
process.exit(1);
|
|
8750
|
+
}
|
|
8751
|
+
spinner6.stop("Understanding loaded");
|
|
8752
|
+
let packageJsonDeps;
|
|
8753
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
8754
|
+
if (existsSync(packageJsonPath)) {
|
|
8755
|
+
try {
|
|
8756
|
+
const pkg2 = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
8757
|
+
packageJsonDeps = {
|
|
8758
|
+
...pkg2.dependencies || {},
|
|
8759
|
+
...pkg2.devDependencies || {}
|
|
8760
|
+
};
|
|
8761
|
+
} catch {
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
const projectFiles = understanding.features.flatMap((f) => f.relatedFiles || []);
|
|
8765
|
+
try {
|
|
8766
|
+
const rootFiles = readdirSync(cwd);
|
|
8767
|
+
for (const file of rootFiles) {
|
|
8768
|
+
if (/^\.env($|\.)/.test(file) || /^config\.(json|yml|yaml|toml)$/.test(file) || file === "openapi.json" || file === "openapi.yml" || file === "openapi.yaml" || file === "swagger.json" || file === "swagger.yml" || file === "swagger.yaml") {
|
|
8769
|
+
projectFiles.push(file);
|
|
8770
|
+
}
|
|
8771
|
+
}
|
|
8772
|
+
} catch {
|
|
8773
|
+
}
|
|
8774
|
+
spinner6.start("Generating risk profile...");
|
|
8775
|
+
const profile = generateRiskProfile(understanding, projectFiles, packageJsonDeps);
|
|
8776
|
+
const cachePath = join(whiterosePath, "cache");
|
|
8777
|
+
mkdirSync(cachePath, { recursive: true });
|
|
8778
|
+
const profilePath = join(whiterosePath, "risk-profile.json");
|
|
8779
|
+
writeFileSync(profilePath, JSON.stringify(profile, null, 2), "utf-8");
|
|
8780
|
+
spinner6.stop("Risk profile generated");
|
|
8781
|
+
if (options.json) {
|
|
8782
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
8783
|
+
return;
|
|
8784
|
+
}
|
|
8785
|
+
displayProfile(profile);
|
|
8786
|
+
console.log();
|
|
8787
|
+
p3.log.success(`Saved to ${chalk3.cyan(".whiterose/risk-profile.json")}`);
|
|
8788
|
+
p3.log.info(`Run ${chalk3.cyan("whiterose scan --full")} to use this profile.`);
|
|
8789
|
+
}
|
|
8790
|
+
function displayProfile(profile) {
|
|
8791
|
+
console.log();
|
|
8792
|
+
if (profile.domains.length > 0) {
|
|
8793
|
+
p3.log.step(chalk3.bold("Detected Domains"));
|
|
8794
|
+
for (const domain of profile.domains) {
|
|
8795
|
+
const icon = getDomainIcon(domain);
|
|
8796
|
+
console.log(` ${icon} ${chalk3.cyan(domain)}`);
|
|
8797
|
+
}
|
|
8798
|
+
} else {
|
|
8799
|
+
p3.log.info("No specific domains detected");
|
|
8800
|
+
}
|
|
8801
|
+
if (profile.sensitiveDataTypes.length > 0) {
|
|
8802
|
+
console.log();
|
|
8803
|
+
p3.log.step(chalk3.bold("Sensitive Data Types"));
|
|
8804
|
+
for (const dt of profile.sensitiveDataTypes) {
|
|
8805
|
+
console.log(` ${chalk3.yellow("\u25CF")} ${dt}`);
|
|
8806
|
+
}
|
|
8807
|
+
}
|
|
8808
|
+
if (profile.hotPaths.length > 0) {
|
|
8809
|
+
console.log();
|
|
8810
|
+
p3.log.step(chalk3.bold("Hot Paths") + chalk3.dim(" (files touching 2+ domains)"));
|
|
8811
|
+
for (const hp of profile.hotPaths) {
|
|
8812
|
+
const color = hp.riskLevel === "critical" ? chalk3.red : hp.riskLevel === "high" ? chalk3.yellow : chalk3.blue;
|
|
8813
|
+
console.log(` ${color("[" + hp.riskLevel + "]")} ${hp.file}`);
|
|
8814
|
+
console.log(` ${chalk3.dim(hp.reason)}`);
|
|
8815
|
+
}
|
|
8816
|
+
}
|
|
8817
|
+
if (profile.customPasses.length > 0) {
|
|
8818
|
+
console.log();
|
|
8819
|
+
p3.log.step(chalk3.bold("Custom Passes") + chalk3.dim(` (${profile.customPasses.length} domain-specific)`));
|
|
8820
|
+
for (const pass of profile.customPasses) {
|
|
8821
|
+
const phaseColor = pass.phase === "unit" ? chalk3.green : pass.phase === "integration" ? chalk3.blue : chalk3.magenta;
|
|
8822
|
+
console.log(` ${chalk3.cyan("+")} ${pass.id} ${phaseColor("[" + pass.phase + "]")}`);
|
|
8823
|
+
}
|
|
8824
|
+
}
|
|
8825
|
+
if (profile.skippedPasses.length > 0) {
|
|
8826
|
+
console.log();
|
|
8827
|
+
p3.log.step(chalk3.bold("Skipped Passes") + chalk3.dim(` (${profile.skippedPasses.length} not relevant)`));
|
|
8828
|
+
for (const skip of profile.skippedPasses) {
|
|
8829
|
+
console.log(` ${chalk3.dim("-")} ${skip.passName} ${chalk3.dim("(" + skip.reason + ")")}`);
|
|
8830
|
+
}
|
|
8831
|
+
}
|
|
8832
|
+
}
|
|
8833
|
+
function getDomainIcon(domain) {
|
|
8834
|
+
const icons = {
|
|
8835
|
+
"payments": "\u25B6",
|
|
8836
|
+
"auth": "\u25B6",
|
|
8837
|
+
"file-upload": "\u25B6",
|
|
8838
|
+
"messaging": "\u25B6",
|
|
8839
|
+
"search": "\u25B6",
|
|
8840
|
+
"analytics": "\u25B6",
|
|
8841
|
+
"admin": "\u25B6",
|
|
8842
|
+
"api": "\u25B6",
|
|
8843
|
+
"database": "\u25B6",
|
|
8844
|
+
"realtime": "\u25B6"
|
|
8845
|
+
};
|
|
8846
|
+
return icons[domain] || "\u25B6";
|
|
8847
|
+
}
|
|
8848
|
+
|
|
7937
8849
|
// src/cli/index.ts
|
|
7938
8850
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
7939
8851
|
var __dirname$1 = dirname(__filename$1);
|
|
@@ -7963,6 +8875,7 @@ program.command("refresh").description("Rebuild codebase understanding from scra
|
|
|
7963
8875
|
program.command("status").description("Show whiterose status (cache, last scan, provider)").action(statusCommand);
|
|
7964
8876
|
program.command("report").description("Generate BUGS.md from last scan").option("-o, --output <path>", "Output path", "BUGS.md").option("--format <format>", "Output format (markdown, sarif, json)", "markdown").action(reportCommand);
|
|
7965
8877
|
program.command("clear").description("Clear accumulated bug list (start fresh)").option("--force", "Skip confirmation prompt").action(clearCommand);
|
|
8878
|
+
program.command("profile").description("Generate risk profile for targeted scanning").option("--json", "Output as JSON").action(profileCommand);
|
|
7966
8879
|
async function autoRun() {
|
|
7967
8880
|
console.log(BANNER);
|
|
7968
8881
|
p3.intro(chalk3.red("whiterose") + chalk3.dim(" - AI Bug Hunter"));
|