cto-ai-cli 3.1.0 → 3.2.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/score.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli/score.ts
4
4
  import { resolve as resolve4, join as join4 } from "path";
5
- import { mkdirSync, writeFileSync, readFileSync } from "fs";
5
+ import { mkdirSync, writeFileSync, readFileSync, appendFileSync } from "fs";
6
6
 
7
7
  // src/engine/analyzer.ts
8
8
  import { readFile as readFile2, readdir, stat as stat2 } from "fs/promises";
@@ -737,7 +737,29 @@ var BUILTIN_PATTERNS = [
737
737
  { type: "connection-string", source: `(?:mongodb(?:\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\/\\/[^\\s'"]+:[^\\s'"]+@[^\\s'"]+`, flags: "gi", severity: "critical", description: "Database Connection String" },
738
738
  { type: "connection-string", source: `(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\s*[:=]\\s*['"]?([^\\s'"]{10,})['"]?`, flags: "gi", severity: "high", description: "Database URL" },
739
739
  // Environment variables with secrets
740
- { type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" }
740
+ { type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" },
741
+ // Stripe
742
+ { type: "api-key", source: "sk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Live Secret Key" },
743
+ { type: "api-key", source: "pk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "high", description: "Stripe Live Publishable Key" },
744
+ { type: "api-key", source: "rk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Restricted Key" },
745
+ // Slack
746
+ { type: "token", source: "xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack Bot Token" },
747
+ { type: "token", source: "xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack User Token" },
748
+ { type: "api-key", source: "https://hooks\\.slack\\.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+", flags: "g", severity: "high", description: "Slack Webhook URL" },
749
+ // Google
750
+ { type: "api-key", source: "AIza[0-9A-Za-z_-]{35}", flags: "g", severity: "high", description: "Google API Key" },
751
+ { type: "token", source: "ya29\\.[0-9A-Za-z_-]+", flags: "g", severity: "high", description: "Google OAuth Token" },
752
+ // Azure
753
+ { type: "api-key", source: "(?:AccountKey|SharedAccessKey)\\s*=\\s*[a-zA-Z0-9+/=]{40,}", flags: "g", severity: "critical", description: "Azure Storage Key" },
754
+ // Twilio
755
+ { type: "api-key", source: "AC[a-f0-9]{32}", flags: "g", severity: "high", description: "Twilio Account SID" },
756
+ // SendGrid
757
+ { type: "api-key", source: "SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}", flags: "g", severity: "critical", description: "SendGrid API Key" },
758
+ // JWT
759
+ { type: "token", source: "eyJ[a-zA-Z0-9_-]{10,}\\.eyJ[a-zA-Z0-9_-]{10,}\\.[a-zA-Z0-9_-]{10,}", flags: "g", severity: "high", description: "JSON Web Token" },
760
+ // PII
761
+ { type: "pii", source: "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b", flags: "g", severity: "medium", description: "Email Address (PII)" },
762
+ { type: "pii", source: "\\b\\d{3}[-.]?\\d{2}[-.]?\\d{4}\\b", flags: "g", severity: "high", description: "Possible SSN (PII)" }
741
763
  ];
742
764
  function buildPatterns(customPatterns = []) {
743
765
  const patterns = BUILTIN_PATTERNS.map((def) => ({
@@ -827,6 +849,136 @@ function deduplicateFindings(findings) {
827
849
  return true;
828
850
  });
829
851
  }
852
+ function shannonEntropy(str) {
853
+ const freq = /* @__PURE__ */ new Map();
854
+ for (const ch of str) {
855
+ freq.set(ch, (freq.get(ch) || 0) + 1);
856
+ }
857
+ let entropy = 0;
858
+ for (const count of freq.values()) {
859
+ const p = count / str.length;
860
+ if (p > 0) entropy -= p * Math.log2(p);
861
+ }
862
+ return entropy;
863
+ }
864
+ var HIGH_ENTROPY_RE = /['"]([a-zA-Z0-9+/=_\-]{30,})['"]|=\s*['"]?([a-zA-Z0-9+/=_\-]{30,})['"]?/g;
865
+ var ENTROPY_SKIP = [
866
+ /^[a-f0-9]{32,}$/i,
867
+ // hex hashes
868
+ /^[A-Z_]{30,}$/,
869
+ // all-caps constants
870
+ /^[a-z_]{30,}$/,
871
+ // all-lowercase identifiers
872
+ /^[a-zA-Z0-9+/]+=+$/,
873
+ // base64 padding
874
+ /^[a-z]+[A-Z][a-zA-Z]+$/,
875
+ // camelCase identifiers
876
+ /sha\d+-/i
877
+ // integrity hashes (sha256-, sha512-)
878
+ ];
879
+ function scanContentForHighEntropy(content, filePath, threshold = 5) {
880
+ const findings = [];
881
+ const lines = content.split("\n");
882
+ for (let i = 0; i < lines.length; i++) {
883
+ const line = lines[i];
884
+ if (line.trim().startsWith("//") || line.trim().startsWith("#") || line.trim().startsWith("*")) continue;
885
+ HIGH_ENTROPY_RE.lastIndex = 0;
886
+ let match;
887
+ while ((match = HIGH_ENTROPY_RE.exec(line)) !== null) {
888
+ const value = match[1] || match[2];
889
+ if (!value || value.length < 40) continue;
890
+ if (isTemplateOrPlaceholder(value)) continue;
891
+ if (ENTROPY_SKIP.some((p) => p.test(value))) continue;
892
+ const entropy = shannonEntropy(value);
893
+ if (entropy >= threshold) {
894
+ findings.push({
895
+ type: "high-entropy",
896
+ file: filePath,
897
+ line: i + 1,
898
+ match: value,
899
+ redacted: redactSecret(value),
900
+ severity: entropy >= 5 ? "high" : "medium"
901
+ });
902
+ }
903
+ }
904
+ }
905
+ return deduplicateFindings(findings);
906
+ }
907
+ async function auditProject(projectPath, filePaths, options = {}) {
908
+ const { customPatterns = [], entropyThreshold = 4.5, includePII = true } = options;
909
+ const allFindings = [];
910
+ const filesWithSecrets = /* @__PURE__ */ new Set();
911
+ for (const fp of filePaths) {
912
+ try {
913
+ const content = await readFile3(fp, "utf-8");
914
+ const relPath = relative3(resolve3(projectPath), resolve3(fp));
915
+ const isTestFile = /\.(test|spec|mock)\.[jt]sx?$/.test(relPath) || relPath.includes("__tests__");
916
+ const isDtsFile = relPath.endsWith(".d.ts");
917
+ let findings = scanContentForSecrets(content, relPath, customPatterns);
918
+ if (!includePII) {
919
+ findings = findings.filter((f) => f.type !== "pii");
920
+ }
921
+ const entropyFindings = isTestFile || isDtsFile ? [] : scanContentForHighEntropy(content, relPath, entropyThreshold);
922
+ const combined = [...findings, ...entropyFindings];
923
+ if (combined.length > 0) {
924
+ filesWithSecrets.add(relPath);
925
+ allFindings.push(...combined);
926
+ }
927
+ } catch {
928
+ }
929
+ }
930
+ allFindings.sort((a, b) => {
931
+ const order = { critical: 0, high: 1, medium: 2, low: 3 };
932
+ return order[a.severity] - order[b.severity];
933
+ });
934
+ const bySeverity = { critical: 0, high: 0, medium: 0, low: 0 };
935
+ const byType = {};
936
+ for (const f of allFindings) {
937
+ bySeverity[f.severity]++;
938
+ byType[f.type] = (byType[f.type] || 0) + 1;
939
+ }
940
+ const recommendations = [];
941
+ if (bySeverity.critical > 0) {
942
+ recommendations.push("CRITICAL: Rotate all detected credentials immediately. They may already be compromised.");
943
+ }
944
+ if (byType["password"] > 0) {
945
+ recommendations.push("Move passwords to environment variables or a secrets manager (AWS Secrets Manager, Vault, etc.).");
946
+ }
947
+ if (byType["api-key"] > 0 || byType["aws-key"] > 0) {
948
+ recommendations.push("Use environment variables for API keys. Never commit them to source control.");
949
+ }
950
+ if (byType["connection-string"] > 0) {
951
+ recommendations.push("Database connection strings should use environment variables, not hardcoded values.");
952
+ }
953
+ if (byType["private-key"] > 0) {
954
+ recommendations.push("Private keys should NEVER be in source code. Use a key management service.");
955
+ }
956
+ if (byType["pii"] > 0) {
957
+ recommendations.push("PII detected. Review for GDPR/CCPA compliance. Consider data anonymization.");
958
+ }
959
+ if (byType["high-entropy"] > 0) {
960
+ recommendations.push("High-entropy strings detected that may be secrets. Review manually.");
961
+ }
962
+ if (allFindings.length > 0) {
963
+ recommendations.push("Add a .gitignore entry for .env files if not already present.");
964
+ recommendations.push("Run `npx cto-ai-cli --audit` regularly or add to CI pipeline.");
965
+ }
966
+ if (allFindings.length === 0) {
967
+ recommendations.push("No secrets detected. Great job keeping your codebase clean!");
968
+ }
969
+ return {
970
+ findings: allFindings,
971
+ summary: {
972
+ totalFiles: filePaths.length,
973
+ filesScanned: filePaths.length,
974
+ filesWithSecrets: filesWithSecrets.size,
975
+ totalFindings: allFindings.length,
976
+ bySeverity,
977
+ byType
978
+ },
979
+ recommendations
980
+ };
981
+ }
830
982
 
831
983
  // src/engine/pruner.ts
832
984
  import { Project as Project2, SyntaxKind as SyntaxKind2 } from "ts-morph";
@@ -1887,6 +2039,7 @@ async function main() {
1887
2039
  const fixMode = args.includes("--fix");
1888
2040
  const reportMode = args.includes("--report");
1889
2041
  const compareMode = args.includes("--compare");
2042
+ const auditMode = args.includes("--audit");
1890
2043
  const helpMode = args.includes("--help") || args.includes("-h");
1891
2044
  const contextIdx = args.indexOf("--context");
1892
2045
  const contextTask = contextIdx !== -1 && args[contextIdx + 1] ? args[contextIdx + 1] : null;
@@ -1904,6 +2057,7 @@ async function main() {
1904
2057
  npx cto-ai-cli --context "your task" Generate task-specific context
1905
2058
  npx cto-ai-cli --report Generate shareable markdown report
1906
2059
  npx cto-ai-cli --compare Compare your score vs popular projects
2060
+ npx cto-ai-cli --audit Security audit: detect secrets & PII
1907
2061
  npx cto-ai-cli --json Output as JSON (for CI/scripts)
1908
2062
 
1909
2063
  What it does:
@@ -1964,6 +2118,9 @@ async function main() {
1964
2118
  if (compareMode) {
1965
2119
  runCompare(score);
1966
2120
  }
2121
+ if (auditMode) {
2122
+ await runAudit(projectPath, analysis);
2123
+ }
1967
2124
  console.log("");
1968
2125
  console.log(` Scanned in ${elapsed}s \xB7 ${analysis.totalFiles} files \xB7 ${Math.round(analysis.totalTokens / 1e3)}K tokens`);
1969
2126
  console.log("");
@@ -2365,6 +2522,188 @@ function renderCompareBar(pct) {
2365
2522
  const empty = width - filled;
2366
2523
  return "\u2588".repeat(filled) + "\u2591".repeat(empty);
2367
2524
  }
2525
+ async function runAudit(projectPath, analysis) {
2526
+ console.log("");
2527
+ console.log(" \u{1F50D} Running security audit...");
2528
+ console.log("");
2529
+ const filePaths = analysis.files.map((f) => f.path);
2530
+ const result = await auditProject(projectPath, filePaths, { includePII: true });
2531
+ const { summary, findings, recommendations } = result;
2532
+ const statusIcon = summary.bySeverity.critical > 0 ? "\u{1F534}" : summary.bySeverity.high > 0 ? "\u{1F7E0}" : summary.totalFindings > 0 ? "\u{1F7E1}" : "\u{1F7E2}";
2533
+ const statusText = summary.bySeverity.critical > 0 ? "CRITICAL ISSUES FOUND" : summary.bySeverity.high > 0 ? "HIGH-SEVERITY ISSUES FOUND" : summary.totalFindings > 0 ? "MINOR ISSUES FOUND" : "ALL CLEAR";
2534
+ console.log(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
2535
+ console.log(" \u2551 \u2551");
2536
+ console.log(` \u2551 ${statusIcon} Security Audit: ${statusText.padEnd(28)} \u2551`);
2537
+ console.log(" \u2551 \u2551");
2538
+ console.log(` \u2551 Files scanned: ${summary.filesScanned.toString().padEnd(30)} \u2551`);
2539
+ console.log(` \u2551 Files affected: ${summary.filesWithSecrets.toString().padEnd(30)} \u2551`);
2540
+ console.log(` \u2551 Total findings: ${summary.totalFindings.toString().padEnd(30)} \u2551`);
2541
+ console.log(" \u2551 \u2551");
2542
+ console.log(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
2543
+ console.log(" \u2551 \u2551");
2544
+ if (summary.bySeverity.critical > 0) {
2545
+ console.log(` \u2551 \u{1F534} Critical: ${summary.bySeverity.critical.toString().padEnd(33)} \u2551`);
2546
+ }
2547
+ if (summary.bySeverity.high > 0) {
2548
+ console.log(` \u2551 \u{1F7E0} High: ${summary.bySeverity.high.toString().padEnd(33)} \u2551`);
2549
+ }
2550
+ if (summary.bySeverity.medium > 0) {
2551
+ console.log(` \u2551 \u{1F7E1} Medium: ${summary.bySeverity.medium.toString().padEnd(33)} \u2551`);
2552
+ }
2553
+ if (summary.bySeverity.low > 0) {
2554
+ console.log(` \u2551 \u{1F535} Low: ${summary.bySeverity.low.toString().padEnd(33)} \u2551`);
2555
+ }
2556
+ if (summary.totalFindings === 0) {
2557
+ console.log(" \u2551 \u2705 No secrets or PII detected \u2551");
2558
+ }
2559
+ console.log(" \u2551 \u2551");
2560
+ if (Object.keys(summary.byType).length > 0) {
2561
+ console.log(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
2562
+ console.log(" \u2551 \u2551");
2563
+ console.log(" \u2551 By type: \u2551");
2564
+ for (const [type, count] of Object.entries(summary.byType)) {
2565
+ const label = type.padEnd(18);
2566
+ console.log(` \u2551 ${label} ${count.toString().padEnd(28)} \u2551`);
2567
+ }
2568
+ console.log(" \u2551 \u2551");
2569
+ }
2570
+ console.log(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
2571
+ if (findings.length > 0) {
2572
+ console.log("");
2573
+ console.log(" Findings:");
2574
+ console.log("");
2575
+ const shown = findings.slice(0, 15);
2576
+ for (const f of shown) {
2577
+ const icon = f.severity === "critical" ? "\u{1F534}" : f.severity === "high" ? "\u{1F7E0}" : f.severity === "medium" ? "\u{1F7E1}" : "\u{1F535}";
2578
+ const sev = f.severity.toUpperCase().padEnd(8);
2579
+ console.log(` ${icon} ${sev} ${f.file}:${f.line}`);
2580
+ console.log(` ${f.type}: ${f.redacted}`);
2581
+ }
2582
+ if (findings.length > 15) {
2583
+ console.log(` ... and ${findings.length - 15} more (see .cto/audit/ for full report)`);
2584
+ }
2585
+ }
2586
+ if (recommendations.length > 0) {
2587
+ console.log("");
2588
+ console.log(" Recommendations:");
2589
+ console.log("");
2590
+ for (const rec of recommendations) {
2591
+ const icon = rec.startsWith("CRITICAL") ? "\u{1F6A8}" : "\u{1F4A1}";
2592
+ console.log(` ${icon} ${rec}`);
2593
+ }
2594
+ }
2595
+ const ctoDir = join4(projectPath, ".cto");
2596
+ const auditDir = join4(ctoDir, "audit");
2597
+ mkdirSync(auditDir, { recursive: true });
2598
+ const now = /* @__PURE__ */ new Date();
2599
+ const dateStr = now.toISOString().split("T")[0];
2600
+ const logFile = join4(auditDir, `${dateStr}.jsonl`);
2601
+ const logEntry = {
2602
+ timestamp: now.toISOString(),
2603
+ version: "3.2.0",
2604
+ summary: {
2605
+ filesScanned: summary.filesScanned,
2606
+ filesWithSecrets: summary.filesWithSecrets,
2607
+ totalFindings: summary.totalFindings,
2608
+ bySeverity: summary.bySeverity,
2609
+ byType: summary.byType
2610
+ },
2611
+ findings: findings.map((f) => ({
2612
+ type: f.type,
2613
+ file: f.file,
2614
+ line: f.line,
2615
+ severity: f.severity,
2616
+ redacted: f.redacted
2617
+ }))
2618
+ };
2619
+ appendFileSync(logFile, JSON.stringify(logEntry) + "\n");
2620
+ let report = `# Security Audit Report
2621
+
2622
+ `;
2623
+ report += `> Generated by cto-ai-cli on ${now.toISOString()}
2624
+
2625
+ `;
2626
+ report += `## Summary
2627
+
2628
+ `;
2629
+ report += `| Metric | Value |
2630
+ `;
2631
+ report += `|--------|-------|
2632
+ `;
2633
+ report += `| Files scanned | ${summary.filesScanned} |
2634
+ `;
2635
+ report += `| Files with issues | ${summary.filesWithSecrets} |
2636
+ `;
2637
+ report += `| Total findings | ${summary.totalFindings} |
2638
+ `;
2639
+ report += `| Critical | ${summary.bySeverity.critical} |
2640
+ `;
2641
+ report += `| High | ${summary.bySeverity.high} |
2642
+ `;
2643
+ report += `| Medium | ${summary.bySeverity.medium} |
2644
+
2645
+ `;
2646
+ if (findings.length > 0) {
2647
+ report += `## Findings
2648
+
2649
+ `;
2650
+ report += `| Severity | Type | File | Line | Redacted |
2651
+ `;
2652
+ report += `|----------|------|------|------|----------|
2653
+ `;
2654
+ for (const f of findings) {
2655
+ report += `| ${f.severity} | ${f.type} | ${f.file} | ${f.line} | \`${f.redacted.slice(0, 30)}\` |
2656
+ `;
2657
+ }
2658
+ report += "\n";
2659
+ }
2660
+ if (recommendations.length > 0) {
2661
+ report += `## Recommendations
2662
+
2663
+ `;
2664
+ for (const rec of recommendations) {
2665
+ report += `- ${rec}
2666
+ `;
2667
+ }
2668
+ }
2669
+ writeFileSync(join4(auditDir, "report.md"), report);
2670
+ const envSecrets = findings.filter(
2671
+ (f) => f.type === "env-variable" || f.type === "password" || f.type === "api-key" || f.type === "aws-key" || f.type === "connection-string"
2672
+ );
2673
+ if (envSecrets.length > 0) {
2674
+ const envVarNames = /* @__PURE__ */ new Set();
2675
+ for (const f of envSecrets) {
2676
+ const varMatch = f.match.match(/^([A-Z_][A-Z0-9_]*)\s*[:=]/i);
2677
+ if (varMatch) {
2678
+ envVarNames.add(varMatch[1].toUpperCase());
2679
+ } else {
2680
+ const name = f.type.toUpperCase().replace(/-/g, "_");
2681
+ envVarNames.add(name);
2682
+ }
2683
+ }
2684
+ if (envVarNames.size > 0) {
2685
+ let envExample = "# Environment variables \u2014 NEVER commit real values\n";
2686
+ envExample += "# Generated by cto-ai-cli --audit\n\n";
2687
+ for (const name of envVarNames) {
2688
+ envExample += `${name}=your_${name.toLowerCase()}_here
2689
+ `;
2690
+ }
2691
+ writeFileSync(join4(ctoDir, ".env.example"), envExample);
2692
+ }
2693
+ }
2694
+ console.log("");
2695
+ console.log(" \u{1F4C1} Audit artifacts:");
2696
+ console.log(` \u{1F4CB} .cto/audit/${dateStr}.jsonl Audit log (append-only)`);
2697
+ console.log(" \u{1F4CA} .cto/audit/report.md Full report");
2698
+ if (envSecrets.length > 0) {
2699
+ console.log(" \u{1F4DD} .cto/.env.example Template for environment variables");
2700
+ }
2701
+ console.log("");
2702
+ if (process.env.CI && (summary.bySeverity.critical > 0 || summary.bySeverity.high > 0)) {
2703
+ console.log(" \u274C CI mode: Failing due to critical/high severity findings.");
2704
+ process.exit(1);
2705
+ }
2706
+ }
2368
2707
  function formatTokens(n) {
2369
2708
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
2370
2709
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
@@ -1336,7 +1336,29 @@ var BUILTIN_PATTERNS = [
1336
1336
  { type: "connection-string", source: `(?:mongodb(?:\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\/\\/[^\\s'"]+:[^\\s'"]+@[^\\s'"]+`, flags: "gi", severity: "critical", description: "Database Connection String" },
1337
1337
  { type: "connection-string", source: `(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\s*[:=]\\s*['"]?([^\\s'"]{10,})['"]?`, flags: "gi", severity: "high", description: "Database URL" },
1338
1338
  // Environment variables with secrets
1339
- { type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" }
1339
+ { type: "env-variable", source: `(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\s*[:=]\\s*['"]?([^\\s'"]{8,})['"]?`, flags: "gi", severity: "high", description: "Secret Environment Variable" },
1340
+ // Stripe
1341
+ { type: "api-key", source: "sk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Live Secret Key" },
1342
+ { type: "api-key", source: "pk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "high", description: "Stripe Live Publishable Key" },
1343
+ { type: "api-key", source: "rk_live_[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Stripe Restricted Key" },
1344
+ // Slack
1345
+ { type: "token", source: "xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack Bot Token" },
1346
+ { type: "token", source: "xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}", flags: "g", severity: "critical", description: "Slack User Token" },
1347
+ { type: "api-key", source: "https://hooks\\.slack\\.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+", flags: "g", severity: "high", description: "Slack Webhook URL" },
1348
+ // Google
1349
+ { type: "api-key", source: "AIza[0-9A-Za-z_-]{35}", flags: "g", severity: "high", description: "Google API Key" },
1350
+ { type: "token", source: "ya29\\.[0-9A-Za-z_-]+", flags: "g", severity: "high", description: "Google OAuth Token" },
1351
+ // Azure
1352
+ { type: "api-key", source: "(?:AccountKey|SharedAccessKey)\\s*=\\s*[a-zA-Z0-9+/=]{40,}", flags: "g", severity: "critical", description: "Azure Storage Key" },
1353
+ // Twilio
1354
+ { type: "api-key", source: "AC[a-f0-9]{32}", flags: "g", severity: "high", description: "Twilio Account SID" },
1355
+ // SendGrid
1356
+ { type: "api-key", source: "SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}", flags: "g", severity: "critical", description: "SendGrid API Key" },
1357
+ // JWT
1358
+ { type: "token", source: "eyJ[a-zA-Z0-9_-]{10,}\\.eyJ[a-zA-Z0-9_-]{10,}\\.[a-zA-Z0-9_-]{10,}", flags: "g", severity: "high", description: "JSON Web Token" },
1359
+ // PII
1360
+ { type: "pii", source: "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b", flags: "g", severity: "medium", description: "Email Address (PII)" },
1361
+ { type: "pii", source: "\\b\\d{3}[-.]?\\d{2}[-.]?\\d{4}\\b", flags: "g", severity: "high", description: "Possible SSN (PII)" }
1340
1362
  ];
1341
1363
  function buildPatterns(customPatterns = []) {
1342
1364
  const patterns = BUILTIN_PATTERNS.map((def) => ({