@rigour-labs/core 5.0.0 → 5.1.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.
Files changed (139) hide show
  1. package/README.md +9 -1
  2. package/dist/gates/agent-team.d.ts +0 -1
  3. package/dist/gates/agent-team.js +0 -1
  4. package/dist/gates/checkpoint.d.ts +0 -2
  5. package/dist/gates/checkpoint.js +0 -2
  6. package/dist/gates/context-window-artifacts.d.ts +6 -2
  7. package/dist/gates/context-window-artifacts.js +107 -31
  8. package/dist/gates/deep-analysis.d.ts +2 -0
  9. package/dist/gates/deep-analysis.js +41 -11
  10. package/dist/gates/dependency.d.ts +0 -2
  11. package/dist/gates/dependency.js +23 -5
  12. package/dist/gates/deprecated-apis.d.ts +0 -2
  13. package/dist/gates/deprecated-apis.js +33 -20
  14. package/dist/gates/duplication-drift/index.d.ts +61 -0
  15. package/dist/gates/duplication-drift/index.js +240 -0
  16. package/dist/gates/duplication-drift/similarity.d.ts +68 -0
  17. package/dist/gates/duplication-drift/similarity.js +177 -0
  18. package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
  19. package/dist/gates/duplication-drift/tokenizer.js +195 -0
  20. package/dist/gates/frontend-secret-exposure.d.ts +0 -3
  21. package/dist/gates/frontend-secret-exposure.js +1 -114
  22. package/dist/gates/frontend-secret-patterns.d.ts +33 -0
  23. package/dist/gates/frontend-secret-patterns.js +119 -0
  24. package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
  25. package/dist/gates/hallucinated-imports/index.js +174 -0
  26. package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
  27. package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
  28. package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
  29. package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
  30. package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
  31. package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
  32. package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
  33. package/dist/gates/hallucinated-imports-lang.js +269 -34
  34. package/dist/gates/hallucinated-imports.test.js +1 -2
  35. package/dist/gates/inconsistent-error-handling.d.ts +0 -5
  36. package/dist/gates/inconsistent-error-handling.js +15 -144
  37. package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
  38. package/dist/gates/language-adapters/csharp-adapter.js +211 -0
  39. package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
  40. package/dist/gates/language-adapters/go-adapter.js +195 -0
  41. package/dist/gates/language-adapters/index.d.ts +15 -0
  42. package/dist/gates/language-adapters/index.js +16 -0
  43. package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
  44. package/dist/gates/language-adapters/java-adapter.js +237 -0
  45. package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
  46. package/dist/gates/language-adapters/js-adapter.js +279 -0
  47. package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
  48. package/dist/gates/language-adapters/python-adapter.js +183 -0
  49. package/dist/gates/language-adapters/registry.d.ts +26 -0
  50. package/dist/gates/language-adapters/registry.js +65 -0
  51. package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
  52. package/dist/gates/language-adapters/ruby-adapter.js +217 -0
  53. package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
  54. package/dist/gates/language-adapters/rust-adapter.js +235 -0
  55. package/dist/gates/language-adapters/types.d.ts +60 -0
  56. package/dist/gates/language-adapters/types.js +22 -0
  57. package/dist/gates/logic-drift-extractors.d.ts +15 -0
  58. package/dist/gates/logic-drift-extractors.js +34 -0
  59. package/dist/gates/logic-drift.d.ts +0 -30
  60. package/dist/gates/logic-drift.js +39 -129
  61. package/dist/gates/phantom-apis.d.ts +0 -2
  62. package/dist/gates/phantom-apis.js +49 -20
  63. package/dist/gates/promise-safety.d.ts +0 -1
  64. package/dist/gates/promise-safety.js +14 -2
  65. package/dist/gates/runner.js +51 -22
  66. package/dist/gates/security-patterns-data.d.ts +14 -0
  67. package/dist/gates/security-patterns-data.js +235 -0
  68. package/dist/gates/security-patterns.d.ts +17 -3
  69. package/dist/gates/security-patterns.js +80 -211
  70. package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
  71. package/dist/gates/side-effect-analysis/categorizer.js +83 -0
  72. package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
  73. package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
  74. package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
  75. package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
  76. package/dist/gates/side-effect-helpers/index.d.ts +4 -0
  77. package/dist/gates/side-effect-helpers/index.js +4 -0
  78. package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
  79. package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
  80. package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
  81. package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
  82. package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
  83. package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
  84. package/dist/gates/side-effect-helpers/types.d.ts +38 -0
  85. package/dist/gates/side-effect-helpers/types.js +41 -0
  86. package/dist/gates/side-effect-rules.d.ts +0 -1
  87. package/dist/gates/side-effect-rules.js +0 -1
  88. package/dist/gates/style-drift-rules.d.ts +86 -0
  89. package/dist/gates/style-drift-rules.js +103 -0
  90. package/dist/gates/style-drift.d.ts +7 -16
  91. package/dist/gates/style-drift.js +101 -119
  92. package/dist/gates/test-quality-matchers.d.ts +53 -0
  93. package/dist/gates/test-quality-matchers.js +86 -0
  94. package/dist/gates/test-quality.d.ts +0 -3
  95. package/dist/gates/test-quality.js +47 -44
  96. package/dist/hooks/checker.d.ts +0 -1
  97. package/dist/hooks/checker.js +1 -3
  98. package/dist/hooks/dlp-templates.d.ts +0 -1
  99. package/dist/hooks/dlp-templates.js +0 -4
  100. package/dist/hooks/index.d.ts +0 -2
  101. package/dist/hooks/index.js +0 -2
  102. package/dist/hooks/input-validator.d.ts +0 -1
  103. package/dist/hooks/input-validator.js +0 -1
  104. package/dist/hooks/input-validator.test.js +0 -1
  105. package/dist/hooks/standalone-checker.d.ts +0 -1
  106. package/dist/hooks/standalone-checker.js +0 -1
  107. package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
  108. package/dist/hooks/standalone-dlp-checker.js +0 -1
  109. package/dist/hooks/templates.d.ts +6 -1
  110. package/dist/hooks/templates.js +6 -1
  111. package/dist/hooks/types.d.ts +1 -2
  112. package/dist/hooks/types.js +1 -1
  113. package/dist/index.d.ts +1 -1
  114. package/dist/index.js +1 -1
  115. package/dist/services/adaptive-thresholds.d.ts +0 -2
  116. package/dist/services/adaptive-thresholds.js +0 -2
  117. package/dist/services/filesystem-cache.d.ts +0 -1
  118. package/dist/services/filesystem-cache.js +0 -1
  119. package/dist/services/score-history.d.ts +0 -1
  120. package/dist/services/score-history.js +0 -1
  121. package/dist/services/temporal-drift.d.ts +1 -2
  122. package/dist/services/temporal-drift.js +7 -8
  123. package/dist/storage/db.d.ts +23 -7
  124. package/dist/storage/db.js +116 -55
  125. package/dist/storage/findings.d.ts +4 -3
  126. package/dist/storage/findings.js +13 -20
  127. package/dist/storage/local-memory.d.ts +4 -4
  128. package/dist/storage/local-memory.js +20 -22
  129. package/dist/storage/patterns.d.ts +5 -5
  130. package/dist/storage/patterns.js +20 -26
  131. package/dist/storage/scans.d.ts +6 -6
  132. package/dist/storage/scans.js +12 -21
  133. package/dist/types/index.d.ts +1 -0
  134. package/dist/utils/scanner.js +1 -1
  135. package/package.json +7 -8
  136. package/dist/gates/duplication-drift.d.ts +0 -128
  137. package/dist/gates/duplication-drift.js +0 -585
  138. package/dist/gates/hallucinated-imports.js +0 -641
  139. package/dist/gates/side-effect-helpers.d.ts +0 -260
