@shakecodeslikecray/whiterose 1.0.11 → 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 CHANGED
@@ -595,6 +595,52 @@ var OllamaExecutor = class {
595
595
  }
596
596
  }
597
597
  };
598
+ var OPENCODE_TIMEOUT = 3e5;
599
+ var OpenCodeExecutor = class {
600
+ name = "opencode";
601
+ async isAvailable() {
602
+ return isProviderAvailable("opencode");
603
+ }
604
+ async runPrompt(prompt, options) {
605
+ const command = getProviderCommand("opencode");
606
+ try {
607
+ const { stdout, stderr } = await execa(
608
+ command,
609
+ ["run", prompt],
610
+ {
611
+ cwd: options.cwd,
612
+ timeout: options.timeout || OPENCODE_TIMEOUT,
613
+ env: {
614
+ ...process.env,
615
+ NO_COLOR: "1"
616
+ },
617
+ reject: false,
618
+ stdin: "ignore"
619
+ }
620
+ );
621
+ if (stderr) {
622
+ if (stderr.includes("429") || stderr.includes("rate limit") || stderr.includes("quota exceeded")) {
623
+ throw new Error("OpenCode API rate limit reached. Try again later.");
624
+ }
625
+ if (stderr.includes("401") || stderr.includes("403") || stderr.includes("unauthorized") || stderr.includes("invalid api key")) {
626
+ throw new Error("OpenCode API authentication failed. Check your API key.");
627
+ }
628
+ if ((stderr.includes("Error") || stderr.includes("error")) && !stdout) {
629
+ throw new Error(`OpenCode error: ${stderr.substring(0, 200)}`);
630
+ }
631
+ }
632
+ return {
633
+ output: stdout || "",
634
+ error: stderr || void 0
635
+ };
636
+ } catch (error) {
637
+ if (error.message?.includes("ENOENT")) {
638
+ throw new Error("OpenCode CLI not found. Install: curl -fsSL https://opencode.ai/install | bash");
639
+ }
640
+ throw error;
641
+ }
642
+ }
643
+ };
598
644
 
599
645
  // src/providers/executors/index.ts
600
646
  var executors = {
@@ -602,7 +648,8 @@ var executors = {
602
648
  "codex": () => new CodexExecutor(),
603
649
  "gemini": () => new GeminiExecutor(),
604
650
  "aider": () => new AiderExecutor(),
605
- "ollama": () => new OllamaExecutor()
651
+ "ollama": () => new OllamaExecutor(),
652
+ "opencode": () => new OpenCodeExecutor()
606
653
  };
607
654
  function getExecutor(name) {
608
655
  const factory = executors[name];
@@ -655,7 +702,7 @@ ${pass.whatToFind.map((w) => `- ${w}`).join("\n")}
655
702
  ${pass.traceInstructions}
656
703
 
657
704
  ## ENTRY POINTS TO START FROM
658
- ${pass.entryPointPatterns.map((p9) => `- ${p9}`).join("\n")}
705
+ ${pass.entryPointPatterns.map((p10) => `- ${p10}`).join("\n")}
659
706
 
660
707
  ## EXAMPLE VULNERABILITY
661
708
  This is what a real finding looks like for this pass:
@@ -1195,12 +1242,12 @@ ${entryPointsSection}
1195
1242
  ${staticSection}
1196
1243
  ## WHAT TO LOOK FOR
1197
1244
 
1198
- ${pass.searchPatterns.map((p9) => `- ${p9}`).join("\n")}
1245
+ ${pass.searchPatterns.map((p10) => `- ${p10}`).join("\n")}
1199
1246
 
1200
1247
  ## SEARCH STRATEGY
1201
1248
 
1202
1249
  Use these grep patterns to find potential issues:
1203
- ${pass.grepPatterns.map((p9) => `\`${p9}\``).join("\n")}
1250
+ ${pass.grepPatterns.map((p10) => `\`${p10}\``).join("\n")}
1204
1251
 
1205
1252
  ## METHODOLOGY
1206
1253
 
@@ -1267,6 +1314,67 @@ IMPORTANT:
1267
1314
  - If unsure, report it with confidence="low"
1268
1315
  - Aim for thoroughness - finding 10 potential issues is better than finding 0 confirmed bugs`;
1269
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
+ }
1270
1378
 
1271
1379
  // src/providers/prompts/constants.ts
1272
1380
  var PROJECT_TYPES_PROMPT = `PROJECT TYPE OPTIONS (pick the best fit):
@@ -1689,7 +1797,7 @@ var SCAN_PASSES = [
1689
1797
  }
1690
1798
  ];
1691
1799
  function getPassConfig(name) {
1692
- return SCAN_PASSES.find((p9) => p9.name === name);
1800
+ return SCAN_PASSES.find((p10) => p10.name === name);
1693
1801
  }
1694
1802
 
1695
1803
  // src/core/flow-analyzer.ts
@@ -2179,7 +2287,7 @@ const user = await User.create(req.body); // No validation! role:'admin' accept
2179
2287
  }
