@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.
Files changed (117) hide show
  1. package/README.md +58 -0
  2. package/dist/context.test.js +2 -3
  3. package/dist/environment.test.js +2 -1
  4. package/dist/gates/agent-team.d.ts +2 -1
  5. package/dist/gates/agent-team.js +1 -0
  6. package/dist/gates/base.d.ts +3 -1
  7. package/dist/gates/base.js +3 -0
  8. package/dist/gates/checkpoint.d.ts +2 -1
  9. package/dist/gates/checkpoint.js +3 -2
  10. package/dist/gates/context-window-artifacts.d.ts +2 -1
  11. package/dist/gates/context-window-artifacts.js +6 -3
  12. package/dist/gates/context.d.ts +2 -1
  13. package/dist/gates/context.js +1 -0
  14. package/dist/gates/coverage.js +3 -1
  15. package/dist/gates/dependency.js +5 -5
  16. package/dist/gates/duplication-drift.d.ts +2 -1
  17. package/dist/gates/duplication-drift.js +4 -1
  18. package/dist/gates/environment.js +4 -4
  19. package/dist/gates/hallucinated-imports.d.ts +21 -2
  20. package/dist/gates/hallucinated-imports.js +116 -2
  21. package/dist/gates/inconsistent-error-handling.d.ts +2 -1
  22. package/dist/gates/inconsistent-error-handling.js +21 -7
  23. package/dist/gates/promise-safety.d.ts +68 -0
  24. package/dist/gates/promise-safety.js +509 -0
  25. package/dist/gates/retry-loop-breaker.d.ts +2 -1
  26. package/dist/gates/retry-loop-breaker.js +2 -1
  27. package/dist/gates/runner.js +34 -1
  28. package/dist/gates/safety.d.ts +2 -1
  29. package/dist/gates/safety.js +2 -1
  30. package/dist/gates/security-patterns-owasp.test.d.ts +1 -0
  31. package/dist/gates/security-patterns-owasp.test.js +171 -0
  32. package/dist/gates/security-patterns.d.ts +6 -1
  33. package/dist/gates/security-patterns.js +101 -0
  34. package/dist/gates/structure.js +1 -1
  35. package/dist/hooks/checker.d.ts +23 -0
  36. package/dist/hooks/checker.js +222 -0
  37. package/dist/hooks/checker.test.d.ts +1 -0
  38. package/dist/hooks/checker.test.js +132 -0
  39. package/dist/hooks/index.d.ts +9 -0
  40. package/dist/hooks/index.js +8 -0
  41. package/dist/hooks/standalone-checker.d.ts +15 -0
  42. package/dist/hooks/standalone-checker.js +106 -0
  43. package/dist/hooks/templates.d.ts +22 -0
  44. package/dist/hooks/templates.js +232 -0
  45. package/dist/hooks/types.d.ts +34 -0
  46. package/dist/hooks/types.js +21 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +2 -0
  49. package/dist/services/fix-packet-service.d.ts +0 -1
  50. package/dist/services/fix-packet-service.js +9 -14
  51. package/dist/services/score-history.d.ts +54 -0
  52. package/dist/services/score-history.js +122 -0
  53. package/dist/templates/index.js +176 -0
  54. package/dist/types/fix-packet.d.ts +5 -5
  55. package/dist/types/fix-packet.js +1 -1
  56. package/dist/types/index.d.ts +207 -0
  57. package/dist/types/index.js +32 -0
  58. package/package.json +21 -1
  59. package/src/context.test.ts +0 -256
  60. package/src/discovery.test.ts +0 -88
  61. package/src/discovery.ts +0 -112
  62. package/src/environment.test.ts +0 -115
  63. package/src/gates/agent-team.test.ts +0 -134
  64. package/src/gates/agent-team.ts +0 -210
  65. package/src/gates/ast-handlers/base.ts +0 -13
  66. package/src/gates/ast-handlers/python.ts +0 -145
  67. package/src/gates/ast-handlers/python_parser.py +0 -181
  68. package/src/gates/ast-handlers/typescript.ts +0 -264
  69. package/src/gates/ast-handlers/universal.ts +0 -184
  70. package/src/gates/ast.ts +0 -54
  71. package/src/gates/base.ts +0 -28
  72. package/src/gates/checkpoint.test.ts +0 -135
  73. package/src/gates/checkpoint.ts +0 -311
  74. package/src/gates/content.ts +0 -51
  75. package/src/gates/context-window-artifacts.ts +0 -277
  76. package/src/gates/context.ts +0 -270
  77. package/src/gates/coverage.ts +0 -74
  78. package/src/gates/dependency.ts +0 -108
  79. package/src/gates/duplication-drift.ts +0 -231
  80. package/src/gates/environment.ts +0 -94
  81. package/src/gates/file.ts +0 -46
  82. package/src/gates/hallucinated-imports.ts +0 -361
  83. package/src/gates/inconsistent-error-handling.ts +0 -254
  84. package/src/gates/retry-loop-breaker.ts +0 -151
  85. package/src/gates/runner.ts +0 -188
  86. package/src/gates/safety.ts +0 -56
  87. package/src/gates/security-patterns.test.ts +0 -162
  88. package/src/gates/security-patterns.ts +0 -306
  89. package/src/gates/structure.ts +0 -36
  90. package/src/index.ts +0 -13
  91. package/src/pattern-index/embeddings.ts +0 -84
  92. package/src/pattern-index/index.ts +0 -59
  93. package/src/pattern-index/indexer.test.ts +0 -276
  94. package/src/pattern-index/indexer.ts +0 -1023
  95. package/src/pattern-index/matcher.test.ts +0 -293
  96. package/src/pattern-index/matcher.ts +0 -493
  97. package/src/pattern-index/overrides.ts +0 -235
  98. package/src/pattern-index/security.ts +0 -151
  99. package/src/pattern-index/staleness.test.ts +0 -313
  100. package/src/pattern-index/staleness.ts +0 -568
  101. package/src/pattern-index/types.ts +0 -339
  102. package/src/safety.test.ts +0 -53
  103. package/src/services/adaptive-thresholds.test.ts +0 -189
  104. package/src/services/adaptive-thresholds.ts +0 -275
  105. package/src/services/context-engine.ts +0 -104
  106. package/src/services/fix-packet-service.ts +0 -42
  107. package/src/services/state-service.ts +0 -138
  108. package/src/smoke.test.ts +0 -18
  109. package/src/templates/index.ts +0 -338
  110. package/src/types/fix-packet.ts +0 -32
  111. package/src/types/index.ts +0 -200
  112. package/src/utils/logger.ts +0 -43
  113. package/src/utils/scanner.test.ts +0 -37
  114. package/src/utils/scanner.ts +0 -43
  115. package/tsconfig.json +0 -10
  116. package/vitest.config.ts +0 -7
  117. package/vitest.setup.ts +0 -30
@@ -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
  }
@@ -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
  }
@@ -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
  });
@@ -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 {};