@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 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((p9) => `- ${p9}`).join("\n")}
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((p9) => `- ${p9}`).join("\n")}
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((p9) => `\`${p9}\``).join("\n")}
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((p9) => p9.name === name);
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((p9) => p9.name === name);
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
- const unitPasses = pipeline[0].passes;
2283
- const integrationPasses = pipeline[1].passes;
2284
- const e2ePasses = pipeline[2].passes;
2285
- 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;
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 unitFindings = await this.runPassBatch(unitJobs, cwd, context.files, globalBugIndex);
2297
- globalBugIndex += unitFindings.length;
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 integrationFindings = await this.runPassBatch(integrationJobs, cwd, context.files, globalBugIndex);
2305
- globalBugIndex += integrationFindings.length;
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 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];
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((p9) => p9.name).join(", ")}`);
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((p9) => p9.test(content));
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: mkdirSync4 } = await import('fs');
6032
+ const { mkdirSync: mkdirSync5 } = await import('fs');
5865
6033
  try {
5866
- mkdirSync4(outputDir, { recursive: true });
5867
- mkdirSync4(reportsDir, { recursive: true });
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"));