@rigour-labs/core 2.22.0 → 3.0.1
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 +58 -0
- package/dist/context.test.js +2 -3
- package/dist/environment.test.js +2 -1
- package/dist/gates/agent-team.d.ts +2 -1
- package/dist/gates/agent-team.js +1 -0
- package/dist/gates/base.d.ts +3 -1
- package/dist/gates/base.js +3 -0
- package/dist/gates/checkpoint.d.ts +2 -1
- package/dist/gates/checkpoint.js +3 -2
- package/dist/gates/context-window-artifacts.d.ts +2 -1
- package/dist/gates/context-window-artifacts.js +6 -3
- package/dist/gates/context.d.ts +2 -1
- package/dist/gates/context.js +1 -0
- package/dist/gates/coverage.js +3 -1
- package/dist/gates/dependency.js +5 -5
- package/dist/gates/duplication-drift.d.ts +2 -1
- package/dist/gates/duplication-drift.js +4 -1
- package/dist/gates/environment.js +4 -4
- package/dist/gates/hallucinated-imports.d.ts +21 -2
- package/dist/gates/hallucinated-imports.js +116 -2
- package/dist/gates/inconsistent-error-handling.d.ts +2 -1
- package/dist/gates/inconsistent-error-handling.js +21 -7
- package/dist/gates/promise-safety.d.ts +68 -0
- package/dist/gates/promise-safety.js +509 -0
- package/dist/gates/retry-loop-breaker.d.ts +2 -1
- package/dist/gates/retry-loop-breaker.js +2 -1
- package/dist/gates/runner.js +34 -1
- package/dist/gates/safety.d.ts +2 -1
- package/dist/gates/safety.js +2 -1
- package/dist/gates/security-patterns-owasp.test.d.ts +1 -0
- package/dist/gates/security-patterns-owasp.test.js +171 -0
- package/dist/gates/security-patterns.d.ts +6 -1
- package/dist/gates/security-patterns.js +101 -0
- package/dist/gates/structure.js +1 -1
- package/dist/hooks/checker.d.ts +23 -0
- package/dist/hooks/checker.js +222 -0
- package/dist/hooks/checker.test.d.ts +1 -0
- package/dist/hooks/checker.test.js +132 -0
- package/dist/hooks/index.d.ts +9 -0
- package/dist/hooks/index.js +8 -0
- package/dist/hooks/standalone-checker.d.ts +15 -0
- package/dist/hooks/standalone-checker.js +106 -0
- package/dist/hooks/templates.d.ts +22 -0
- package/dist/hooks/templates.js +232 -0
- package/dist/hooks/types.d.ts +34 -0
- package/dist/hooks/types.js +21 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/services/fix-packet-service.d.ts +0 -1
- package/dist/services/fix-packet-service.js +9 -14
- package/dist/services/score-history.d.ts +54 -0
- package/dist/services/score-history.js +122 -0
- package/dist/templates/index.js +176 -0
- package/dist/types/fix-packet.d.ts +5 -5
- package/dist/types/fix-packet.js +1 -1
- package/dist/types/index.d.ts +207 -0
- package/dist/types/index.js +32 -0
- package/package.json +21 -1
- package/src/context.test.ts +0 -256
- package/src/discovery.test.ts +0 -88
- package/src/discovery.ts +0 -112
- package/src/environment.test.ts +0 -115
- package/src/gates/agent-team.test.ts +0 -134
- package/src/gates/agent-team.ts +0 -210
- package/src/gates/ast-handlers/base.ts +0 -13
- package/src/gates/ast-handlers/python.ts +0 -145
- package/src/gates/ast-handlers/python_parser.py +0 -181
- package/src/gates/ast-handlers/typescript.ts +0 -264
- package/src/gates/ast-handlers/universal.ts +0 -184
- package/src/gates/ast.ts +0 -54
- package/src/gates/base.ts +0 -28
- package/src/gates/checkpoint.test.ts +0 -135
- package/src/gates/checkpoint.ts +0 -311
- package/src/gates/content.ts +0 -51
- package/src/gates/context-window-artifacts.ts +0 -277
- package/src/gates/context.ts +0 -270
- package/src/gates/coverage.ts +0 -74
- package/src/gates/dependency.ts +0 -108
- package/src/gates/duplication-drift.ts +0 -231
- package/src/gates/environment.ts +0 -94
- package/src/gates/file.ts +0 -46
- package/src/gates/hallucinated-imports.ts +0 -361
- package/src/gates/inconsistent-error-handling.ts +0 -254
- package/src/gates/retry-loop-breaker.ts +0 -151
- package/src/gates/runner.ts +0 -188
- package/src/gates/safety.ts +0 -56
- package/src/gates/security-patterns.test.ts +0 -162
- package/src/gates/security-patterns.ts +0 -306
- package/src/gates/structure.ts +0 -36
- package/src/index.ts +0 -13
- package/src/pattern-index/embeddings.ts +0 -84
- package/src/pattern-index/index.ts +0 -59
- package/src/pattern-index/indexer.test.ts +0 -276
- package/src/pattern-index/indexer.ts +0 -1023
- package/src/pattern-index/matcher.test.ts +0 -293
- package/src/pattern-index/matcher.ts +0 -493
- package/src/pattern-index/overrides.ts +0 -235
- package/src/pattern-index/security.ts +0 -151
- package/src/pattern-index/staleness.test.ts +0 -313
- package/src/pattern-index/staleness.ts +0 -568
- package/src/pattern-index/types.ts +0 -339
- package/src/safety.test.ts +0 -53
- package/src/services/adaptive-thresholds.test.ts +0 -189
- package/src/services/adaptive-thresholds.ts +0 -275
- package/src/services/context-engine.ts +0 -104
- package/src/services/fix-packet-service.ts +0 -42
- package/src/services/state-service.ts +0 -138
- package/src/smoke.test.ts +0 -18
- package/src/templates/index.ts +0 -338
- package/src/types/fix-packet.ts +0 -32
- package/src/types/index.ts +0 -200
- package/src/utils/logger.ts +0 -43
- package/src/utils/scanner.test.ts +0 -37
- package/src/utils/scanner.ts +0 -43
- package/tsconfig.json +0 -10
- package/vitest.config.ts +0 -7
- package/vitest.setup.ts +0 -30
package/dist/gates/runner.js
CHANGED
|
@@ -17,6 +17,7 @@ import { DuplicationDriftGate } from './duplication-drift.js';
|
|
|
17
17
|
import { HallucinatedImportsGate } from './hallucinated-imports.js';
|
|
18
18
|
import { InconsistentErrorHandlingGate } from './inconsistent-error-handling.js';
|
|
19
19
|
import { ContextWindowArtifactsGate } from './context-window-artifacts.js';
|
|
20
|
+
import { PromiseSafetyGate } from './promise-safety.js';
|
|
20
21
|
import { execa } from 'execa';
|
|
21
22
|
import { Logger } from '../utils/logger.js';
|
|
22
23
|
export class GateRunner {
|
|
@@ -73,6 +74,10 @@ export class GateRunner {
|
|
|
73
74
|
if (this.config.gates.context_window_artifacts?.enabled !== false) {
|
|
74
75
|
this.gates.push(new ContextWindowArtifactsGate(this.config.gates.context_window_artifacts));
|
|
75
76
|
}
|
|
77
|
+
// v2.17+ Promise Safety Gate (async/promise AI failure modes)
|
|
78
|
+
if (this.config.gates.promise_safety?.enabled !== false) {
|
|
79
|
+
this.gates.push(new PromiseSafetyGate(this.config.gates.promise_safety));
|
|
80
|
+
}
|
|
76
81
|
// Environment Alignment Gate (Should be prioritized)
|
|
77
82
|
if (this.config.gates.environment?.enabled) {
|
|
78
83
|
this.gates.unshift(new EnvironmentGate(this.config.gates));
|
|
@@ -114,6 +119,8 @@ export class GateRunner {
|
|
|
114
119
|
id: gate.id,
|
|
115
120
|
title: `Gate Error: ${gate.title}`,
|
|
116
121
|
details: error.message,
|
|
122
|
+
severity: 'medium',
|
|
123
|
+
provenance: 'traditional',
|
|
117
124
|
hint: 'There was an internal error running this gate. Check the logs.',
|
|
118
125
|
});
|
|
119
126
|
}
|
|
@@ -137,6 +144,8 @@ export class GateRunner {
|
|
|
137
144
|
id: key,
|
|
138
145
|
title: `${key.toUpperCase()} Check Failed`,
|
|
139
146
|
details: error.stderr || error.stdout || error.message,
|
|
147
|
+
severity: 'medium',
|
|
148
|
+
provenance: 'traditional',
|
|
140
149
|
hint: `Fix the issues reported by \`${cmd}\`. Use rigorous standards (SOLID, DRY) in your resolution.`,
|
|
141
150
|
});
|
|
142
151
|
}
|
|
@@ -144,7 +153,6 @@ export class GateRunner {
|
|
|
144
153
|
}
|
|
145
154
|
const status = failures.length > 0 ? 'FAIL' : 'PASS';
|
|
146
155
|
// Severity-weighted scoring: each failure deducts based on its severity
|
|
147
|
-
// critical=20, high=10, medium=5, low=2, info=0
|
|
148
156
|
const severityBreakdown = {};
|
|
149
157
|
let totalDeduction = 0;
|
|
150
158
|
for (const f of failures) {
|
|
@@ -153,6 +161,23 @@ export class GateRunner {
|
|
|
153
161
|
totalDeduction += SEVERITY_WEIGHTS[sev] ?? 5;
|
|
154
162
|
}
|
|
155
163
|
const score = Math.max(0, 100 - totalDeduction);
|
|
164
|
+
// Two-score system: separate AI health from structural quality
|
|
165
|
+
let aiDeduction = 0;
|
|
166
|
+
let aiCount = 0;
|
|
167
|
+
let structuralDeduction = 0;
|
|
168
|
+
let structuralCount = 0;
|
|
169
|
+
for (const f of failures) {
|
|
170
|
+
const sev = (f.severity || 'medium');
|
|
171
|
+
const weight = SEVERITY_WEIGHTS[sev] ?? 5;
|
|
172
|
+
if (f.provenance === 'ai-drift') {
|
|
173
|
+
aiDeduction += weight;
|
|
174
|
+
aiCount++;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
structuralDeduction += weight;
|
|
178
|
+
structuralCount++;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
156
181
|
return {
|
|
157
182
|
status,
|
|
158
183
|
summary,
|
|
@@ -160,7 +185,15 @@ export class GateRunner {
|
|
|
160
185
|
stats: {
|
|
161
186
|
duration_ms: Date.now() - start,
|
|
162
187
|
score,
|
|
188
|
+
ai_health_score: Math.max(0, 100 - aiDeduction),
|
|
189
|
+
structural_score: Math.max(0, 100 - structuralDeduction),
|
|
163
190
|
severity_breakdown: severityBreakdown,
|
|
191
|
+
provenance_breakdown: {
|
|
192
|
+
'ai-drift': aiCount,
|
|
193
|
+
traditional: structuralCount - failures.filter(f => f.provenance === 'security' || f.provenance === 'governance').length,
|
|
194
|
+
security: failures.filter(f => f.provenance === 'security').length,
|
|
195
|
+
governance: failures.filter(f => f.provenance === 'governance').length,
|
|
196
|
+
},
|
|
164
197
|
},
|
|
165
198
|
};
|
|
166
199
|
}
|
package/dist/gates/safety.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Gate, GateContext } from './base.js';
|
|
2
|
-
import { Failure, Gates } from '../types/index.js';
|
|
2
|
+
import { Failure, Gates, Provenance } from '../types/index.js';
|
|
3
3
|
export declare class FileGuardGate extends Gate {
|
|
4
4
|
private config;
|
|
5
5
|
constructor(config: Gates);
|
|
6
|
+
protected get provenance(): Provenance;
|
|
6
7
|
run(context: GateContext): Promise<Failure[]>;
|
|
7
8
|
private isProtected;
|
|
8
9
|
}
|
package/dist/gates/safety.js
CHANGED
|
@@ -6,6 +6,7 @@ export class FileGuardGate extends Gate {
|
|
|
6
6
|
super('file-guard', 'File Guard — Protected Paths');
|
|
7
7
|
this.config = config;
|
|
8
8
|
}
|
|
9
|
+
get provenance() { return 'governance'; }
|
|
9
10
|
async run(context) {
|
|
10
11
|
const failures = [];
|
|
11
12
|
const safety = this.config.safety || {};
|
|
@@ -27,7 +28,7 @@ export class FileGuardGate extends Gate {
|
|
|
27
28
|
for (const file of modifiedFiles) {
|
|
28
29
|
if (this.isProtected(file, protectedPaths)) {
|
|
29
30
|
const message = `Protected file '${file}' was modified.`;
|
|
30
|
-
failures.push(this.createFailure(message, [file], `Agents are forbidden from modifying files in ${protectedPaths.join(', ')}.`, message));
|
|
31
|
+
failures.push(this.createFailure(message, [file], `Agents are forbidden from modifying files in ${protectedPaths.join(', ')}.`, message, undefined, undefined, 'high'));
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for OWASP-aligned security patterns added in v3.0.0.
|
|
3
|
+
* Covers: ReDoS, overly permissive code, unsafe output, missing input validation.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import { SecurityPatternsGate, checkSecurityPatterns } from './security-patterns.js';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
describe('SecurityPatternsGate — OWASP extended patterns', () => {
|
|
11
|
+
let testDir;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'owasp-test-'));
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
describe('ReDoS detection (OWASP #7)', () => {
|
|
19
|
+
it('should detect dynamic regex from user input', async () => {
|
|
20
|
+
const filePath = path.join(testDir, 'search.ts');
|
|
21
|
+
fs.writeFileSync(filePath, `
|
|
22
|
+
const pattern = new RegExp(req.query.search);
|
|
23
|
+
const matches = text.match(pattern);
|
|
24
|
+
`);
|
|
25
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
26
|
+
expect(vulns.some(v => v.type === 'redos')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it('should detect nested quantifiers', async () => {
|
|
29
|
+
const filePath = path.join(testDir, 'regex.ts');
|
|
30
|
+
fs.writeFileSync(filePath, `
|
|
31
|
+
const re = /(?:a+)+b/;
|
|
32
|
+
`);
|
|
33
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
34
|
+
expect(vulns.some(v => v.type === 'redos')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('should allow safe regex patterns', async () => {
|
|
37
|
+
const filePath = path.join(testDir, 'safe-regex.ts');
|
|
38
|
+
fs.writeFileSync(filePath, `
|
|
39
|
+
const re = /^[a-z]+$/;
|
|
40
|
+
`);
|
|
41
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
42
|
+
expect(vulns.filter(v => v.type === 'redos')).toHaveLength(0);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('Overly Permissive Code (OWASP #9)', () => {
|
|
46
|
+
it('should detect CORS wildcard origin', async () => {
|
|
47
|
+
const filePath = path.join(testDir, 'server.ts');
|
|
48
|
+
fs.writeFileSync(filePath, `
|
|
49
|
+
import cors from 'cors';
|
|
50
|
+
app.use(cors({ origin: '*' }));
|
|
51
|
+
`);
|
|
52
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
53
|
+
expect(vulns.some(v => v.type === 'overly_permissive')).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
it('should detect CORS origin true', async () => {
|
|
56
|
+
const filePath = path.join(testDir, 'server2.ts');
|
|
57
|
+
fs.writeFileSync(filePath, `
|
|
58
|
+
app.use(cors({ origin: true }));
|
|
59
|
+
`);
|
|
60
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
61
|
+
expect(vulns.some(v => v.type === 'overly_permissive')).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
it('should detect 0.0.0.0 binding', async () => {
|
|
64
|
+
const filePath = path.join(testDir, 'listen.ts');
|
|
65
|
+
fs.writeFileSync(filePath, `
|
|
66
|
+
app.listen(3000, '0.0.0.0');
|
|
67
|
+
`);
|
|
68
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
69
|
+
expect(vulns.some(v => v.type === 'overly_permissive')).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
it('should detect chmod 777', async () => {
|
|
72
|
+
const filePath = path.join(testDir, 'perms.ts');
|
|
73
|
+
fs.writeFileSync(filePath, `
|
|
74
|
+
fs.chmod('/tmp/data', 0o777);
|
|
75
|
+
`);
|
|
76
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
77
|
+
expect(vulns.some(v => v.type === 'overly_permissive')).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
it('should detect wildcard CORS header', async () => {
|
|
80
|
+
const filePath = path.join(testDir, 'headers.ts');
|
|
81
|
+
fs.writeFileSync(filePath, `
|
|
82
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
83
|
+
`);
|
|
84
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
85
|
+
expect(vulns.some(v => v.type === 'overly_permissive')).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
it('should allow specific CORS origin', async () => {
|
|
88
|
+
const filePath = path.join(testDir, 'safe-cors.ts');
|
|
89
|
+
fs.writeFileSync(filePath, `
|
|
90
|
+
app.use(cors({ origin: 'https://myapp.com' }));
|
|
91
|
+
`);
|
|
92
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
93
|
+
expect(vulns.filter(v => v.type === 'overly_permissive')).toHaveLength(0);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('Unsafe Output Handling (OWASP #6)', () => {
|
|
97
|
+
it('should detect response reflecting user input', async () => {
|
|
98
|
+
const filePath = path.join(testDir, 'handler.ts');
|
|
99
|
+
fs.writeFileSync(filePath, `
|
|
100
|
+
app.get('/echo', (req, res) => {
|
|
101
|
+
res.send(req.query.msg);
|
|
102
|
+
});
|
|
103
|
+
`);
|
|
104
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
105
|
+
expect(vulns.some(v => v.type === 'unsafe_output')).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
it('should detect eval with user input', async () => {
|
|
108
|
+
const filePath = path.join(testDir, 'eval.ts');
|
|
109
|
+
fs.writeFileSync(filePath, `
|
|
110
|
+
eval(req.body.code);
|
|
111
|
+
`);
|
|
112
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
113
|
+
expect(vulns.some(v => v.type === 'unsafe_output')).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
it('should allow safe response patterns', async () => {
|
|
116
|
+
const filePath = path.join(testDir, 'safe-res.ts');
|
|
117
|
+
fs.writeFileSync(filePath, `
|
|
118
|
+
res.json({ status: 'ok', data: processedData });
|
|
119
|
+
`);
|
|
120
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
121
|
+
expect(vulns.filter(v => v.type === 'unsafe_output')).toHaveLength(0);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('Missing Input Validation (OWASP #8)', () => {
|
|
125
|
+
it('should detect JSON.parse on raw body', async () => {
|
|
126
|
+
const filePath = path.join(testDir, 'parse.ts');
|
|
127
|
+
fs.writeFileSync(filePath, `
|
|
128
|
+
const data = JSON.parse(req.body);
|
|
129
|
+
`);
|
|
130
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
131
|
+
expect(vulns.some(v => v.type === 'missing_input_validation')).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
it('should detect "as any" type assertion', async () => {
|
|
134
|
+
const filePath = path.join(testDir, 'assert.ts');
|
|
135
|
+
fs.writeFileSync(filePath, `
|
|
136
|
+
const user = payload as any;
|
|
137
|
+
`);
|
|
138
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
139
|
+
expect(vulns.some(v => v.type === 'missing_input_validation')).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
it('should allow validated JSON parse', async () => {
|
|
142
|
+
const filePath = path.join(testDir, 'safe-parse.ts');
|
|
143
|
+
fs.writeFileSync(filePath, `
|
|
144
|
+
const data = JSON.parse(rawString);
|
|
145
|
+
const validated = schema.parse(data);
|
|
146
|
+
`);
|
|
147
|
+
const vulns = await checkSecurityPatterns(filePath);
|
|
148
|
+
expect(vulns.filter(v => v.type === 'missing_input_validation')).toHaveLength(0);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe('config toggles for new patterns', () => {
|
|
152
|
+
it('should disable redos when configured', async () => {
|
|
153
|
+
const gate = new SecurityPatternsGate({ enabled: true, redos: false });
|
|
154
|
+
const filePath = path.join(testDir, 'regex.ts');
|
|
155
|
+
fs.writeFileSync(filePath, `
|
|
156
|
+
const pattern = new RegExp(req.query.search);
|
|
157
|
+
`);
|
|
158
|
+
const failures = await gate.run({ cwd: testDir });
|
|
159
|
+
expect(failures.filter(f => f.title?.includes('ReDoS') || f.title?.includes('regex'))).toHaveLength(0);
|
|
160
|
+
});
|
|
161
|
+
it('should disable overly_permissive when configured', async () => {
|
|
162
|
+
const gate = new SecurityPatternsGate({ enabled: true, overly_permissive: false });
|
|
163
|
+
const filePath = path.join(testDir, 'cors.ts');
|
|
164
|
+
fs.writeFileSync(filePath, `
|
|
165
|
+
app.use(cors({ origin: '*' }));
|
|
166
|
+
`);
|
|
167
|
+
const failures = await gate.run({ cwd: testDir });
|
|
168
|
+
expect(failures.filter(f => f.title?.includes('CORS') || f.title?.includes('permissive'))).toHaveLength(0);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* @since v2.14.0
|
|
16
16
|
*/
|
|
17
17
|
import { Gate, GateContext } from './base.js';
|
|
18
|
-
import { Failure } from '../types/index.js';
|
|
18
|
+
import { Failure, Provenance } from '../types/index.js';
|
|
19
19
|
export interface SecurityVulnerability {
|
|
20
20
|
type: string;
|
|
21
21
|
severity: 'critical' | 'high' | 'medium' | 'low';
|
|
@@ -33,12 +33,17 @@ export interface SecurityPatternsConfig {
|
|
|
33
33
|
hardcoded_secrets?: boolean;
|
|
34
34
|
insecure_randomness?: boolean;
|
|
35
35
|
command_injection?: boolean;
|
|
36
|
+
redos?: boolean;
|
|
37
|
+
overly_permissive?: boolean;
|
|
38
|
+
unsafe_output?: boolean;
|
|
39
|
+
missing_input_validation?: boolean;
|
|
36
40
|
block_on_severity?: 'critical' | 'high' | 'medium' | 'low';
|
|
37
41
|
}
|
|
38
42
|
export declare class SecurityPatternsGate extends Gate {
|
|
39
43
|
private config;
|
|
40
44
|
private severityOrder;
|
|
41
45
|
constructor(config?: SecurityPatternsConfig);
|
|
46
|
+
protected get provenance(): Provenance;
|
|
42
47
|
run(context: GateContext): Promise<Failure[]>;
|
|
43
48
|
private scanFileForVulnerabilities;
|
|
44
49
|
}
|
|
@@ -131,6 +131,98 @@ const VULNERABILITY_PATTERNS = [
|
|
|
131
131
|
cwe: 'CWE-78',
|
|
132
132
|
languages: ['ts', 'js']
|
|
133
133
|
},
|
|
134
|
+
// ReDoS — Denial of Service via regex (OWASP #7)
|
|
135
|
+
{
|
|
136
|
+
type: 'redos',
|
|
137
|
+
regex: /new RegExp\s*\([^)]*(?:req\.|params|query|body|input|user)/g,
|
|
138
|
+
severity: 'high',
|
|
139
|
+
description: 'Dynamic regex from user input — potential ReDoS',
|
|
140
|
+
cwe: 'CWE-1333',
|
|
141
|
+
languages: ['ts', 'js']
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: 'redos',
|
|
145
|
+
regex: /\(\?:[^)]*\+[^)]*\)\+|\([^)]*\*[^)]*\)\+|\(\.\*\)\{/g,
|
|
146
|
+
severity: 'medium',
|
|
147
|
+
description: 'Regex with nested quantifiers — potential ReDoS',
|
|
148
|
+
cwe: 'CWE-1333',
|
|
149
|
+
languages: ['ts', 'js', 'py']
|
|
150
|
+
},
|
|
151
|
+
// Overly Permissive Code (OWASP #9)
|
|
152
|
+
{
|
|
153
|
+
type: 'overly_permissive',
|
|
154
|
+
regex: /cors\s*\(\s*\{[^}]*origin\s*:\s*(?:true|['"`]\*['"`])/g,
|
|
155
|
+
severity: 'high',
|
|
156
|
+
description: 'CORS wildcard origin — allows any domain',
|
|
157
|
+
cwe: 'CWE-942',
|
|
158
|
+
languages: ['ts', 'js']
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
type: 'overly_permissive',
|
|
162
|
+
regex: /(?:listen|bind)\s*\(\s*(?:\d+\s*,\s*)?['"`]0\.0\.0\.0['"`]/g,
|
|
163
|
+
severity: 'medium',
|
|
164
|
+
description: 'Binding to 0.0.0.0 exposes service to all interfaces',
|
|
165
|
+
cwe: 'CWE-668',
|
|
166
|
+
languages: ['ts', 'js', 'py', 'go']
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: 'overly_permissive',
|
|
170
|
+
regex: /chmod\s*\(\s*[^,]*,\s*['"`]?(?:0o?)?777['"`]?\s*\)/g,
|
|
171
|
+
severity: 'high',
|
|
172
|
+
description: 'chmod 777 — world-readable/writable permissions',
|
|
173
|
+
cwe: 'CWE-732',
|
|
174
|
+
languages: ['ts', 'js', 'py']
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: 'overly_permissive',
|
|
178
|
+
regex: /(?:Access-Control-Allow-Origin|x-powered-by)['"`,\s:]+\*/gi,
|
|
179
|
+
severity: 'high',
|
|
180
|
+
description: 'Wildcard Access-Control-Allow-Origin header',
|
|
181
|
+
cwe: 'CWE-942',
|
|
182
|
+
languages: ['ts', 'js', 'py']
|
|
183
|
+
},
|
|
184
|
+
// Unsafe Output Handling (OWASP #6)
|
|
185
|
+
{
|
|
186
|
+
type: 'unsafe_output',
|
|
187
|
+
regex: /res\.(?:send|write|end)\s*\(\s*(?:req\.|params|query|body|input|user)/g,
|
|
188
|
+
severity: 'high',
|
|
189
|
+
description: 'Reflecting user input in response without sanitization',
|
|
190
|
+
cwe: 'CWE-79',
|
|
191
|
+
languages: ['ts', 'js']
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
type: 'unsafe_output',
|
|
195
|
+
regex: /\$\{[^}]*(?:req\.|params|query|body|input|user)[^}]*\}.*(?:html|template|render)/gi,
|
|
196
|
+
severity: 'high',
|
|
197
|
+
description: 'User input interpolated into template/HTML output',
|
|
198
|
+
cwe: 'CWE-79',
|
|
199
|
+
languages: ['ts', 'js', 'py']
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: 'unsafe_output',
|
|
203
|
+
regex: /eval\s*\(\s*(?:req\.|params|query|body|input|user)/g,
|
|
204
|
+
severity: 'critical',
|
|
205
|
+
description: 'eval() with user input — code injection',
|
|
206
|
+
cwe: 'CWE-94',
|
|
207
|
+
languages: ['ts', 'js', 'py']
|
|
208
|
+
},
|
|
209
|
+
// Missing Input Validation (OWASP #8)
|
|
210
|
+
{
|
|
211
|
+
type: 'missing_input_validation',
|
|
212
|
+
regex: /JSON\.parse\s*\(\s*(?:req\.body|request\.body|body|data|input)\s*\)/g,
|
|
213
|
+
severity: 'medium',
|
|
214
|
+
description: 'JSON.parse on raw input without schema validation',
|
|
215
|
+
cwe: 'CWE-20',
|
|
216
|
+
languages: ['ts', 'js']
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: 'missing_input_validation',
|
|
220
|
+
regex: /(?:as\s+any|:\s*any)\s*(?:[;,)\]}])/g,
|
|
221
|
+
severity: 'medium',
|
|
222
|
+
description: 'Type assertion to "any" bypasses type safety',
|
|
223
|
+
cwe: 'CWE-20',
|
|
224
|
+
languages: ['ts']
|
|
225
|
+
},
|
|
134
226
|
];
|
|
135
227
|
export class SecurityPatternsGate extends Gate {
|
|
136
228
|
config;
|
|
@@ -145,9 +237,14 @@ export class SecurityPatternsGate extends Gate {
|
|
|
145
237
|
hardcoded_secrets: config.hardcoded_secrets ?? true,
|
|
146
238
|
insecure_randomness: config.insecure_randomness ?? true,
|
|
147
239
|
command_injection: config.command_injection ?? true,
|
|
240
|
+
redos: config.redos ?? true,
|
|
241
|
+
overly_permissive: config.overly_permissive ?? true,
|
|
242
|
+
unsafe_output: config.unsafe_output ?? true,
|
|
243
|
+
missing_input_validation: config.missing_input_validation ?? true,
|
|
148
244
|
block_on_severity: config.block_on_severity ?? 'high',
|
|
149
245
|
};
|
|
150
246
|
}
|
|
247
|
+
get provenance() { return 'security'; }
|
|
151
248
|
async run(context) {
|
|
152
249
|
if (!this.config.enabled) {
|
|
153
250
|
return [];
|
|
@@ -177,6 +274,10 @@ export class SecurityPatternsGate extends Gate {
|
|
|
177
274
|
case 'hardcoded_secrets': return this.config.hardcoded_secrets;
|
|
178
275
|
case 'insecure_randomness': return this.config.insecure_randomness;
|
|
179
276
|
case 'command_injection': return this.config.command_injection;
|
|
277
|
+
case 'redos': return this.config.redos;
|
|
278
|
+
case 'overly_permissive': return this.config.overly_permissive;
|
|
279
|
+
case 'unsafe_output': return this.config.unsafe_output;
|
|
280
|
+
case 'missing_input_validation': return this.config.missing_input_validation;
|
|
180
281
|
default: return true;
|
|
181
282
|
}
|
|
182
283
|
});
|
package/dist/gates/structure.js
CHANGED
|
@@ -17,7 +17,7 @@ export class StructureGate extends Gate {
|
|
|
17
17
|
}
|
|
18
18
|
if (missing.length > 0) {
|
|
19
19
|
return [
|
|
20
|
-
this.createFailure('The following required files are missing:', missing, 'Create these files to maintain project documentation and consistency.'),
|
|
20
|
+
this.createFailure('The following required files are missing:', missing, 'Create these files to maintain project documentation and consistency.', undefined, undefined, undefined, 'low'),
|
|
21
21
|
];
|
|
22
22
|
}
|
|
23
23
|
return [];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight per-file checker for hook integration.
|
|
3
|
+
*
|
|
4
|
+
* Runs a fast subset of Rigour gates on individual files,
|
|
5
|
+
* designed to complete in <200ms for real-time hook feedback.
|
|
6
|
+
*
|
|
7
|
+
* Used by all tool-specific hooks (Claude, Cursor, Cline, Windsurf).
|
|
8
|
+
*
|
|
9
|
+
* @since v3.0.0
|
|
10
|
+
*/
|
|
11
|
+
import type { HookCheckerResult } from './types.js';
|
|
12
|
+
interface CheckerOptions {
|
|
13
|
+
cwd: string;
|
|
14
|
+
files: string[];
|
|
15
|
+
timeout_ms?: number;
|
|
16
|
+
block_on_failure?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run fast gates on a set of files.
|
|
20
|
+
* Returns structured JSON for hook consumers.
|
|
21
|
+
*/
|
|
22
|
+
export declare function runHookChecker(options: CheckerOptions): Promise<HookCheckerResult>;
|
|
23
|
+
export {};
|