2180
2288
  ];
2181
2289
  function getFlowPassConfig(name) {
2182
- return FLOW_PASSES.find((p9) => p9.name === name);
2290
+ return FLOW_PASSES.find((p10) => p10.name === name);
2183
2291
  }
2184
2292
 
2185
2293
  // src/core/utils.ts
@@ -2232,38 +2340,68 @@ var CoreScanner = class {
2232
2340
  const startTime = Date.now();
2233
2341
  this.passErrors = [];
2234
2342
  const pipeline = getFullAnalysisPipeline();
2235
- const unitPasses = pipeline[0].passes;
2236
- const integrationPasses = pipeline[1].passes;
2237
- const e2ePasses = pipeline[2].passes;
2238
- const totalPasses = unitPasses.length + integrationPasses.length + e2ePasses.length;
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;
2239
2359
  this.report(`
2240
2360
  \u2550\u2550\u2550\u2550 CORE SCANNER (PIPELINE MODE) \u2550\u2550\u2550\u2550`);
2241
2361
  this.report(` Provider: ${this.executor.name}`);
2242
- 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
+ }
2243
2366
  this.report(` Findings flow: Unit \u2192 Integration \u2192 E2E`);
2244
2367
  let globalBugIndex = 0;
2245
2368
  this.report(`
2246
2369
  \u2550\u2550\u2550\u2550 PHASE 1: UNIT ANALYSIS \u2550\u2550\u2550\u2550`);
2247
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;
2248
2375
  const unitJobs = this.buildUnitPassJobs(context, unitPasses);
2249
- const unitFindings = await this.runPassBatch(unitJobs, cwd, context.files, globalBugIndex);
2250
- globalBugIndex += unitFindings.length;
2376
+ const standardUnitFindings = await this.runPassBatch(unitJobs, cwd, context.files, globalBugIndex);
2377
+ globalBugIndex += standardUnitFindings.length;
2378
+ const unitFindings = [...customUnitFindings, ...standardUnitFindings];
2251
2379
  this.report(` Phase 1 complete: ${unitFindings.length} findings`);
2252
2380
  this.report(`
2253
2381
  \u2550\u2550\u2550\u2550 PHASE 2: INTEGRATION ANALYSIS \u2550\u2550\u2550\u2550`);
2254
2382
  this.report(` Building on ${unitFindings.length} unit findings`);
2255
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;
2256
2388
  const integrationJobs = this.buildIntegrationPassJobs(context, integrationPasses, unitFindings);
2257
- const integrationFindings = await this.runPassBatch(integrationJobs, cwd, context.files, globalBugIndex);
2258
- globalBugIndex += integrationFindings.length;
2389
+ const standardIntFindings = await this.runPassBatch(integrationJobs, cwd, context.files, globalBugIndex);
2390
+ globalBugIndex += standardIntFindings.length;
2391
+ const integrationFindings = [...customIntFindings, ...standardIntFindings];
2259
2392
  this.report(` Phase 2 complete: ${integrationFindings.length} findings`);
2260
2393
  this.report(`
2261
2394
  \u2550\u2550\u2550\u2550 PHASE 3: E2E ANALYSIS \u2550\u2550\u2550\u2550`);
2262
2395
  this.report(` Building on ${unitFindings.length} unit + ${integrationFindings.length} integration findings`);
2263
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;
2264
2401
  const allPreviousFindings = [...unitFindings, ...integrationFindings];
2265
2402
  const e2eJobs = this.buildE2EPassJobs(context, e2ePasses, allPreviousFindings);
2266
- const e2eFindings = await this.runPassBatch(e2eJobs, cwd, context.files, globalBugIndex);
2403
+ const standardE2EFindings = await this.runPassBatch(e2eJobs, cwd, context.files, globalBugIndex);
2404
+ const e2eFindings = [...customE2EFindings, ...standardE2EFindings];
2267
2405
  this.report(` Phase 3 complete: ${e2eFindings.length} findings`);
2268
2406
  this.report(`
2269
2407
  \u2550\u2550\u2550\u2550 POST-PROCESSING \u2550\u2550\u2550\u2550`);
