@rigour-labs/core 2.18.0 → 2.18.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 (40) hide show
  1. package/dist/context.test.js +15 -1
  2. package/dist/discovery.js +13 -1
  3. package/dist/gates/agent-team.d.ts +50 -0
  4. package/dist/gates/agent-team.js +159 -0
  5. package/dist/gates/agent-team.test.d.ts +1 -0
  6. package/dist/gates/agent-team.test.js +113 -0
  7. package/dist/gates/checkpoint.d.ts +72 -0
  8. package/dist/gates/checkpoint.js +231 -0
  9. package/dist/gates/checkpoint.test.d.ts +1 -0
  10. package/dist/gates/checkpoint.test.js +102 -0
  11. package/dist/gates/context.d.ts +35 -0
  12. package/dist/gates/context.js +151 -2
  13. package/dist/gates/runner.js +15 -0
  14. package/dist/gates/security-patterns.d.ts +48 -0
  15. package/dist/gates/security-patterns.js +236 -0
  16. package/dist/gates/security-patterns.test.d.ts +1 -0
  17. package/dist/gates/security-patterns.test.js +133 -0
  18. package/dist/services/adaptive-thresholds.d.ts +63 -0
  19. package/dist/services/adaptive-thresholds.js +204 -0
  20. package/dist/services/adaptive-thresholds.test.d.ts +1 -0
  21. package/dist/services/adaptive-thresholds.test.js +129 -0
  22. package/dist/templates/index.js +34 -0
  23. package/dist/types/fix-packet.d.ts +4 -4
  24. package/dist/types/index.d.ts +404 -0
  25. package/dist/types/index.js +36 -0
  26. package/package.json +1 -1
  27. package/src/context.test.ts +15 -1
  28. package/src/discovery.ts +14 -2
  29. package/src/gates/agent-team.test.ts +134 -0
  30. package/src/gates/agent-team.ts +210 -0
  31. package/src/gates/checkpoint.test.ts +135 -0
  32. package/src/gates/checkpoint.ts +311 -0
  33. package/src/gates/context.ts +200 -2
  34. package/src/gates/runner.ts +18 -0
  35. package/src/gates/security-patterns.test.ts +162 -0
  36. package/src/gates/security-patterns.ts +303 -0
  37. package/src/services/adaptive-thresholds.test.ts +189 -0
  38. package/src/services/adaptive-thresholds.ts +275 -0
  39. package/src/templates/index.ts +34 -0
  40. package/src/types/index.ts +36 -0
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { CheckpointGate, recordCheckpoint, getCheckpointSession, clearCheckpointSession, getOrCreateCheckpointSession, completeCheckpointSession, abortCheckpointSession } from './checkpoint.js';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import * as os from 'os';
6
+ describe('CheckpointGate', () => {
7
+ let testDir;
8
+ beforeEach(() => {
9
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'checkpoint-test-'));
10
+ });
11
+ afterEach(() => {
12
+ clearCheckpointSession(testDir);
13
+ fs.rmSync(testDir, { recursive: true, force: true });
14
+ });
15
+ describe('gate initialization', () => {
16
+ it('should create gate with default config', () => {
17
+ const gate = new CheckpointGate();
18
+ expect(gate.id).toBe('checkpoint');
19
+ expect(gate.title).toBe('Checkpoint Supervision');
20
+ });
21
+ it('should skip when not enabled', async () => {
22
+ const gate = new CheckpointGate({ enabled: false });
23
+ const failures = await gate.run({ cwd: testDir });
24
+ expect(failures).toEqual([]);
25
+ });
26
+ });
27
+ describe('session management', () => {
28
+ it('should create a new session', () => {
29
+ const session = getOrCreateCheckpointSession(testDir);
30
+ expect(session.sessionId).toMatch(/^chk-session-/);
31
+ expect(session.status).toBe('active');
32
+ expect(session.checkpoints).toHaveLength(0);
33
+ });
34
+ it('should record a checkpoint', () => {
35
+ const result = recordCheckpoint(testDir, 25, // progressPct
36
+ ['src/api/users.ts'], 'Implemented user API', 85 // qualityScore
37
+ );
38
+ expect(result.continue).toBe(true);
39
+ expect(result.checkpoint.progressPct).toBe(25);
40
+ expect(result.checkpoint.qualityScore).toBe(85);
41
+ });
42
+ it('should persist session to disk', () => {
43
+ recordCheckpoint(testDir, 50, [], 'Test', 90);
44
+ const sessionPath = path.join(testDir, '.rigour', 'checkpoint-session.json');
45
+ expect(fs.existsSync(sessionPath)).toBe(true);
46
+ });
47
+ it('should complete session', () => {
48
+ getOrCreateCheckpointSession(testDir);
49
+ completeCheckpointSession(testDir);
50
+ const session = getCheckpointSession(testDir);
51
+ expect(session?.status).toBe('completed');
52
+ });
53
+ it('should abort session with reason', () => {
54
+ getOrCreateCheckpointSession(testDir);
55
+ abortCheckpointSession(testDir, 'Quality too low');
56
+ const session = getCheckpointSession(testDir);
57
+ expect(session?.status).toBe('aborted');
58
+ expect(session?.checkpoints[0].summary).toContain('Quality too low');
59
+ });
60
+ });
61
+ describe('quality threshold', () => {
62
+ it('should continue when quality above threshold', () => {
63
+ const result = recordCheckpoint(testDir, 50, [], 'Good work', 85);
64
+ expect(result.continue).toBe(true);
65
+ expect(result.warnings).toHaveLength(0);
66
+ });
67
+ it('should stop when quality below threshold', () => {
68
+ const result = recordCheckpoint(testDir, 50, [], 'Poor work', 70);
69
+ expect(result.continue).toBe(false);
70
+ expect(result.warnings).toContain('Quality score 70% is below threshold 80%');
71
+ });
72
+ });
73
+ describe('drift detection', () => {
74
+ it('should detect quality degradation', () => {
75
+ // Record several checkpoints with declining quality
76
+ recordCheckpoint(testDir, 20, [], 'Start', 95);
77
+ recordCheckpoint(testDir, 40, [], 'Middle', 90);
78
+ const result = recordCheckpoint(testDir, 60, [], 'Decline', 75);
79
+ expect(result.warnings.some(w => w.includes('Drift detected'))).toBe(true);
80
+ });
81
+ it('should not flag stable quality', () => {
82
+ recordCheckpoint(testDir, 20, [], 'Start', 85);
83
+ recordCheckpoint(testDir, 40, [], 'Middle', 85);
84
+ const result = recordCheckpoint(testDir, 60, [], 'Stable', 85);
85
+ expect(result.warnings.filter(w => w.includes('Drift'))).toHaveLength(0);
86
+ });
87
+ });
88
+ describe('gate run', () => {
89
+ it('should pass with healthy checkpoints', async () => {
90
+ const gate = new CheckpointGate({ enabled: true, quality_threshold: 80 });
91
+ recordCheckpoint(testDir, 50, [], 'Good work', 90);
92
+ const failures = await gate.run({ cwd: testDir });
93
+ expect(failures).toHaveLength(0);
94
+ });
95
+ it('should fail when quality below threshold', async () => {
96
+ const gate = new CheckpointGate({ enabled: true, quality_threshold: 80 });
97
+ recordCheckpoint(testDir, 50, [], 'Poor work', 70);
98
+ const failures = await gate.run({ cwd: testDir });
99
+ expect(failures.some(f => f.title === 'Quality Below Threshold')).toBe(true);
100
+ });
101
+ });
102
+ });
@@ -1,8 +1,43 @@
1
1
  import { Gate, GateContext } from './base.js';
