@rigour-labs/core 5.0.1 → 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.
- package/README.md +9 -1
- package/dist/gates/agent-team.d.ts +0 -1
- package/dist/gates/agent-team.js +0 -1
- package/dist/gates/checkpoint.d.ts +0 -2
- package/dist/gates/checkpoint.js +0 -2
- package/dist/gates/context-window-artifacts.d.ts +6 -2
- package/dist/gates/context-window-artifacts.js +107 -31
- package/dist/gates/deep-analysis.d.ts +2 -0
- package/dist/gates/deep-analysis.js +41 -11
- package/dist/gates/dependency.d.ts +0 -2
- package/dist/gates/dependency.js +23 -5
- package/dist/gates/deprecated-apis.d.ts +0 -2
- package/dist/gates/deprecated-apis.js +33 -20
- package/dist/gates/duplication-drift/index.d.ts +61 -0
- package/dist/gates/duplication-drift/index.js +240 -0
- package/dist/gates/duplication-drift/similarity.d.ts +68 -0
- package/dist/gates/duplication-drift/similarity.js +177 -0
- package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
- package/dist/gates/duplication-drift/tokenizer.js +195 -0
- package/dist/gates/frontend-secret-exposure.d.ts +0 -3
- package/dist/gates/frontend-secret-exposure.js +1 -114
- package/dist/gates/frontend-secret-patterns.d.ts +33 -0
- package/dist/gates/frontend-secret-patterns.js +119 -0
- package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
- package/dist/gates/hallucinated-imports/index.js +174 -0
- package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
- package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
- package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
- package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
- package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
- package/dist/gates/hallucinated-imports-lang.js +269 -34
- package/dist/gates/hallucinated-imports.test.js +1 -2
- package/dist/gates/inconsistent-error-handling.d.ts +0 -5
- package/dist/gates/inconsistent-error-handling.js +15 -144
- package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/csharp-adapter.js +211 -0
- package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/go-adapter.js +195 -0
- package/dist/gates/language-adapters/index.d.ts +15 -0
- package/dist/gates/language-adapters/index.js +16 -0
- package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/java-adapter.js +237 -0
- package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/js-adapter.js +279 -0
- package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/python-adapter.js +183 -0
- package/dist/gates/language-adapters/registry.d.ts +26 -0
- package/dist/gates/language-adapters/registry.js +65 -0
- package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/ruby-adapter.js +217 -0
- package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
- package/dist/gates/language-adapters/rust-adapter.js +235 -0
- package/dist/gates/language-adapters/types.d.ts +60 -0
- package/dist/gates/language-adapters/types.js +22 -0
- package/dist/gates/logic-drift-extractors.d.ts +15 -0
- package/dist/gates/logic-drift-extractors.js +34 -0
- package/dist/gates/logic-drift.d.ts +0 -30
- package/dist/gates/logic-drift.js +39 -129
- package/dist/gates/phantom-apis.d.ts +0 -2
- package/dist/gates/phantom-apis.js +49 -20
- package/dist/gates/promise-safety.d.ts +0 -1
- package/dist/gates/promise-safety.js +14 -2
- package/dist/gates/runner.js +51 -22
- package/dist/gates/security-patterns-data.d.ts +14 -0
- package/dist/gates/security-patterns-data.js +235 -0
- package/dist/gates/security-patterns.d.ts +17 -3
- package/dist/gates/security-patterns.js +80 -211
- package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
- package/dist/gates/side-effect-analysis/categorizer.js +83 -0
- package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
- package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
- package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
- package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
- package/dist/gates/side-effect-helpers/index.d.ts +4 -0
- package/dist/gates/side-effect-helpers/index.js +4 -0
- package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
- package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
- package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
- package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
- package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
- package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
- package/dist/gates/side-effect-helpers/types.d.ts +38 -0
- package/dist/gates/side-effect-helpers/types.js +41 -0
- package/dist/gates/side-effect-rules.d.ts +0 -1
- package/dist/gates/side-effect-rules.js +0 -1
- package/dist/gates/style-drift-rules.d.ts +86 -0
- package/dist/gates/style-drift-rules.js +103 -0
- package/dist/gates/style-drift.d.ts +7 -16
- package/dist/gates/style-drift.js +101 -119
- package/dist/gates/test-quality-matchers.d.ts +53 -0
- package/dist/gates/test-quality-matchers.js +86 -0
- package/dist/gates/test-quality.d.ts +0 -3
- package/dist/gates/test-quality.js +47 -44
- package/dist/hooks/checker.d.ts +0 -1
- package/dist/hooks/checker.js +0 -2
- package/dist/hooks/dlp-templates.d.ts +0 -1
- package/dist/hooks/dlp-templates.js +0 -4
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/input-validator.d.ts +0 -1
- package/dist/hooks/input-validator.js +0 -1
- package/dist/hooks/input-validator.test.js +0 -1
- package/dist/hooks/standalone-checker.d.ts +0 -1
- package/dist/hooks/standalone-checker.js +0 -1
- package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
- package/dist/hooks/standalone-dlp-checker.js +0 -1
- package/dist/hooks/templates.d.ts +0 -1
- package/dist/hooks/templates.js +0 -1
- package/dist/hooks/types.d.ts +0 -1
- package/dist/hooks/types.js +0 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/services/adaptive-thresholds.d.ts +0 -2
- package/dist/services/adaptive-thresholds.js +0 -2
- package/dist/services/filesystem-cache.d.ts +0 -1
- package/dist/services/filesystem-cache.js +0 -1
- package/dist/services/score-history.d.ts +0 -1
- package/dist/services/score-history.js +0 -1
- package/dist/services/temporal-drift.d.ts +1 -2
- package/dist/services/temporal-drift.js +7 -8
- package/dist/storage/db.d.ts +23 -7
- package/dist/storage/db.js +116 -55
- package/dist/storage/findings.d.ts +4 -3
- package/dist/storage/findings.js +13 -20
- package/dist/storage/local-memory.d.ts +4 -4
- package/dist/storage/local-memory.js +20 -22
- package/dist/storage/patterns.d.ts +5 -5
- package/dist/storage/patterns.js +20 -26
- package/dist/storage/scans.d.ts +6 -6
- package/dist/storage/scans.js +12 -21
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/scanner.js +1 -1
- package/package.json +7 -8
- package/dist/gates/duplication-drift.d.ts +0 -128
- package/dist/gates/duplication-drift.js +0 -585
- package/dist/gates/hallucinated-imports.js +0 -641
- 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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/gates/runner.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
290
|
+
aiGateDeductions.set(gateId, (aiGateDeductions.get(gateId) || 0) + weight);
|
|
267
291
|
break;
|
|
268
292
|
case 'traditional':
|
|
269
|
-
|
|
293
|
+
structuralGateDeductions.set(gateId, (structuralGateDeductions.get(gateId) || 0) + weight);
|
|
270
294
|
break;
|
|
271
295
|
case 'deep-analysis':
|
|
272
|
-
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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,
|
|
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
|