@@ -2297,7 +2435,7 @@ var CoreScanner = class {
2297
2435
  const batchNum = Math.floor(i / this.config.batchSize) + 1;
2298
2436
  const totalBatches = Math.ceil(passes.length / this.config.batchSize);
2299
2437
  this.report(`
2300
- [Batch ${batchNum}/${totalBatches}] ${batch.map((p9) => p9.name).join(", ")}`);
2438
+ [Batch ${batchNum}/${totalBatches}] ${batch.map((p10) => p10.name).join(", ")}`);
2301
2439
  const batchPromises = batch.map(async (pass) => {
2302
2440
  this.progress.onPassStart?.(pass.name);
2303
2441
  try {
@@ -2569,6 +2707,37 @@ var CoreScanner = class {
2569
2707
  }
2570
2708
  return jobs;
2571
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
+ }
2572
2741
  buildQuickScanPrompt(context) {
2573
2742
  const { understanding, staticResults, files } = context;
2574
2743
  const staticSignals = staticResults.length > 0 ? `
@@ -3767,6 +3936,33 @@ var CodebaseUnderstanding = z.object({
3767
3936
  packages: z.array(z.string()).optional()
3768
3937
  })
3769
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
+ });
3770
3966
  var FileHash = z.object({
3771
3967
  path: z.string(),
3772
3968
  hash: z.string(),
@@ -3811,8 +4007,6 @@ z.object({
3811
4007
  summary: ScanSummary,
3812
4008
  meta: ScanMeta.optional()
3813
4009
  });
3814
-
3815
- // src/core/config.ts
3816
4010
  async function loadConfig(cwd) {
3817
4011
  const configPath = join(cwd, ".whiterose", "config.yml");
3818
4012
  if (!existsSync(configPath)) {
@@ -5297,7 +5491,7 @@ function functionExists(content, functionName) {
5297
5491
  new RegExp(`\\blet\\s+${escaped}\\s*=\\s*`),
5298
5492
  new RegExp(`\\b${escaped}\\s*\\(`)
5299
5493
  ];
5300
- return patterns.some((p9) => p9.test(content));
5494
+ return patterns.some((p10) => p10.test(content));
5301
5495
  }
5302
5496
  function makeIntentBug(cwd, contract, reason) {
5303
5497
  return {
@@ -5564,6 +5758,25 @@ async function scanCommand(paths, options) {
5564
5758
  p3.log.info(`Incremental scan: ${filesToScan.length} changed files`);
5565
5759
  }
5566
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
+ }
5567
5780
  let staticResults;
5568
5781
  if (!isQuiet) {
5569
5782
  console.log(chalk3.dim("\u2502") + " Running static analysis (tsc, eslint)...");
@@ -5619,7 +5832,8 @@ async function scanCommand(paths, options) {
5619
5832
  files: filesToScan,
5620
5833
  understanding,
5621
5834
  staticResults: staticResults || [],
5622
- config
5835
+ config,
5836
+ riskProfile
5623
5837
  });
5624
5838
  }
5625
5839
  console.log();
@@ -5653,7 +5867,8 @@ async function scanCommand(paths, options) {
5653
5867
  files: filesToScan,
5654
5868
  understanding,
5655
5869
  staticResults: staticResults || [],
5656
- config
5870
+ config,
5871
+ riskProfile
5657
5872
  });
5658
5873
  }
5659
5874
  if (options.ci && scanner.hasPassErrors()) {
@@ -5814,10 +6029,10 @@ async function scanCommand(paths, options) {
5814
6029
  } else {
5815
6030
  const outputDir = join(cwd, "whiterose-output");
5816
6031
  const reportsDir = join(whiterosePath, "reports");
5817
- const { mkdirSync: mkdirSync4 } = await import('fs');
6032
+ const { mkdirSync: mkdirSync5 } = await import('fs');
5818
6033
  try {
5819
- mkdirSync4(outputDir, { recursive: true });
5820
- mkdirSync4(reportsDir, { recursive: true });
6034
+ mkdirSync5(outputDir, { recursive: true });
6035
+ mkdirSync5(reportsDir, { recursive: true });
5821
6036
  } catch {
5822
6037
  }
5823
6038
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -7887,6 +8102,750 @@ async function clearCommand(options) {
7887
8102
  p3.outro(chalk3.green("Done"));
7888
8103
  }
7889
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
+
7890
8849
  // src/cli/index.ts
7891
8850
  var __filename$1 = fileURLToPath(import.meta.url);
7892
8851
  var __dirname$1 = dirname(__filename$1);
@@ -7916,6 +8875,7 @@ program.command("refresh").description("Rebuild codebase understanding from scra
7916
8875
  program.command("status").description("Show whiterose status (cache, last scan, provider)").action(statusCommand);
7917
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);
7918
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);
7919
8879
  async function autoRun() {
7920
8880
  console.log(BANNER);
7921
8881
  p3.intro(chalk3.red("whiterose") + chalk3.dim(" - AI Bug Hunter"));