2
2
  import { Failure, Gates } from '../types/index.js';
3
+ /**
4
+ * Extended Context Configuration (v2.14+)
5
+ * For 1M token frontier models like Opus 4.6
6
+ */
7
+ export interface ExtendedContextConfig {
8
+ enabled?: boolean;
9
+ sensitivity?: number;
10
+ mining_depth?: number;
11
+ cross_file_patterns?: boolean;
12
+ naming_consistency?: boolean;
13
+ import_relationships?: boolean;
14
+ max_cross_file_depth?: number;
15
+ }
3
16
  export declare class ContextGate extends Gate {
4
17
  private config;
18
+ private extendedConfig;
5
19
  constructor(config: Gates);
6
20
  run(context: GateContext): Promise<Failure[]>;
7
21
  private checkEnvDrift;
22
+ /**
23
+ * Collect naming patterns (function names, class names, variable names)
24
+ */
25
+ private collectNamingPatterns;
26
+ /**
27
+ * Collect import patterns
28
+ */
29
+ private collectImportPatterns;
30
+ /**
31
+ * Analyze naming consistency across files
32
+ */
33
+ private analyzeNamingConsistency;
34
+ /**
35
+ * Analyze import patterns for consistency
36
+ */
37
+ private analyzeImportPatterns;
38
+ /**
39
+ * Detect casing convention of an identifier
40
+ */
41
+ private detectCasing;
42
+ private addPattern;
8
43
  }
