@rigour-labs/core 4.3.1 → 4.3.2

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.
@@ -0,0 +1,27 @@
1
+ import { Gate, GateContext } from './base.js';
2
+ import { Failure, Provenance } from '../types/index.js';
3
+ export interface FrontendSecretExposureConfig {
4
+ enabled?: boolean;
5
+ block_on_severity?: 'critical' | 'high' | 'medium' | 'low';
6
+ check_process_env?: boolean;
7
+ check_import_meta_env?: boolean;
8
+ secret_env_name_patterns?: string[];
9
+ safe_public_prefixes?: string[];
10
+ frontend_path_patterns?: string[];
11
+ server_path_patterns?: string[];
12
+ allowlist_env_names?: string[];
13
+ }
14
+ export declare class FrontendSecretExposureGate extends Gate {
15
+ private config;
16
+ private severityOrder;
17
+ constructor(config?: FrontendSecretExposureConfig);
18
+ protected get provenance(): Provenance;
19
+ run(context: GateContext): Promise<Failure[]>;
20
+ private shouldSkipFile;
21
+ private isClientBundled;
22
+ private isServerOnlyContent;
23
+ private findEnvExposures;
24
+ private collectMatches;
25
+ private isSecretLikeEnvName;
26
+ private matchesAnyPattern;
27
+ }
@@ -0,0 +1,174 @@
1
+ import { Gate } from './base.js';
2
+ import { FileScanner } from '../utils/scanner.js';
3
+ import { Logger } from '../utils/logger.js';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+ const DEFAULT_SECRET_ENV_NAME_PATTERNS = [
7
+ '(?:^|_)(?:secret|private)(?:_|$)',
8
+ '(?:^|_)(?:token|api[_-]?key|access[_-]?key|client[_-]?secret|signing|webhook)(?:_|$)',
9
+ '(?:^|_)(?:db[_-]?url|database[_-]?url|connection[_-]?string)(?:_|$)',
10
+ ];
11
+ const DEFAULT_SAFE_PUBLIC_PREFIXES = [
12
+ 'NEXT_PUBLIC_',
13
+ 'VITE_',
14
+ 'PUBLIC_',
15
+ 'NUXT_PUBLIC_',
16
+ 'REACT_APP_',
17
+ ];
18
+ const DEFAULT_FRONTEND_PATH_PATTERNS = [
19
+ '(^|/)pages/(?!api/)',
20
+ '(^|/)components/',
21
+ '(^|/)src/components/',
22
+ '(^|/)src/views/',
23
+ '(^|/)src/app/',
24
+ '(^|/)app/(?!api/)',
25
+ '(^|/)views/',
26
+ '(^|/)public/',
27
+ ];
28
+ const DEFAULT_SERVER_PATH_PATTERNS = [
29
+ '(^|/)pages/api/',
30
+ '(^|/)src/pages/api/',
31
+ '(^|/)app/api/',
32
+ '(^|/)src/app/api/',
33
+ '\\.server\\.(?:ts|tsx|js|jsx|mjs|cjs)$',
34
+ ];
35
+ export class FrontendSecretExposureGate extends Gate {
36
+ config;
37
+ severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
38
+ constructor(config = {}) {
39
+ super('frontend-secret-exposure', 'Frontend Secret Exposure Detection');
40
+ this.config = {
41
+ enabled: config.enabled ?? true,
42
+ block_on_severity: config.block_on_severity ?? 'high',
43
+ check_process_env: config.check_process_env ?? true,
44
+ check_import_meta_env: config.check_import_meta_env ?? true,
45
+ secret_env_name_patterns: config.secret_env_name_patterns ?? DEFAULT_SECRET_ENV_NAME_PATTERNS,
46
+ safe_public_prefixes: config.safe_public_prefixes ?? DEFAULT_SAFE_PUBLIC_PREFIXES,
47
+ frontend_path_patterns: config.frontend_path_patterns ?? DEFAULT_FRONTEND_PATH_PATTERNS,
48
+ server_path_patterns: config.server_path_patterns ?? DEFAULT_SERVER_PATH_PATTERNS,
49
+ allowlist_env_names: config.allowlist_env_names ?? [],
50
+ };
51
+ }
52
+ get provenance() { return 'security'; }
53
+ async run(context) {
54
+ if (!this.config.enabled)
55
+ return [];
56
+ const files = await FileScanner.findFiles({
57
+ cwd: context.cwd,
58
+ patterns: ['**/*.{ts,tsx,js,jsx,mjs,cjs}'],
59
+ ignore: [...(context.ignore || []), '**/node_modules/**', '**/dist/**', '**/build/**', '**/.next/**', '**/coverage/**'],
60
+ });
61
+ const scanFiles = files.filter(file => !this.shouldSkipFile(file));
62
+ const findings = [];
63
+ Logger.info(`Frontend Secret Exposure Gate: Scanning ${scanFiles.length} files`);
64
+ for (const file of scanFiles) {
65
+ const fullPath = path.join(context.cwd, file);
66
+ let content = '';
67
+ try {
68
+ content = await fs.readFile(fullPath, 'utf-8');
69
+ }
70
+ catch {
71
+ continue;
72
+ }
73
+ if (!this.isClientBundled(file, content))
74
+ continue;
75
+ findings.push(...this.findEnvExposures(file, content));
76
+ }
77
+ findings.sort((a, b) => this.severityOrder[a.severity] - this.severityOrder[b.severity]);
78
+ const threshold = this.severityOrder[this.config.block_on_severity];
79
+ return findings
80
+ .filter(f => this.severityOrder[f.severity] <= threshold)
81
+ .map(f => this.createFailure(`Potential frontend secret exposure: ${f.source}.${f.envVar} is referenced in client-bundled code.`, [f.file], 'Move secret usage to server-only code (API route/server action) and expose only public-safe values.', 'Security: Frontend Secret Exposure', f.line, f.line, f.severity));
82
+ }
83
+ shouldSkipFile(file) {
84
+ const normalized = file.replace(/\\/g, '/');
85
+ if (/\.(test|spec)\.(?:ts|tsx|js|jsx|mjs|cjs)$/i.test(normalized))
86
+ return true;
87
+ if (/\/(?:__tests__|tests|test|__test__|e2e|fixtures|mocks)\//.test(`/${normalized}`))
88
+ return true;
89
+ if (/\/(?:examples|studio-dist)\//.test(`/${normalized}`))
90
+ return true;
91
+ return false;
92
+ }
93
+ isClientBundled(file, content) {
94
+ const normalized = file.replace(/\\/g, '/');
95
+ if (this.matchesAnyPattern(normalized, this.config.server_path_patterns))
96
+ return false;
97
+ if (this.isServerOnlyContent(content))
98
+ return false;
99
+ if (/^\s*['"]use client['"]\s*;?/m.test(content))
100
+ return true;
101
+ if (this.matchesAnyPattern(normalized, this.config.frontend_path_patterns))
102
+ return true;
103
+ return false;
104
+ }
105
+ isServerOnlyContent(content) {
106
+ if (/from\s+['"]server-only['"]/.test(content))
107
+ return true;
108
+ if (/import\s+['"]server-only['"]/.test(content))
109
+ return true;
110
+ if (/export\s+async\s+function\s+getServerSideProps\s*\(/.test(content))
111
+ return true;
112
+ if (/export\s+async\s+function\s+getStaticProps\s*\(/.test(content))
113
+ return true;
114
+ if (/['"]use server['"]/.test(content))
115
+ return true;
116
+ return false;
117
+ }
118
+ findEnvExposures(file, content) {
119
+ const findings = [];
120
+ if (this.config.check_process_env) {
121
+ const processEnvRegex = /process\.env\.([A-Za-z_][A-Za-z0-9_]*)/g;
122
+ findings.push(...this.collectMatches(file, content, processEnvRegex, 'process.env'));
123
+ }
124
+ if (this.config.check_import_meta_env) {
125
+ const importMetaRegex = /import\.meta\.env\.([A-Za-z_][A-Za-z0-9_]*)/g;
126
+ findings.push(...this.collectMatches(file, content, importMetaRegex, 'import.meta.env'));
127
+ }
128
+ return findings;
129
+ }
130
+ collectMatches(file, content, regex, source) {
131
+ const matches = [];
132
+ const scanRegex = new RegExp(regex.source, 'g');
133
+ for (const match of content.matchAll(scanRegex)) {
134
+ const envVar = match[1];
135
+ if (!this.isSecretLikeEnvName(envVar))
136
+ continue;
137
+ const startIndex = match.index ?? 0;
138
+ const beforeMatch = content.slice(0, startIndex);
139
+ const line = beforeMatch.split('\n').length;
140
+ matches.push({
141
+ file,
142
+ line,
143
+ envVar,
144
+ source,
145
+ severity: 'high',
146
+ });
147
+ }
148
+ return matches;
149
+ }
150
+ isSecretLikeEnvName(envVar) {
151
+ if (this.config.allowlist_env_names.includes(envVar))
152
+ return false;
153
+ if (this.config.safe_public_prefixes.some(prefix => envVar.startsWith(prefix)))
154
+ return false;
155
+ return this.config.secret_env_name_patterns.some(pattern => {
156
+ try {
157
+ return new RegExp(pattern, 'i').test(envVar);
158
+ }
159
+ catch {
160
+ return false;
161
+ }
162
+ });
163
+ }
164
+ matchesAnyPattern(value, patterns) {
165
+ return patterns.some(pattern => {
166
+ try {
167
+ return new RegExp(pattern, 'i').test(value);
168
+ }
169
+ catch {
170
+ return false;
171
+ }
172
+ });
173
+ }
174
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { FrontendSecretExposureGate } from './frontend-secret-exposure.js';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import * as os from 'os';
6
+ describe('FrontendSecretExposureGate', () => {
7
+ let testDir;
8
+ beforeEach(() => {
9
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'frontend-secret-test-'));
10
+ });
11
+ afterEach(() => {
12
+ fs.rmSync(testDir, { recursive: true, force: true });
13
+ });
14
+ it('detects process.env secret usage in client-bundled file', async () => {
15
+ const filePath = path.join(testDir, 'src/components/Checkout.tsx');
16
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
17
+ fs.writeFileSync(filePath, `
18
+ export function Checkout() {
19
+ const key = process.env.STRIPE_SECRET_KEY;
20
+ return <div>{key}</div>;
21
+ }
22
+ `);
23
+ const gate = new FrontendSecretExposureGate();
24
+ const failures = await gate.run({ cwd: testDir });
25
+ expect(failures.length).toBeGreaterThan(0);
26
+ expect(failures[0].id).toBe('frontend-secret-exposure');
27
+ expect(failures[0].files).toContain('src/components/Checkout.tsx');
28
+ });
29
+ it('detects import.meta.env secret usage in frontend app path', async () => {
30
+ const filePath = path.join(testDir, 'src/app/page.tsx');
31
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
32
+ fs.writeFileSync(filePath, `
33
+ export default function Page() {
34
+ return <span>{import.meta.env.OPENAI_API_KEY}</span>;
35
+ }
36
+ `);
37
+ const gate = new FrontendSecretExposureGate();
38
+ const failures = await gate.run({ cwd: testDir });
39
+ expect(failures.length).toBeGreaterThan(0);
40
+ });
41
+ it('does not flag public env prefixes in client files', async () => {
42
+ const filePath = path.join(testDir, 'components/Header.tsx');
43
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
44
+ fs.writeFileSync(filePath, `
45
+ export const key = process.env.NEXT_PUBLIC_STRIPE_KEY;
46
+ `);
47
+ const gate = new FrontendSecretExposureGate();
48
+ const failures = await gate.run({ cwd: testDir });
49
+ expect(failures).toHaveLength(0);
50
+ });
51
+ it('does not flag server-only API route', async () => {
52
+ const filePath = path.join(testDir, 'pages/api/charge.ts');
53
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
54
+ fs.writeFileSync(filePath, `
55
+ export default function handler() {
56
+ return process.env.STRIPE_SECRET_KEY;
57
+ }
58
+ `);
59
+ const gate = new FrontendSecretExposureGate();
60
+ const failures = await gate.run({ cwd: testDir });
61
+ expect(failures).toHaveLength(0);
62
+ });
63
+ it('does not flag .server files', async () => {
64
+ const filePath = path.join(testDir, 'src/lib/payments.server.ts');
65
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
66
+ fs.writeFileSync(filePath, `
67
+ export const stripeSecret = process.env.STRIPE_SECRET_KEY;
68
+ `);
69
+ const gate = new FrontendSecretExposureGate();
70
+ const failures = await gate.run({ cwd: testDir });
71
+ expect(failures).toHaveLength(0);
72
+ });
73
+ it('respects explicit allowlist env names', async () => {
74
+ const filePath = path.join(testDir, 'src/views/App.tsx');
75
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
76
+ fs.writeFileSync(filePath, `
77
+ export const x = process.env.INTERNAL_TOKEN_FOR_DOCS;
78
+ `);
79
+ const gate = new FrontendSecretExposureGate({
80
+ allowlist_env_names: ['INTERNAL_TOKEN_FOR_DOCS'],
81
+ });
82
+ const failures = await gate.run({ cwd: testDir });
83
+ expect(failures).toHaveLength(0);
84
+ });
85
+ it('skips when disabled', async () => {
86
+ const filePath = path.join(testDir, 'src/components/Client.tsx');
87
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
88
+ fs.writeFileSync(filePath, `
89
+ export const x = process.env.OPENAI_API_KEY;
90
+ `);
91
+ const gate = new FrontendSecretExposureGate({ enabled: false });
92
+ const failures = await gate.run({ cwd: testDir });
93
+ expect(failures).toHaveLength(0);
94
+ });
95
+ });
@@ -15,6 +15,7 @@ import { RetryLoopBreakerGate } from './retry-loop-breaker.js';
15
15
  import { AgentTeamGate } from './agent-team.js';
16
16
  import { CheckpointGate } from './checkpoint.js';
17
17
  import { SecurityPatternsGate } from './security-patterns.js';
18
+ import { FrontendSecretExposureGate } from './frontend-secret-exposure.js';
18
19
  import { DuplicationDriftGate } from './duplication-drift.js';
19
20
  import { HallucinatedImportsGate } from './hallucinated-imports.js';
20
21
  import { InconsistentErrorHandlingGate } from './inconsistent-error-handling.js';
@@ -67,6 +68,9 @@ export class GateRunner {
67
68
  if (this.config.gates.security?.enabled !== false) {
68
69
  this.gates.push(new SecurityPatternsGate(this.config.gates.security));
69
70
  }
71
+ if (this.config.gates.frontend_secret_exposure?.enabled !== false) {
72
+ this.gates.push(new FrontendSecretExposureGate(this.config.gates.frontend_secret_exposure));
73
+ }
70
74
  // v2.16+ AI-Native Drift Detection Gates (enabled by default)
71
75
  if (this.config.gates.duplication_drift?.enabled !== false) {
72
76
  this.gates.push(new DuplicationDriftGate(this.config.gates.duplication_drift));
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './types/fix-packet.js';
7
7
  export { Gate, GateContext } from './gates/base.js';
8
8
  export { RetryLoopBreakerGate } from './gates/retry-loop-breaker.js';
9
9
  export { SideEffectAnalysisGate } from './gates/side-effect-analysis.js';
10
+ export { FrontendSecretExposureGate } from './gates/frontend-secret-exposure.js';
10
11
  export * from './utils/logger.js';
11
12
  export * from './services/score-history.js';
12
13
  export * from './hooks/index.js';
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ export * from './types/fix-packet.js';
7
7
  export { Gate } from './gates/base.js';
8
8
  export { RetryLoopBreakerGate } from './gates/retry-loop-breaker.js';
9
9
  export { SideEffectAnalysisGate } from './gates/side-effect-analysis.js';
10
+ export { FrontendSecretExposureGate } from './gates/frontend-secret-exposure.js';
10
11
  export * from './utils/logger.js';
11
12
  export * from './services/score-history.js';
12
13
  export * from './hooks/index.js';
@@ -72,6 +72,36 @@ export const UNIVERSAL_CONFIG = {
72
72
  command_injection: true,
73
73
  block_on_severity: 'high',
74
74
  },
75
+ frontend_secret_exposure: {
76
+ enabled: true,
77
+ block_on_severity: 'high',
78
+ check_process_env: true,
79
+ check_import_meta_env: true,
80
+ secret_env_name_patterns: [
81
+ '(?:^|_)(?:secret|private)(?:_|$)',
82
+ '(?:^|_)(?:token|api[_-]?key|access[_-]?key|client[_-]?secret|signing|webhook)(?:_|$)',
83
+ '(?:^|_)(?:db[_-]?url|database[_-]?url|connection[_-]?string)(?:_|$)',
84
+ ],
85
+ safe_public_prefixes: ['NEXT_PUBLIC_', 'VITE_', 'PUBLIC_', 'NUXT_PUBLIC_', 'REACT_APP_'],
86
+ frontend_path_patterns: [
87
+ '(^|/)pages/(?!api/)',
88
+ '(^|/)components/',
89
+ '(^|/)src/components/',
90
+ '(^|/)src/views/',
91
+ '(^|/)src/app/',
92
+ '(^|/)app/(?!api/)',
93
+ '(^|/)views/',
94
+ '(^|/)public/',
95
+ ],
96
+ server_path_patterns: [
97
+ '(^|/)pages/api/',
98
+ '(^|/)src/pages/api/',
99
+ '(^|/)app/api/',
100
+ '(^|/)src/app/api/',
101
+ '\\.server\\.(?:ts|tsx|js|jsx|mjs|cjs)$',
102
+ ],
103
+ allowlist_env_names: [],
104
+ },
75
105
  adaptive: {
76
106
  enabled: false,
77
107
  base_coverage_threshold: 80,
@@ -213,6 +213,37 @@ export declare const GatesSchema: z.ZodObject<{
213
213
  command_injection?: boolean | undefined;
214
214
  block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
215
215
  }>>>;
216
+ frontend_secret_exposure: z.ZodDefault<z.ZodOptional<z.ZodObject<{
217
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
218
+ block_on_severity: z.ZodDefault<z.ZodOptional<z.ZodEnum<["critical", "high", "medium", "low"]>>>;
219
+ check_process_env: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
220
+ check_import_meta_env: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
221
+ secret_env_name_patterns: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
222
+ safe_public_prefixes: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
223
+ frontend_path_patterns: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
224
+ server_path_patterns: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
225
+ allowlist_env_names: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
226
+ }, "strip", z.ZodTypeAny, {
227
+ enabled: boolean;
228
+ block_on_severity: "critical" | "high" | "medium" | "low";
229
+ check_process_env: boolean;
230
+ check_import_meta_env: boolean;
231
+ secret_env_name_patterns: string[];
232
+ safe_public_prefixes: string[];
233
+ frontend_path_patterns: string[];
234
+ server_path_patterns: string[];
235
+ allowlist_env_names: string[];
236
+ }, {
237
+ enabled?: boolean | undefined;
238
+ block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
239
+ check_process_env?: boolean | undefined;
240
+ check_import_meta_env?: boolean | undefined;
241
+ secret_env_name_patterns?: string[] | undefined;
242
+ safe_public_prefixes?: string[] | undefined;
243
+ frontend_path_patterns?: string[] | undefined;
244
+ server_path_patterns?: string[] | undefined;
245
+ allowlist_env_names?: string[] | undefined;
246
+ }>>>;
216
247
  adaptive: z.ZodDefault<z.ZodOptional<z.ZodObject<{
217
248
  enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
218
249
  base_coverage_threshold: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
@@ -674,6 +705,17 @@ export declare const GatesSchema: z.ZodObject<{
674
705
  command_injection: boolean;
675
706
  block_on_severity: "critical" | "high" | "medium" | "low";
676
707
  };
708
+ frontend_secret_exposure: {
709
+ enabled: boolean;
710
+ block_on_severity: "critical" | "high" | "medium" | "low";
711
+ check_process_env: boolean;
712
+ check_import_meta_env: boolean;
713
+ secret_env_name_patterns: string[];
714
+ safe_public_prefixes: string[];
715
+ frontend_path_patterns: string[];
716
+ server_path_patterns: string[];
717
+ allowlist_env_names: string[];
718
+ };
677
719
  adaptive: {
678
720
  enabled: boolean;
679
721
  base_coverage_threshold: number;
@@ -874,6 +916,17 @@ export declare const GatesSchema: z.ZodObject<{
874
916
  command_injection?: boolean | undefined;
875
917
  block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
876
918
  } | undefined;
919
+ frontend_secret_exposure?: {
920
+ enabled?: boolean | undefined;
921
+ block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
922
+ check_process_env?: boolean | undefined;
923
+ check_import_meta_env?: boolean | undefined;
924
+ secret_env_name_patterns?: string[] | undefined;
925
+ safe_public_prefixes?: string[] | undefined;
926
+ frontend_path_patterns?: string[] | undefined;
927
+ server_path_patterns?: string[] | undefined;
928
+ allowlist_env_names?: string[] | undefined;
929
+ } | undefined;
877
930
  adaptive?: {
878
931
  enabled?: boolean | undefined;
879
932
  base_coverage_threshold?: number | undefined;
@@ -1245,6 +1298,37 @@ export declare const ConfigSchema: z.ZodObject<{
1245
1298
  command_injection?: boolean | undefined;
1246
1299
  block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
1247
1300
  }>>>;
1301
+ frontend_secret_exposure: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1302
+ enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1303
+ block_on_severity: z.ZodDefault<z.ZodOptional<z.ZodEnum<["critical", "high", "medium", "low"]>>>;
1304
+ check_process_env: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1305
+ check_import_meta_env: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1306
+ secret_env_name_patterns: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
1307
+ safe_public_prefixes: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
1308
+ frontend_path_patterns: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
1309
+ server_path_patterns: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
1310
+ allowlist_env_names: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
1311
+ }, "strip", z.ZodTypeAny, {
1312
+ enabled: boolean;
1313
+ block_on_severity: "critical" | "high" | "medium" | "low";
1314
+ check_process_env: boolean;
1315
+ check_import_meta_env: boolean;
1316
+ secret_env_name_patterns: string[];
1317
+ safe_public_prefixes: string[];
1318
+ frontend_path_patterns: string[];
1319
+ server_path_patterns: string[];
1320
+ allowlist_env_names: string[];
1321
+ }, {
1322
+ enabled?: boolean | undefined;
1323
+ block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
1324
+ check_process_env?: boolean | undefined;
1325
+ check_import_meta_env?: boolean | undefined;
1326
+ secret_env_name_patterns?: string[] | undefined;
1327
+ safe_public_prefixes?: string[] | undefined;
1328
+ frontend_path_patterns?: string[] | undefined;
1329
+ server_path_patterns?: string[] | undefined;
1330
+ allowlist_env_names?: string[] | undefined;
1331
+ }>>>;
1248
1332
  adaptive: z.ZodDefault<z.ZodOptional<z.ZodObject<{
1249
1333
  enabled: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
1250
1334
  base_coverage_threshold: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
@@ -1706,6 +1790,17 @@ export declare const ConfigSchema: z.ZodObject<{
1706
1790
  command_injection: boolean;
1707
1791
  block_on_severity: "critical" | "high" | "medium" | "low";
1708
1792
  };
1793
+ frontend_secret_exposure: {
1794
+ enabled: boolean;
1795
+ block_on_severity: "critical" | "high" | "medium" | "low";
1796
+ check_process_env: boolean;
1797
+ check_import_meta_env: boolean;
1798
+ secret_env_name_patterns: string[];
1799
+ safe_public_prefixes: string[];
1800
+ frontend_path_patterns: string[];
1801
+ server_path_patterns: string[];
1802
+ allowlist_env_names: string[];
1803
+ };
1709
1804
  adaptive: {
1710
1805
  enabled: boolean;
1711
1806
  base_coverage_threshold: number;
@@ -1906,6 +2001,17 @@ export declare const ConfigSchema: z.ZodObject<{
1906
2001
  command_injection?: boolean | undefined;
1907
2002
  block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
1908
2003
  } | undefined;
2004
+ frontend_secret_exposure?: {
2005
+ enabled?: boolean | undefined;
2006
+ block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
2007
+ check_process_env?: boolean | undefined;
2008
+ check_import_meta_env?: boolean | undefined;
2009
+ secret_env_name_patterns?: string[] | undefined;
2010
+ safe_public_prefixes?: string[] | undefined;
2011
+ frontend_path_patterns?: string[] | undefined;
2012
+ server_path_patterns?: string[] | undefined;
2013
+ allowlist_env_names?: string[] | undefined;
2014
+ } | undefined;
1909
2015
  adaptive?: {
1910
2016
  enabled?: boolean | undefined;
1911
2017
  base_coverage_threshold?: number | undefined;
@@ -2148,6 +2254,17 @@ export declare const ConfigSchema: z.ZodObject<{
2148
2254
  command_injection: boolean;
2149
2255
  block_on_severity: "critical" | "high" | "medium" | "low";
2150
2256
  };
2257
+ frontend_secret_exposure: {
2258
+ enabled: boolean;
2259
+ block_on_severity: "critical" | "high" | "medium" | "low";
2260
+ check_process_env: boolean;
2261
+ check_import_meta_env: boolean;
2262
+ secret_env_name_patterns: string[];
2263
+ safe_public_prefixes: string[];
2264
+ frontend_path_patterns: string[];
2265
+ server_path_patterns: string[];
2266
+ allowlist_env_names: string[];
2267
+ };
2151
2268
  adaptive: {
2152
2269
  enabled: boolean;
2153
2270
  base_coverage_threshold: number;
@@ -2374,6 +2491,17 @@ export declare const ConfigSchema: z.ZodObject<{
2374
2491
  command_injection?: boolean | undefined;
2375
2492
  block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
2376
2493
  } | undefined;
2494
+ frontend_secret_exposure?: {
2495
+ enabled?: boolean | undefined;
2496
+ block_on_severity?: "critical" | "high" | "medium" | "low" | undefined;
2497
+ check_process_env?: boolean | undefined;
2498
+ check_import_meta_env?: boolean | undefined;
2499
+ secret_env_name_patterns?: string[] | undefined;
2500
+ safe_public_prefixes?: string[] | undefined;
2501
+ frontend_path_patterns?: string[] | undefined;
2502
+ server_path_patterns?: string[] | undefined;
2503
+ allowlist_env_names?: string[] | undefined;
2504
+ } | undefined;
2377
2505
  adaptive?: {
2378
2506
  enabled?: boolean | undefined;
2379
2507
  base_coverage_threshold?: number | undefined;
@@ -95,6 +95,42 @@ export const GatesSchema = z.object({
95
95
  command_injection: z.boolean().optional().default(true),
96
96
  block_on_severity: z.enum(['critical', 'high', 'medium', 'low']).optional().default('high'),
97
97
  }).optional().default({}),
98
+ frontend_secret_exposure: z.object({
99
+ enabled: z.boolean().optional().default(true),
100
+ block_on_severity: z.enum(['critical', 'high', 'medium', 'low']).optional().default('high'),
101
+ check_process_env: z.boolean().optional().default(true),
102
+ check_import_meta_env: z.boolean().optional().default(true),
103
+ secret_env_name_patterns: z.array(z.string()).optional().default([
104
+ '(?:^|_)(?:secret|private)(?:_|$)',
105
+ '(?:^|_)(?:token|api[_-]?key|access[_-]?key|client[_-]?secret|signing|webhook)(?:_|$)',
106
+ '(?:^|_)(?:db[_-]?url|database[_-]?url|connection[_-]?string)(?:_|$)',
107
+ ]),
108
+ safe_public_prefixes: z.array(z.string()).optional().default([
109
+ 'NEXT_PUBLIC_',
110
+ 'VITE_',
111
+ 'PUBLIC_',
112
+ 'NUXT_PUBLIC_',
113
+ 'REACT_APP_',
114
+ ]),
115
+ frontend_path_patterns: z.array(z.string()).optional().default([
116
+ '(^|/)pages/(?!api/)',
117
+ '(^|/)components/',
118
+ '(^|/)src/components/',
119
+ '(^|/)src/views/',
120
+ '(^|/)src/app/',
121
+ '(^|/)app/(?!api/)',
122
+ '(^|/)views/',
123
+ '(^|/)public/',
124
+ ]),
125
+ server_path_patterns: z.array(z.string()).optional().default([
126
+ '(^|/)pages/api/',
127
+ '(^|/)src/pages/api/',
128
+ '(^|/)app/api/',
129
+ '(^|/)src/app/api/',
130
+ '\\.server\\.(?:ts|tsx|js|jsx|mjs|cjs)$',
131
+ ]),
132
+ allowlist_env_names: z.array(z.string()).optional().default([]),
133
+ }).optional().default({}),
98
134
  adaptive: z.object({
99
135
  enabled: z.boolean().optional().default(false),
100
136
  base_coverage_threshold: z.number().optional().default(80),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/core",
3
- "version": "4.3.1",
3
+ "version": "4.3.2",
4
4
  "description": "Deterministic quality gate engine for AI-generated code. AST analysis, drift detection, and Fix Packet generation across TypeScript, JavaScript, Python, Go, Ruby, and C#.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rigour.run",
@@ -59,11 +59,11 @@
59
59
  "@xenova/transformers": "^2.17.2",
60
60
  "better-sqlite3": "^11.0.0",
61
61
  "openai": "^4.104.0",
62
- "@rigour-labs/brain-darwin-arm64": "4.3.1",
63
- "@rigour-labs/brain-darwin-x64": "4.3.1",
64
- "@rigour-labs/brain-linux-arm64": "4.3.1",
65
- "@rigour-labs/brain-win-x64": "4.3.1",
66
- "@rigour-labs/brain-linux-x64": "4.3.1"
62
+ "@rigour-labs/brain-darwin-arm64": "4.3.2",
63
+ "@rigour-labs/brain-linux-arm64": "4.3.2",
64
+ "@rigour-labs/brain-darwin-x64": "4.3.2",
65
+ "@rigour-labs/brain-win-x64": "4.3.2",
66
+ "@rigour-labs/brain-linux-x64": "4.3.2"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/better-sqlite3": "^7.6.12",