@@ -16,12 +16,11 @@
16
16
  * C# — Common .NET hallucinated APIs (LINQ, File I/O, string methods)
17
17
  * Java — Common hallucinated JDK APIs (Collections, String, Stream, Files)
18
18
  *
19
- * @since v3.0.0
20
- * @since v3.0.3 — Go, C#, Java pattern-based detection added
21
19
  */
22
20
  import { Gate } from './base.js';
23
21
  import { FileScanner } from '../utils/scanner.js';
24
22
  import { Logger } from '../utils/logger.js';
23
+ import { languageAdapters } from './language-adapters/index.js';
25
24
  import fs from 'fs-extra';
26
25
  import path from 'path';
27
26
  import { GO_PHANTOM_RULES, CSHARP_PHANTOM_RULES, JAVA_PHANTOM_RULES, NODE_STDLIB_METHODS, PYTHON_STDLIB_METHODS } from './phantom-apis-data.js';
@@ -63,23 +62,41 @@ export class PhantomApisGate extends Gate {
63
62
  const fullPath = path.join(context.cwd, file);
64
63
  const content = await fs.readFile(fullPath, 'utf-8');
65
64
  const ext = path.extname(file);
66
- if (['.ts', '.js', '.tsx', '.jsx'].includes(ext) && this.config.check_node) {
67
- this.checkNodePhantomApis(content, file, phantoms);
68
- }
69
- else if (ext === '.py' && this.config.check_python) {
70
- this.checkPythonPhantomApis(content, file, phantoms);
71
- }
72
- else if (ext === '.go' && this.config.check_go) {
73
- this.checkGoPhantomApis(content, file, phantoms);
74
- }
75
- else if (ext === '.cs' && this.config.check_csharp) {
76
- this.checkCSharpPhantomApis(content, file, phantoms);
77
- }
78
- else if (ext === '.java' && this.config.check_java) {
79
- this.checkJavaPhantomApis(content, file, phantoms);
80
- }
81
- else if (ext === '.kt' && this.config.check_java) {
82
- this.checkKotlinPhantomApis(content, file, phantoms);
65
+ const adapter = languageAdapters.getAdapter(file);
66
+ if (!adapter)
67
+ continue;
68
+ /** Map adapter IDs to config flags */
69
+ const configCheck = {
70
+ js: this.config.check_node,
71
+ python: this.config.check_python,
72
+ go: this.config.check_go,
73
+ csharp: this.config.check_csharp,
74
+ java: this.config.check_java,
75
+ };
76
+ if (configCheck[adapter.id] === false)
77
+ continue;
78
+ switch (adapter.id) {
79
+ case 'js':
80
+ this.checkNodePhantomApis(content, file, phantoms);
81
+ break;
82
+ case 'python':
83
+ this.checkPythonPhantomApis(content, file, phantoms);
84
+ break;
85
+ case 'go':
86
+ this.checkGoPhantomApis(content, file, phantoms);
87
+ break;
88
+ case 'csharp':
89
+ this.checkCSharpPhantomApis(content, file, phantoms);
90
+ break;
91
+ case 'java':
92
+ // Java adapter handles both .java and .kt via extensions
93
+ if (ext === '.kt') {
94
+ this.checkKotlinPhantomApis(content, file, phantoms);
95
+ }
96
+ else {
97
+ this.checkJavaPhantomApis(content, file, phantoms);
98
+ }
99
+ break;
83
100
  }
84
101
  }
85
102
  catch { /* skip unreadable files */ }
@@ -152,6 +169,14 @@ export class PhantomApisGate extends Gate {
152
169
  const requireImport = line.match(/(?:const|let|var)\s+(\w+)\s*=\s*require\s*\(\s*['"](?:node:)?(fs|path|crypto|os|child_process|http|https|url|util|stream|events|buffer|querystring|net|dns|tls|zlib|readline|cluster|worker_threads|timers|perf_hooks|assert)['"]\s*\)/);
153
170
  if (requireImport) {
154
171
  moduleAliases.set(requireImport[1], requireImport[2]);
172
+ continue;
173
+ }
174
+ // import { readFile, writeFile } from 'fs' — destructured imports are NOT phantom API candidates
175
+ // since they reference individual exports, not module.method() calls
176
+ // import fs/promises or fs.promises patterns
177
+ const promisesImport = line.match(/import\s+(?:\*\s+as\s+)?(\w+)\s+from\s+['"](?:node:)?(fs)\/promises['"]/);
178
+ if (promisesImport) {
179
+ moduleAliases.set(promisesImport[1], 'fs/promises');
155
180
  }
156
181
  }
157
182
  if (moduleAliases.size === 0)
@@ -249,7 +274,11 @@ export class PhantomApisGate extends Gate {
249
274
  }
250
275
  findClosest(target, candidates) {
251
276
  let best = null;
252
- let bestDist = 4; // max distance threshold
277
+ // Adaptive threshold: short names (< 6 chars) get max distance 1,
278
+ // medium names (6-10) get 2, long names get 3.
279
+ // This prevents false suggestions like "read" → "readdir" for short phantoms.
280
+ const maxDist = target.length < 6 ? 2 : target.length <= 10 ? 3 : 4;
281
+ let bestDist = maxDist;
253
282
  for (const c of candidates) {
254
283
  const dist = this.levenshtein(target.toLowerCase(), c.toLowerCase());
255
284
  if (dist < bestDist) {
@@ -4,7 +4,6 @@
4
4
  * Detects unsafe async/promise/error patterns that AI code generators commonly produce.
5
5
  * Supports: JS/TS, Python, Go, Ruby, C#/.NET
6
6
  *
7
- * @since v2.17.0
8
7
  */
9
8
  import { Gate, GateContext } from './base.js';
10
9
  import { Failure, Provenance } from '../types/index.js';
@@ -4,7 +4,6 @@
4
4
  * Detects unsafe async/promise/error patterns that AI code generators commonly produce.
5
5
  * Supports: JS/TS, Python, Go, Ruby, C#/.NET
6
6
  *
7
- * @since v2.17.0
8
7
  */
9
8
  import { Gate } from './base.js';
10
9
  import { FileScanner } from '../utils/scanner.js';
@@ -88,12 +87,25 @@ export class PromiseSafetyGate extends Gate {
88
87
  if (!/\.then\s*\(/.test(line))
89
88
  continue;
90
89
  let hasCatch = false;
91
- for (let j = i; j < Math.min(i + 50, lines.length); j++) {
90
+ // Scan ahead until we hit a statement boundary (no arbitrary limit).
91
+ // A "statement boundary" is a new declaration, function, class, or blank line after content.
92
+ let braceDepth = 0;
93
+ for (let j = i; j < lines.length; j++) {
92
94
  const lookahead = this.sanitizeLine(lines[j]);
95
+ // Track brace depth to stay within the same scope
96
+ for (const ch of lookahead) {
97
+ if (ch === '{')
98
+ braceDepth++;
99
+ if (ch === '}')
100
+ braceDepth--;
101
+ }
93
102
  if (/\.catch\s*\(/.test(lookahead)) {
94
103
  hasCatch = true;
95
104
  break;
96
105
  }
106
+ // Also count .then().then().catch() chains — the catch at the end covers all
107
+ if (j > i && braceDepth < 0)
108
+ break; // Exited enclosing scope
97
109
  if (j > i && /^(?:const|let|var|function|class|export|import|if|for|while|return)\b/.test(lookahead.trim()))
98
110
  break;
99
111
  }
@@ -17,15 +17,15 @@ import { AgentTeamGate } from './agent-team.js';
17
17
  import { CheckpointGate } from './checkpoint.js';
18
18
  import { SecurityPatternsGate } from './security-patterns.js';
19
19
  import { FrontendSecretExposureGate } from './frontend-secret-exposure.js';
20
- import { DuplicationDriftGate } from './duplication-drift.js';
21
- import { HallucinatedImportsGate } from './hallucinated-imports.js';
20
+ import { DuplicationDriftGate } from './duplication-drift/index.js';
21
+ import { HallucinatedImportsGate } from './hallucinated-imports/index.js';
22
22
  import { InconsistentErrorHandlingGate } from './inconsistent-error-handling.js';
23
23
  import { ContextWindowArtifactsGate } from './context-window-artifacts.js';
24
24
  import { PromiseSafetyGate } from './promise-safety.js';
25
25
  import { PhantomApisGate } from './phantom-apis.js';
26
26
  import { DeprecatedApisGate } from './deprecated-apis.js';
27
27
  import { TestQualityGate } from './test-quality.js';
28
- import { SideEffectAnalysisGate } from './side-effect-analysis.js';
28
+ import { SideEffectAnalysisGate } from './side-effect-analysis/index.js';
29
29
  import { StyleDriftGate } from './style-drift.js';
30
30
  import { LogicDriftGate } from './logic-drift.js';
31
31
  import { execa } from 'execa';
@@ -233,22 +233,45 @@ export class GateRunner {
233
233
  }
234
234
  }
235
235
  const status = failures.length > 0 ? 'FAIL' : 'PASS';
236
- // Severity-weighted scoring: each failure deducts based on its severity
236
+ // Severity-weighted scoring with per-gate cap and deduplication
237
+ // Step 1: Deduplicate — same file + line + gate should not stack
238
+ const deduped = [];
239
+ const seen = new Set();
240
+ for (const f of failures) {
241
+ const key = `${f.id}:${(f.files || []).join(',')}:${f.line || 0}`;
242
+ if (!seen.has(key)) {
243
+ seen.add(key);
244
+ deduped.push(f);
245
+ }
246
+ }
247
+ // Replace failures array with deduplicated version
248
+ failures.length = 0;
249
+ failures.push(...deduped);
250
+ // Step 2: Calculate per-gate deductions with cap
251
+ const PER_GATE_CAP = 30; // No single gate can deduct more than 30 points
237
252
  const severityBreakdown = {};
238
- let totalDeduction = 0;
253
+ const gateDeductions = new Map();
239
254
  for (const f of failures) {
240
255
  const sev = (f.severity || 'medium');
241
256
  severityBreakdown[sev] = (severityBreakdown[sev] || 0) + 1;
242
- totalDeduction += SEVERITY_WEIGHTS[sev] ?? 5;
257
+ const weight = SEVERITY_WEIGHTS[sev] ?? 5;
258
+ const gateId = f.id || 'unknown';
259
+ gateDeductions.set(gateId, (gateDeductions.get(gateId) || 0) + weight);
260
+ }
261
+ // Step 3: Apply cap per gate and sum
262
+ let totalDeduction = 0;
263
+ for (const [_gateId, deduction] of gateDeductions) {
264
+ totalDeduction += Math.min(deduction, PER_GATE_CAP);
243
265
  }
244
266
  const score = Math.max(0, 100 - totalDeduction);
245
267
  // Two-score system: separate AI health from structural quality
246
268
  // IMPORTANT: Only ai-drift affects ai_health_score, only traditional affects structural_score.
247
269
  // Security and governance affect the overall score but NOT the sub-scores,
248
270
  // preventing security criticals from incorrectly zeroing structural_score.
249
- let aiDeduction = 0;
250
- let structuralDeduction = 0;
251
- let deepDeduction = 0;
271
+ // Per-gate caps applied to sub-scores too for fairness.
272
+ const aiGateDeductions = new Map();
273
+ const structuralGateDeductions = new Map();
274
+ const deepGateDeductions = new Map();
252
275
  const provenanceCounts = {
253
276
  'ai-drift': 0,
254
277
  'traditional': 0,
@@ -260,24 +283,33 @@ export class GateRunner {
260
283
  const sev = (f.severity || 'medium');
261
284
  const weight = SEVERITY_WEIGHTS[sev] ?? 5;
262
285
  const prov = f.provenance || 'traditional';
286
+ const gateId = f.id || 'unknown';
263
287
  provenanceCounts[prov] = (provenanceCounts[prov] || 0) + 1;
264
288
  switch (prov) {
265
289
  case 'ai-drift':
266
- aiDeduction += weight;
290
+ aiGateDeductions.set(gateId, (aiGateDeductions.get(gateId) || 0) + weight);
267
291
  break;
268
292
  case 'traditional':
269
- structuralDeduction += weight;
293
+ structuralGateDeductions.set(gateId, (structuralGateDeductions.get(gateId) || 0) + weight);
270
294
  break;
271
295
  case 'deep-analysis':
272
- deepDeduction += weight;
296
+ deepGateDeductions.set(gateId, (deepGateDeductions.get(gateId) || 0) + weight);
273
297
  break;
274
- // security and governance contribute to overall score (totalDeduction)
275
- // but do NOT pollute the sub-scores
276
298
  case 'security':
277
299
  case 'governance':
278
300
  break;
279
301
  }
280
302
  }
303
+ // Apply per-gate cap to sub-scores
304
+ let aiDeduction = 0;
305
+ for (const [, d] of aiGateDeductions)
306
+ aiDeduction += Math.min(d, PER_GATE_CAP);
307
+ let structuralDeduction = 0;
308
+ for (const [, d] of structuralGateDeductions)
309
+ structuralDeduction += Math.min(d, PER_GATE_CAP);
310
+ let deepDeduction = 0;
311
+ for (const [, d] of deepGateDeductions)
312
+ deepDeduction += Math.min(d, PER_GATE_CAP);
281
313
  // Persist findings + reinforce patterns in local SQLite (fire-and-forget)
282
314
  const report = {
283
315
  status,
@@ -295,15 +327,12 @@ export class GateRunner {
295
327
  },
296
328
  };
297
329
  // Store findings + reinforce patterns in local SQLite (non-blocking)
298
- try {
299
- persistAndReinforce(cwd, report, deepStats ? {
300
- deepTier: deepStats.tier,
301
- deepModel: deepStats.model,
302
- } : undefined);
303
- }
304
- catch {
330
+ persistAndReinforce(cwd, report, deepStats ? {
331
+ deepTier: deepStats.tier,
332
+ deepModel: deepStats.model,
333
+ } : undefined).catch(() => {
305
334
  // Silent — local memory is advisory, never blocks scans
306
- }
335
+ });
307
336
  // v5: Record per-provenance data for adaptive thresholds + temporal drift
308
337
  try {
309
338
  const passedCount = Object.values(summary).filter(s => s === 'PASS').length;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Security Patterns Gate — Vulnerability Pattern Definitions
3
+ *
4
+ * Contains OWASP rule definitions, regex pattern arrays, and
5
+ * language-specific vulnerability patterns.
6
+ */
7
+ export declare const VULNERABILITY_PATTERNS: {
8
+ type: string;
9
+ regex: RegExp;
10
+ severity: 'critical' | 'high' | 'medium' | 'low';
11
+ description: string;
12
+ cwe: string;
13
+ languages: string[];
14
+ }[];
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Security Patterns Gate — Vulnerability Pattern Definitions
3
+ *
4
+ * Contains OWASP rule definitions, regex pattern arrays, and
5
+ * language-specific vulnerability patterns.
6
+ */
7
+ export const VULNERABILITY_PATTERNS = [
8
+ // SQL Injection
9
+ {
10
+ type: 'sql_injection',
11
+ regex: /(?:execute|query|raw|exec)\s*\(\s*`[^`]*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|WITH)[^`]*\$\{[^}]+\}[^`]*`/gi,
12
+ severity: 'critical',
13
+ description: 'Potential SQL injection: User input concatenated into SQL query',
14
+ cwe: 'CWE-89',
15
+ languages: ['ts', 'js', 'py']
16
+ },
17
+ {
18
+ type: 'sql_injection',
19
+ regex: /(?:execute|query|raw|exec)\s*\(\s*['"`][^'"`]*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|WITH)[^'"`]*['"`]\s*\+\s*[^)]+\)/gi,
20
+ severity: 'critical',
21
+ description: 'SQL query built with string concatenation',
22
+ cwe: 'CWE-89',
23
+ languages: ['ts', 'js']
24
+ },
25
+ // XSS
26
+ {
27
+ type: 'xss',
28
+ regex: /innerHTML\s*=\s*(?!\s*['"`]\s*['"`])[^;]+/g,
29
+ severity: 'high',
30
+ description: 'Potential XSS: innerHTML assignment with dynamic content',
31
+ cwe: 'CWE-79',
32
+ languages: ['ts', 'js', 'tsx', 'jsx']
33
+ },
34
+ {
35
+ type: 'xss',
36
+ regex: /dangerouslySetInnerHTML\s*=\s*\{/g,
37
+ severity: 'high',
38
+ description: 'dangerouslySetInnerHTML usage (ensure content is sanitized)',
39
+ cwe: 'CWE-79',
40
+ languages: ['tsx', 'jsx']
41
+ },
42
+ {
43
+ type: 'xss',
44
+ regex: /document\.write\s*\(/g,
45
+ severity: 'high',
46
+ description: 'document.write is dangerous for XSS',
47
+ cwe: 'CWE-79',
48
+ languages: ['ts', 'js']
49
+ },
50
+ // Path Traversal
51
+ {
52
+ type: 'path_traversal',
53
+ regex: /(?:readFile|writeFile|readdir|unlink|rmdir)\s*\([^)]*(?:req\.(?:params|query|body)|\.\.\/)/g,
54
+ severity: 'high',
55
+ description: 'Potential path traversal: File operation with user input',
56
+ cwe: 'CWE-22',
57
+ languages: ['ts', 'js']
58
+ },
59
+ {
60
+ type: 'path_traversal',
61
+ regex: /path\.join\s*\([^)]*req\./g,
62
+ severity: 'medium',
63
+ description: 'path.join with request data (verify input sanitization)',
64
+ cwe: 'CWE-22',
65
+ languages: ['ts', 'js']
66
+ },
67
+ // Hardcoded Secrets
68
+ {
69
+ type: 'hardcoded_secrets',
70
+ regex: /(?:password|secret|api_key|apikey|auth_token|access_token|private_key)\s*[:=]\s*['"][^'"]{8,}['"]/gi,
71
+ severity: 'critical',
72
+ description: 'Hardcoded secret detected in code',
73
+ cwe: 'CWE-798',
74
+ languages: ['ts', 'js', 'py', 'java', 'go']
75
+ },
76
+ {
77
+ type: 'hardcoded_secrets',
78
+ regex: /(?:sk-|pk-|rk-|ghp_|gho_|ghu_|ghs_|ghr_)[a-zA-Z0-9]{20,}/g,
79
+ severity: 'critical',
80
+ description: 'API key pattern detected (OpenAI, GitHub, etc.)',
81
+ cwe: 'CWE-798',
82
+ languages: ['*']
83
+ },
84
+ {
85
+ type: 'hardcoded_secrets',
86
+ regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
87
+ severity: 'critical',
88
+ description: 'Private key embedded in source code',
89
+ cwe: 'CWE-798',
90
+ languages: ['*']
91
+ },
92
+ // Insecure Randomness
93
+ {
94
+ type: 'insecure_randomness',
95
+ regex: /Math\.random\s*\(\s*\)/g,
96
+ severity: 'medium',
97
+ description: 'Math.random() is not cryptographically secure',
98
+ cwe: 'CWE-338',
99
+ languages: ['ts', 'js', 'tsx', 'jsx']
100
+ },
101
+ // Command Injection
102
+ {
103
+ type: 'command_injection',
104
+ regex: /(?:exec|execSync|spawn|spawnSync)\s*\(\s*(?:`[^`]*\$\{[^}]*(?:req\.|query|params|body|input|user|argv|process\.env)[^}]*\}[^`]*`|[^)]*(?:req\.|query|params|body|input|user|argv|process\.env)[^)]*)\)/g,
105
+ severity: 'critical',
106
+ description: 'Potential command injection: shell execution with user input',
107
+ cwe: 'CWE-78',
108
+ languages: ['ts', 'js']
109
+ },
110
+ {
111
+ type: 'command_injection',
112
+ regex: /child_process.*\s*\.\s*(?:exec|spawn)\s*\([^)]*(?:req\.|query|params|body|input|user|argv|process\.env)/g,
113
+ severity: 'high',
114
+ description: 'child_process usage detected (verify input sanitization)',
115
+ cwe: 'CWE-78',
116
+ languages: ['ts', 'js']
117
+ },
118
+ // Python subprocess command injection
119
+ {
120
+ type: 'command_injection',
121
+ regex: /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\([^)]*shell\s*=\s*True/g,
122
+ severity: 'critical',
123
+ description: 'Python subprocess with shell=True — potential command injection',
124
+ cwe: 'CWE-78',
125
+ languages: ['py']
126
+ },
127
+ {
128
+ type: 'command_injection',
129
+ regex: /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*f["'][^"']*\{[^}]+\}/g,
130
+ severity: 'critical',
131
+ description: 'Python subprocess with f-string interpolation — potential command injection',
132
+ cwe: 'CWE-78',
133
+ languages: ['py']
134
+ },
135
+ {
136
+ type: 'command_injection',
137
+ regex: /os\.(?:system|popen)\s*\(\s*(?:f["']|[^"']*\.format\(|[^"']*%\s)/g,
138
+ severity: 'critical',
139
+ description: 'Python os.system/os.popen with dynamic string — command injection risk',
140
+ cwe: 'CWE-78',
141
+ languages: ['py']
142
+ },
143
+ // ReDoS — Denial of Service via regex (OWASP #7)
144
+ {
145
+ type: 'redos',
146
+ regex: /new RegExp\s*\([^)]*(?:req\.|params|query|body|input|user)/g,
147
+ severity: 'high',
148
+ description: 'Dynamic regex from user input — potential ReDoS',
149
+ cwe: 'CWE-1333',
150
+ languages: ['ts', 'js']
151
+ },
152
+ {
153
+ type: 'redos',
154
+ regex: /\(\?:[^)]*\+[^)]*\)\+|\([^)]*\*[^)]*\)\+|\(\.\*\)\{/g,
155
+ severity: 'medium',
156
+ description: 'Regex with nested quantifiers — potential ReDoS',
157
+ cwe: 'CWE-1333',
158
+ languages: ['ts', 'js', 'py']
159
+ },
160
+ // Overly Permissive Code (OWASP #9)
161
+ {
162
+ type: 'overly_permissive',
163
+ regex: /cors\s*\(\s*\{[^}]*origin\s*:\s*(?:true|['"`]\*['"`])/g,
164
+ severity: 'high',
165
+ description: 'CORS wildcard origin — allows any domain',
166
+ cwe: 'CWE-942',
167
+ languages: ['ts', 'js']
168
+ },
169
+ {
170
+ type: 'overly_permissive',
171
+ regex: /(?:listen|bind)\s*\(\s*(?:\d+\s*,\s*)?['"`]0\.0\.0\.0['"`]/g,
172
+ severity: 'medium',
173
+ description: 'Binding to 0.0.0.0 exposes service to all interfaces',
174
+ cwe: 'CWE-668',
175
+ languages: ['ts', 'js', 'py', 'go']
176
+ },
177
+ {
178
+ type: 'overly_permissive',
179
+ regex: /chmod\s*\(\s*[^,]*,\s*['"`]?(?:0o?)?777['"`]?\s*\)/g,
180
+ severity: 'high',
181
+ description: 'chmod 777 — world-readable/writable permissions',
182
+ cwe: 'CWE-732',
183
+ languages: ['ts', 'js', 'py']
184
+ },
185
+ {
186
+ type: 'overly_permissive',
187
+ regex: /(?:Access-Control-Allow-Origin|x-powered-by)['"`,\s:]+\*/gi,
188
+ severity: 'high',
189
+ description: 'Wildcard Access-Control-Allow-Origin header',
190
+ cwe: 'CWE-942',
191
+ languages: ['ts', 'js', 'py']
192
+ },
193
+ // Unsafe Output Handling (OWASP #6)
194
+ {
195
+ type: 'unsafe_output',
196
+ regex: /res\.(?:send|write|end)\s*\(\s*(?:req\.|params|query|body|input|user)/g,
197
+ severity: 'high',
198
+ description: 'Reflecting user input in response without sanitization',
199
+ cwe: 'CWE-79',
200
+ languages: ['ts', 'js']
201
+ },
202
+ {
203
+ type: 'unsafe_output',
204
+ regex: /\$\{[^}]*(?:req\.|params|query|body|input|user)[^}]*\}.*(?:html|template|render)/gi,
205
+ severity: 'high',
206
+ description: 'User input interpolated into template/HTML output',
207
+ cwe: 'CWE-79',
208
+ languages: ['ts', 'js', 'py']
209
+ },
210
+ {
211
+ type: 'unsafe_output',
212
+ regex: /eval\s*\(\s*(?:req\.|params|query|body|input|user)/g,
213
+ severity: 'critical',
214
+ description: 'eval() with user input — code injection',
215
+ cwe: 'CWE-94',
216
+ languages: ['ts', 'js', 'py']
217
+ },
218
+ // Missing Input Validation (OWASP #8)
219
+ {
220
+ type: 'missing_input_validation',
221
+ regex: /JSON\.parse\s*\(\s*(?:req\.body|request\.body|body|data|input)\s*\)/g,
222
+ severity: 'medium',
223
+ description: 'JSON.parse on raw input without schema validation',
224
+ cwe: 'CWE-20',
225
+ languages: ['ts', 'js']
226
+ },
227
+ {
228
+ type: 'missing_input_validation',
229
+ regex: /(?:as\s+any|:\s*any)\s*(?:[;,)\]}])/g,
230
+ severity: 'medium',
231
+ description: 'Type assertion to "any" bypasses type safety',
232
+ cwe: 'CWE-20',
233
+ languages: ['ts']
234
+ },
235
+ ];
@@ -11,8 +11,6 @@
11
11
  * - Hardcoded Secrets
12
12
  * - Insecure Randomness
13
13
  * - Command Injection
14
- *
15
- * @since v2.14.0
16
14
  */
17
15
  import { Gate, GateContext } from './base.js';
18
16
  import { Failure, Provenance } from '../types/index.js';
@@ -49,9 +47,25 @@ export declare class SecurityPatternsGate extends Gate {
49
47
  private scanFileForVulnerabilities;
50
48
  /**
51
49
  * Check if a hardcoded secret match is actually a dummy/placeholder value.
52
- * Filters out test values, placeholder defaults, and env-var-name assignments.
50
+ * Filters out test values, placeholder defaults, env-var-name assignments,
51
+ * store action types, and low-entropy constants.
53
52
  */
54
53
  private isDummySecretValue;
54
+ /**
55
+ * Calculate Shannon entropy (bits per character) of a string.
56
+ * Real secrets have high entropy (>4.5); constants/names have low entropy (<3.0).
57
+ */
58
+ private shannonEntropy;
59
+ /**
60
+ * Check if an innerHTML or dangerouslySetInnerHTML assignment is wrapped in a sanitizer.
61
+ * Known sanitizers: DOMPurify.sanitize(), sanitize(), xss(), escapeHtml(), htmlEncode().
62
+ */
63
+ private isSanitizedAssignment;
64
+ /**
65
+ * Check if a shell execution call is using { shell: false } option (safe pattern).
66
+ * Also, spawn() without { shell: true } is safe by default — don't flag it.
67
+ */
68
+ private isSafeShellCall;
55
69
  }
56
70
  /**
57
71
  * Quick helper to check a single file for security issues