@@ -4,25 +4,51 @@ import fs from 'fs-extra';
4
4
  import path from 'path';
5
5
  export class ContextGate extends Gate {
6
6
  config;
7
+ extendedConfig;
7
8
  constructor(config) {
8
9
  super('context-drift', 'Context Awareness & Drift Detection');
9
10
  this.config = config;
11
+ this.extendedConfig = {
12
+ enabled: config.context?.enabled ?? false,
13
+ sensitivity: config.context?.sensitivity ?? 0.8,
14
+ mining_depth: config.context?.mining_depth ?? 100,
15
+ cross_file_patterns: true, // Default ON for frontier model support
16
+ naming_consistency: true,
17
+ import_relationships: true,
18
+ max_cross_file_depth: 50,
19
+ };
10
20
  }
11
21
  async run(context) {
12
22
  const failures = [];
13
23
  const record = context.record;
14
- if (!record || !this.config.context?.enabled)
24
+ if (!record || !this.extendedConfig.enabled)
15
25
  return [];
16
26
  const files = await FileScanner.findFiles({ cwd: context.cwd });
17
27
  const envAnchors = record.anchors.filter(a => a.type === 'env' && a.confidence >= 1);
28
+ // Collect all patterns across files for cross-file analysis
29
+ const namingPatterns = new Map();
30
+ const importPatterns = new Map();
18
31
  for (const file of files) {
19
32
  try {
20
33
  const content = await fs.readFile(path.join(context.cwd, file), 'utf-8');
21
- // 1. Detect Redundant Suffixes (The Golden Example)
34
+ // 1. Original: Detect Redundant Suffixes (The Golden Example)
22
35
  this.checkEnvDrift(content, file, envAnchors, failures);
36
+ // 2. NEW: Cross-file pattern collection
37
+ if (this.extendedConfig.cross_file_patterns) {
38
+ this.collectNamingPatterns(content, file, namingPatterns);
39
+ this.collectImportPatterns(content, file, importPatterns);
40
+ }
23
41
  }
24
42
  catch (e) { }
25
43
  }
44
+ // 3. NEW: Analyze naming consistency across files
45
+ if (this.extendedConfig.naming_consistency) {
46
+ this.analyzeNamingConsistency(namingPatterns, failures);
47
+ }
48
+ // 4. NEW: Analyze import relationship patterns
49
+ if (this.extendedConfig.import_relationships) {
50
+ this.analyzeImportPatterns(importPatterns, failures);
51
+ }
26
52
  return failures;
27
53
  }
28
54
  checkEnvDrift(content, file, anchors, failures) {
@@ -40,4 +66,127 @@ export class ContextGate extends Gate {
40
66
  }
41
67
  }
42
68
  }
69
+ /**
70
+ * Collect naming patterns (function names, class names, variable names)
71
+ */
72
+ collectNamingPatterns(content, file, patterns) {
73
+ // Function declarations
74
+ const funcMatches = content.matchAll(/(?:function|const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[=(]/g);
75
+ for (const match of funcMatches) {
76
+ const name = match[1];
77
+ const casing = this.detectCasing(name);
78
+ this.addPattern(patterns, 'function', { casing, file, count: 1 });
79
+ }
80
+ // Class declarations
81
+ const classMatches = content.matchAll(/class\s+([A-Za-z_$][A-Za-z0-9_$]*)/g);
82
+ for (const match of classMatches) {
83
+ const casing = this.detectCasing(match[1]);
84
+ this.addPattern(patterns, 'class', { casing, file, count: 1 });
85
+ }
86
+ // Interface declarations (TypeScript)
87
+ const interfaceMatches = content.matchAll(/interface\s+([A-Za-z_$][A-Za-z0-9_$]*)/g);
88
+ for (const match of interfaceMatches) {
89
+ const casing = this.detectCasing(match[1]);
90
+ this.addPattern(patterns, 'interface', { casing, file, count: 1 });
91
+ }
92
+ }
93
+ /**
94
+ * Collect import patterns
95
+ */
96
+ collectImportPatterns(content, file, patterns) {
97
+ // ES6 imports
98
+ const importMatches = content.matchAll(/import\s+(?:{[^}]+}|\*\s+as\s+\w+|\w+)\s+from\s+['"]([^'"]+)['"]/g);
99
+ for (const match of importMatches) {
100
+ const importPath = match[1];
101
+ if (!patterns.has(file)) {
102
+ patterns.set(file, []);
103
+ }
104
+ patterns.get(file).push(importPath);
105
+ }
106
+ }
107
+ /**
108
+ * Analyze naming consistency across files
109
+ */
110
+ analyzeNamingConsistency(patterns, failures) {
111
+ for (const [type, entries] of patterns) {
112
+ const casingCounts = new Map();
113
+ for (const entry of entries) {
114
+ casingCounts.set(entry.casing, (casingCounts.get(entry.casing) || 0) + entry.count);
115
+ }
116
+ // Find dominant casing
117
+ let dominant = '';
118
+ let maxCount = 0;
119
+ for (const [casing, count] of casingCounts) {
120
+ if (count > maxCount) {
121
+ dominant = casing;
122
+ maxCount = count;
123
+ }
124
+ }
125
+ // Report violations (non-dominant casing with significant usage)
126
+ const total = entries.reduce((sum, e) => sum + e.count, 0);
127
+ const threshold = total * (1 - (this.extendedConfig.sensitivity ?? 0.8));
128
+ for (const [casing, count] of casingCounts) {
129
+ if (casing !== dominant && count > threshold) {
130
+ const violatingFiles = entries.filter(e => e.casing === casing).map(e => e.file);
131
+ const uniqueFiles = [...new Set(violatingFiles)].slice(0, 5);
132
+ failures.push(this.createFailure(`Cross-file naming inconsistency: ${type} names use ${casing} in ${count} places (dominant is ${dominant})`, uniqueFiles, `Standardize ${type} naming to ${dominant}. Found ${casing} in: ${uniqueFiles.join(', ')}`, 'Naming Convention Drift'));
133
+ }
134
+ }
135
+ }
136
+ }
137
+ /**
138
+ * Analyze import patterns for consistency
139
+ */
140
+ analyzeImportPatterns(patterns, failures) {
141
+ // Check for mixed import styles (relative vs absolute)
142
+ const relativeCount = new Map();
143
+ const absoluteCount = new Map();
144
+ for (const [file, imports] of patterns) {
145
+ for (const imp of imports) {
146
+ if (imp.startsWith('.') || imp.startsWith('..')) {
147
+ relativeCount.set(file, (relativeCount.get(file) || 0) + 1);
148
+ }
149
+ else if (!imp.startsWith('@') && !imp.includes('/')) {
150
+ // Skip external packages
151
+ }
152
+ else {
153
+ absoluteCount.set(file, (absoluteCount.get(file) || 0) + 1);
154
+ }
155
+ }
156
+ }
157
+ // Detect files with both relative AND absolute local imports
158
+ const mixedFiles = [];
159
+ for (const file of patterns.keys()) {
160
+ const hasRelative = (relativeCount.get(file) || 0) > 0;
161
+ const hasAbsolute = (absoluteCount.get(file) || 0) > 0;
162
+ if (hasRelative && hasAbsolute) {
163
+ mixedFiles.push(file);
164
+ }
165
+ }
166
+ if (mixedFiles.length > 3) {
167
+ failures.push(this.createFailure(`Cross-file import inconsistency: ${mixedFiles.length} files mix relative and absolute imports`, mixedFiles.slice(0, 5), 'Standardize import style across the codebase. Use either relative (./foo) or path aliases (@/foo) consistently.', 'Import Pattern Drift'));
168
+ }
169
+ }
170
+ /**
171
+ * Detect casing convention of an identifier
172
+ */
173
+ detectCasing(name) {
174
+ if (/^[A-Z][a-z]/.test(name) && /[a-z][A-Z]/.test(name))
175
+ return 'PascalCase';
176
+ if (/^[a-z]/.test(name) && /[a-z][A-Z]/.test(name))
177
+ return 'camelCase';
178
+ if (/^[a-z]+(_[a-z]+)+$/.test(name))
179
+ return 'snake_case';
180
+ if (/^[A-Z]+(_[A-Z]+)*$/.test(name))
181
+ return 'SCREAMING_SNAKE';
182
+ if (/^[A-Z][a-zA-Z]*$/.test(name))
183
+ return 'PascalCase';
184
+ return 'unknown';
185
+ }
186
+ addPattern(patterns, type, entry) {
187
+ if (!patterns.has(type)) {
188
+ patterns.set(type, []);
189
+ }
190
+ patterns.get(type).push(entry);
191
+ }
43
192
  }
@@ -9,6 +9,9 @@ import { ContextGate } from './context.js';
9
9
  import { ContextEngine } from '../services/context-engine.js';
10
10
  import { EnvironmentGate } from './environment.js';
11
11
  import { RetryLoopBreakerGate } from './retry-loop-breaker.js';
12
+ import { AgentTeamGate } from './agent-team.js';
13
+ import { CheckpointGate } from './checkpoint.js';
14
+ import { SecurityPatternsGate } from './security-patterns.js';
12
15
  import { execa } from 'execa';
13
16
  import { Logger } from '../utils/logger.js';
14
17
  export class GateRunner {
@@ -40,6 +43,18 @@ export class GateRunner {
40
43
  if (this.config.gates.context?.enabled) {
41
44
  this.gates.push(new ContextGate(this.config.gates));
42
45
  }
46
+ // Agent Team Governance Gate (for Opus 4.6 / GPT-5.3 multi-agent workflows)
47
+ if (this.config.gates.agent_team?.enabled) {
48
+ this.gates.push(new AgentTeamGate(this.config.gates.agent_team));
49
+ }
50
+ // Checkpoint Supervision Gate (for long-running GPT-5.3 coworking mode)
51
+ if (this.config.gates.checkpoint?.enabled) {
52
+ this.gates.push(new CheckpointGate(this.config.gates.checkpoint));
53
+ }
54
+ // Security Patterns Gate (code-level vulnerability detection)
55
+ if (this.config.gates.security?.enabled) {
56
+ this.gates.push(new SecurityPatternsGate(this.config.gates.security));
57
+ }
43
58
  // Environment Alignment Gate (Should be prioritized)
44
59
  if (this.config.gates.environment?.enabled) {
45
60
  this.gates.unshift(new EnvironmentGate(this.config.gates));
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Security Patterns Gate
3
+ *
4
+ * Detects code-level security vulnerabilities for frontier models
5
+ * that may generate insecure patterns at scale.
6
+ *
7
+ * Patterns covered:
8
+ * - SQL Injection
9
+ * - XSS (Cross-Site Scripting)
10
+ * - Path Traversal
11
+ * - Hardcoded Secrets
12
+ * - Insecure Randomness
13
+ * - Command Injection
14
+ *
15
+ * @since v2.14.0
16
+ */
17
+ import { Gate, GateContext } from './base.js';
18
+ import { Failure } from '../types/index.js';
19
+ export interface SecurityVulnerability {
20
+ type: string;
21
+ severity: 'critical' | 'high' | 'medium' | 'low';
22
+ file: string;
23
+ line: number;
24
+ match: string;
25
+ description: string;
26
+ cwe?: string;
27
+ }
28
+ export interface SecurityPatternsConfig {
29
+ enabled?: boolean;
30
+ sql_injection?: boolean;
31
+ xss?: boolean;
32
+ path_traversal?: boolean;
33
+ hardcoded_secrets?: boolean;
34
+ insecure_randomness?: boolean;
35
+ command_injection?: boolean;
36
+ block_on_severity?: 'critical' | 'high' | 'medium' | 'low';
37
+ }
38
+ export declare class SecurityPatternsGate extends Gate {
39
+ private config;
40
+ private severityOrder;
41
+ constructor(config?: SecurityPatternsConfig);
42
+ run(context: GateContext): Promise<Failure[]>;
43
+ private scanFileForVulnerabilities;
44
+ }
45
+ /**
46
+ * Quick helper to check a single file for security issues
47
+ */
48
+ export declare function checkSecurityPatterns(filePath: string, config?: SecurityPatternsConfig): Promise<SecurityVulnerability[]>;
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Security Patterns Gate
3
+ *
4
+ * Detects code-level security vulnerabilities for frontier models
5
+ * that may generate insecure patterns at scale.
6
+ *
7
+ * Patterns covered:
8
+ * - SQL Injection
9
+ * - XSS (Cross-Site Scripting)
10
+ * - Path Traversal
11
+ * - Hardcoded Secrets
12
+ * - Insecure Randomness
13
+ * - Command Injection
14
+ *
15
+ * @since v2.14.0
16
+ */
17
+ import { Gate } from './base.js';
18
+ import { FileScanner } from '../utils/scanner.js';
19
+ import { Logger } from '../utils/logger.js';
20
+ import fs from 'fs-extra';
21
+ import path from 'path';
22
+ // Pattern definitions with regex and metadata
23
+ const VULNERABILITY_PATTERNS = [
24
+ // SQL Injection
25
+ {
26
+ type: 'sql_injection',
27
+ regex: /(?:execute|query|raw|exec)\s*\(\s*[`'"].*\$\{.+\}|`\s*\+\s*\w+|\$\{.+\}.*(?:SELECT|INSERT|UPDATE|DELETE|DROP)/gi,
28
+ severity: 'critical',
29
+ description: 'Potential SQL injection: User input concatenated into SQL query',
30
+ cwe: 'CWE-89',
31
+ languages: ['ts', 'js', 'py']
32
+ },
33
+ {
34
+ type: 'sql_injection',
35
+ regex: /\.query\s*\(\s*['"`].*\+.*\+.*['"`]\s*\)/g,
36
+ severity: 'critical',
37
+ description: 'SQL query built with string concatenation',
38
+ cwe: 'CWE-89',
39
+ languages: ['ts', 'js']
40
+ },
41
+ // XSS
42
+ {
43
+ type: 'xss',
44
+ regex: /innerHTML\s*=\s*(?!\s*['"`]\s*['"`])[^;]+/g,
45
+ severity: 'high',
46
+ description: 'Potential XSS: innerHTML assignment with dynamic content',
47
+ cwe: 'CWE-79',
48
+ languages: ['ts', 'js', 'tsx', 'jsx']
49
+ },
50
+ {
51
+ type: 'xss',
52
+ regex: /dangerouslySetInnerHTML\s*=\s*\{/g,
53
+ severity: 'high',
54
+ description: 'dangerouslySetInnerHTML usage (ensure content is sanitized)',
55
+ cwe: 'CWE-79',
56
+ languages: ['tsx', 'jsx']
57
+ },
58
+ {
59
+ type: 'xss',
60
+ regex: /document\.write\s*\(/g,
61
+ severity: 'high',
62
+ description: 'document.write is dangerous for XSS',
63
+ cwe: 'CWE-79',
64
+ languages: ['ts', 'js']
65
+ },
66
+ // Path Traversal
67
+ {
68
+ type: 'path_traversal',
69
+ regex: /(?:readFile|writeFile|readdir|unlink|rmdir)\s*\([^)]*(?:req\.(?:params|query|body)|\.\.\/)/g,
70
+ severity: 'high',
71
+ description: 'Potential path traversal: File operation with user input',
72
+ cwe: 'CWE-22',
73
+ languages: ['ts', 'js']
74
+ },
75
+ {
76
+ type: 'path_traversal',
77
+ regex: /path\.join\s*\([^)]*req\./g,
78
+ severity: 'medium',
79
+ description: 'path.join with request data (verify input sanitization)',
80
+ cwe: 'CWE-22',
81
+ languages: ['ts', 'js']
82
+ },
83
+ // Hardcoded Secrets
84
+ {
85
+ type: 'hardcoded_secrets',
86
+ regex: /(?:password|secret|api_key|apikey|auth_token|access_token|private_key)\s*[:=]\s*['"][^'"]{8,}['"]/gi,
87
+ severity: 'critical',
88
+ description: 'Hardcoded secret detected in code',
89
+ cwe: 'CWE-798',
90
+ languages: ['ts', 'js', 'py', 'java', 'go']
91
+ },
92
+ {
93
+ type: 'hardcoded_secrets',
94
+ regex: /(?:sk-|pk-|rk-|ghp_|gho_|ghu_|ghs_|ghr_)[a-zA-Z0-9]{20,}/g,
95
+ severity: 'critical',
96
+ description: 'API key pattern detected (OpenAI, GitHub, etc.)',
97
+ cwe: 'CWE-798',
98
+ languages: ['*']
99
+ },
100
+ {
101
+ type: 'hardcoded_secrets',
102
+ regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
103
+ severity: 'critical',
104
+ description: 'Private key embedded in source code',
105
+ cwe: 'CWE-798',
106
+ languages: ['*']
107
+ },
108
+ // Insecure Randomness
109
+ {
110
+ type: 'insecure_randomness',
111
+ regex: /Math\.random\s*\(\s*\)/g,
112
+ severity: 'medium',
113
+ description: 'Math.random() is not cryptographically secure',
114
+ cwe: 'CWE-338',
115
+ languages: ['ts', 'js', 'tsx', 'jsx']
116
+ },
117
+ // Command Injection
118
+ {
119
+ type: 'command_injection',
120
+ regex: /(?:exec|spawn|execSync|spawnSync)\s*\([^)]*(?:req\.|`.*\$\{)/g,
121
+ severity: 'critical',
122
+ description: 'Potential command injection: shell execution with user input',
123
+ cwe: 'CWE-78',
124
+ languages: ['ts', 'js']
125
+ },
126
+ {
127
+ type: 'command_injection',
128
+ regex: /child_process.*\s*\.\s*(?:exec|spawn)\s*\(/g,
129
+ severity: 'high',
130
+ description: 'child_process usage detected (verify input sanitization)',
131
+ cwe: 'CWE-78',
132
+ languages: ['ts', 'js']
133
+ },
134
+ ];
135
+ export class SecurityPatternsGate extends Gate {
136
+ config;
137
+ severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
138
+ constructor(config = {}) {
139
+ super('security-patterns', 'Security Pattern Detection');
140
+ this.config = {
141
+ enabled: config.enabled ?? false,
142
+ sql_injection: config.sql_injection ?? true,
143
+ xss: config.xss ?? true,
144
+ path_traversal: config.path_traversal ?? true,
145
+ hardcoded_secrets: config.hardcoded_secrets ?? true,
146
+ insecure_randomness: config.insecure_randomness ?? true,
147
+ command_injection: config.command_injection ?? true,
148
+ block_on_severity: config.block_on_severity ?? 'high',
149
+ };
150
+ }
151
+ async run(context) {
152
+ if (!this.config.enabled) {
153
+ return [];
154
+ }
155
+ const failures = [];
156
+ const vulnerabilities = [];
157
+ const files = await FileScanner.findFiles({
158
+ cwd: context.cwd,
159
+ patterns: ['**/*.{ts,js,tsx,jsx,py,java,go}'],
160
+ });
161
+ Logger.info(`Security Patterns Gate: Scanning ${files.length} files`);
162
+ for (const file of files) {
163
+ try {
164
+ const fullPath = path.join(context.cwd, file);
165
+ const content = await fs.readFile(fullPath, 'utf-8');
166
+ const ext = path.extname(file).slice(1);
167
+ this.scanFileForVulnerabilities(content, file, ext, vulnerabilities);
168
+ }
169
+ catch (e) { }
170
+ }
171
+ // Filter by enabled checks
172
+ const filteredVulns = vulnerabilities.filter(v => {
173
+ switch (v.type) {
174
+ case 'sql_injection': return this.config.sql_injection;
175
+ case 'xss': return this.config.xss;
176
+ case 'path_traversal': return this.config.path_traversal;
177
+ case 'hardcoded_secrets': return this.config.hardcoded_secrets;
178
+ case 'insecure_randomness': return this.config.insecure_randomness;
179
+ case 'command_injection': return this.config.command_injection;
180
+ default: return true;
181
+ }
182
+ });
183
+ // Sort by severity
184
+ filteredVulns.sort((a, b) => this.severityOrder[a.severity] - this.severityOrder[b.severity]);
185
+ // Convert to failures based on block_on_severity threshold
186
+ const blockThreshold = this.severityOrder[this.config.block_on_severity ?? 'high'];
187
+ for (const vuln of filteredVulns) {
188
+ if (this.severityOrder[vuln.severity] <= blockThreshold) {
189
+ failures.push(this.createFailure(`[${vuln.cwe}] ${vuln.description} at line ${vuln.line}`, [vuln.file], `Found: "${vuln.match.slice(0, 60)}..." - Use parameterized queries/sanitization.`, `Security: ${vuln.type.replace('_', ' ').toUpperCase()}`));
190
+ }
191
+ }
192
+ if (filteredVulns.length > 0 && failures.length === 0) {
193
+ // Vulnerabilities found but below threshold - log info
194
+ Logger.info(`Security scan found ${filteredVulns.length} issues below ${this.config.block_on_severity} threshold`);
195
+ }
196
+ return failures;
197
+ }
198
+ scanFileForVulnerabilities(content, file, ext, vulnerabilities) {
199
+ const lines = content.split('\n');
200
+ for (const pattern of VULNERABILITY_PATTERNS) {
201
+ // Check if pattern applies to this file type
202
+ if (!pattern.languages.includes('*') && !pattern.languages.includes(ext)) {
203
+ continue;
204
+ }
205
+ // Reset regex state
206
+ pattern.regex.lastIndex = 0;
207
+ let match;
208
+ while ((match = pattern.regex.exec(content)) !== null) {
209
+ // Find line number
210
+ const beforeMatch = content.slice(0, match.index);
211
+ const lineNumber = beforeMatch.split('\n').length;
212
+ vulnerabilities.push({
213
+ type: pattern.type,
214
+ severity: pattern.severity,
215
+ file,
216
+ line: lineNumber,
217
+ match: match[0],
218
+ description: pattern.description,
219
+ cwe: pattern.cwe,
220
+ });
221
+ }
222
+ }
223
+ }
224
+ }
225
+ /**
226
+ * Quick helper to check a single file for security issues
227
+ */
228
+ export async function checkSecurityPatterns(filePath, config = { enabled: true }) {
229
+ const gate = new SecurityPatternsGate(config);
230
+ const content = await fs.readFile(filePath, 'utf-8');
231
+ const ext = path.extname(filePath).slice(1);
232
+ const vulnerabilities = [];
233
+ // Use the private method via reflection for testing
234
+ gate.scanFileForVulnerabilities(content, filePath, ext, vulnerabilities);
235
+ return vulnerabilities;
236
+ }
@@ -0,0 +1 @@
1
+ export {};