@rigour-labs/core 5.0.1 → 5.1.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 (141) hide show
  1. package/README.md +9 -1
  2. package/dist/gates/agent-team.d.ts +0 -1
  3. package/dist/gates/agent-team.js +0 -1
  4. package/dist/gates/checkpoint.d.ts +0 -2
  5. package/dist/gates/checkpoint.js +0 -2
  6. package/dist/gates/context-window-artifacts.d.ts +6 -2
  7. package/dist/gates/context-window-artifacts.js +107 -31
  8. package/dist/gates/deep-analysis.d.ts +2 -0
  9. package/dist/gates/deep-analysis.js +41 -11
  10. package/dist/gates/dependency.d.ts +0 -2
  11. package/dist/gates/dependency.js +23 -5
  12. package/dist/gates/deprecated-apis.d.ts +0 -2
  13. package/dist/gates/deprecated-apis.js +33 -20
  14. package/dist/gates/duplication-drift/index.d.ts +61 -0
  15. package/dist/gates/duplication-drift/index.js +240 -0
  16. package/dist/gates/duplication-drift/similarity.d.ts +68 -0
  17. package/dist/gates/duplication-drift/similarity.js +177 -0
  18. package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
  19. package/dist/gates/duplication-drift/tokenizer.js +195 -0
  20. package/dist/gates/frontend-secret-exposure.d.ts +0 -3
  21. package/dist/gates/frontend-secret-exposure.js +1 -114
  22. package/dist/gates/frontend-secret-patterns.d.ts +33 -0
  23. package/dist/gates/frontend-secret-patterns.js +119 -0
  24. package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
  25. package/dist/gates/hallucinated-imports/index.js +174 -0
  26. package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
  27. package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
  28. package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
  29. package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
  30. package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
  31. package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
  32. package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
  33. package/dist/gates/hallucinated-imports-lang.js +269 -34
  34. package/dist/gates/hallucinated-imports.test.js +1 -2
  35. package/dist/gates/inconsistent-error-handling.d.ts +0 -5
  36. package/dist/gates/inconsistent-error-handling.js +15 -144
  37. package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
  38. package/dist/gates/language-adapters/csharp-adapter.js +211 -0
  39. package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
  40. package/dist/gates/language-adapters/go-adapter.js +195 -0
  41. package/dist/gates/language-adapters/index.d.ts +15 -0
  42. package/dist/gates/language-adapters/index.js +16 -0
  43. package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
  44. package/dist/gates/language-adapters/java-adapter.js +237 -0
  45. package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
  46. package/dist/gates/language-adapters/js-adapter.js +279 -0
  47. package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
  48. package/dist/gates/language-adapters/python-adapter.js +183 -0
  49. package/dist/gates/language-adapters/registry.d.ts +26 -0
  50. package/dist/gates/language-adapters/registry.js +65 -0
  51. package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
  52. package/dist/gates/language-adapters/ruby-adapter.js +217 -0
  53. package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
  54. package/dist/gates/language-adapters/rust-adapter.js +235 -0
  55. package/dist/gates/language-adapters/types.d.ts +60 -0
  56. package/dist/gates/language-adapters/types.js +22 -0
  57. package/dist/gates/logic-drift-extractors.d.ts +15 -0
  58. package/dist/gates/logic-drift-extractors.js +34 -0
  59. package/dist/gates/logic-drift.d.ts +0 -30
  60. package/dist/gates/logic-drift.js +39 -129
  61. package/dist/gates/phantom-apis.d.ts +0 -2
  62. package/dist/gates/phantom-apis.js +49 -20
  63. package/dist/gates/promise-safety.d.ts +0 -1
  64. package/dist/gates/promise-safety.js +14 -2
  65. package/dist/gates/runner.js +52 -23
  66. package/dist/gates/runner.test.js +1 -1
  67. package/dist/gates/security-patterns-data.d.ts +14 -0
  68. package/dist/gates/security-patterns-data.js +235 -0
  69. package/dist/gates/security-patterns.d.ts +17 -3
  70. package/dist/gates/security-patterns.js +80 -211
  71. package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
  72. package/dist/gates/side-effect-analysis/categorizer.js +83 -0
  73. package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
  74. package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
  75. package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
  76. package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
  77. package/dist/gates/side-effect-helpers/index.d.ts +4 -0
  78. package/dist/gates/side-effect-helpers/index.js +4 -0
  79. package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
  80. package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
  81. package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
  82. package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
  83. package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
  84. package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
  85. package/dist/gates/side-effect-helpers/types.d.ts +38 -0
  86. package/dist/gates/side-effect-helpers/types.js +41 -0
  87. package/dist/gates/side-effect-rules.d.ts +0 -1
  88. package/dist/gates/side-effect-rules.js +0 -1
  89. package/dist/gates/style-drift-rules.d.ts +86 -0
  90. package/dist/gates/style-drift-rules.js +103 -0
  91. package/dist/gates/style-drift.d.ts +7 -16
  92. package/dist/gates/style-drift.js +101 -119
  93. package/dist/gates/test-quality-matchers.d.ts +53 -0
  94. package/dist/gates/test-quality-matchers.js +86 -0
  95. package/dist/gates/test-quality.d.ts +0 -3
  96. package/dist/gates/test-quality.js +47 -44
  97. package/dist/hooks/checker.d.ts +0 -1
  98. package/dist/hooks/checker.js +0 -2
  99. package/dist/hooks/dlp-templates.d.ts +0 -1
  100. package/dist/hooks/dlp-templates.js +0 -4
  101. package/dist/hooks/index.d.ts +0 -2
  102. package/dist/hooks/index.js +0 -2
  103. package/dist/hooks/input-validator.d.ts +0 -1
  104. package/dist/hooks/input-validator.js +0 -1
  105. package/dist/hooks/input-validator.test.js +0 -1
  106. package/dist/hooks/standalone-checker.d.ts +0 -1
  107. package/dist/hooks/standalone-checker.js +0 -1
  108. package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
  109. package/dist/hooks/standalone-dlp-checker.js +0 -1
  110. package/dist/hooks/templates.d.ts +0 -1
  111. package/dist/hooks/templates.js +0 -1
  112. package/dist/hooks/types.d.ts +0 -1
  113. package/dist/hooks/types.js +0 -1
  114. package/dist/index.d.ts +1 -1
  115. package/dist/index.js +1 -1
  116. package/dist/inference/index.js +1 -1
  117. package/dist/services/adaptive-thresholds.d.ts +0 -2
  118. package/dist/services/adaptive-thresholds.js +0 -2
  119. package/dist/services/filesystem-cache.d.ts +0 -1
  120. package/dist/services/filesystem-cache.js +0 -1
  121. package/dist/services/score-history.d.ts +0 -1
  122. package/dist/services/score-history.js +0 -1
  123. package/dist/services/temporal-drift.d.ts +1 -2
  124. package/dist/services/temporal-drift.js +7 -8
  125. package/dist/storage/db.d.ts +23 -7
  126. package/dist/storage/db.js +116 -55
  127. package/dist/storage/findings.d.ts +4 -3
  128. package/dist/storage/findings.js +13 -20
  129. package/dist/storage/local-memory.d.ts +4 -4
  130. package/dist/storage/local-memory.js +20 -22
  131. package/dist/storage/patterns.d.ts +5 -5
  132. package/dist/storage/patterns.js +20 -26
  133. package/dist/storage/scans.d.ts +6 -6
  134. package/dist/storage/scans.js +12 -21
  135. package/dist/types/index.d.ts +1 -0
  136. package/dist/utils/scanner.js +1 -1
  137. package/package.json +7 -8
  138. package/dist/gates/duplication-drift.d.ts +0 -128
  139. package/dist/gates/duplication-drift.js +0 -585
  140. package/dist/gates/hallucinated-imports.js +0 -641
  141. package/dist/gates/side-effect-helpers.d.ts +0 -260
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Duplication Drift Tokenizer
3
+ *
4
+ * Handles language-aware tokenization of code functions.
5
+ * Supports tree-sitter AST-based tokenization with fallback to text-based.
6
+ */
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { Logger } from '../../utils/logger.js';
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ // tree-sitter is optional — graceful fallback to text tokenization
12
+ let Parser = null;
13
+ let treeSitterReady = false;
14
+ let treeSitterFailed = false;
15
+ export async function initTreeSitter() {
16
+ if (treeSitterReady)
17
+ return true;
18
+ if (treeSitterFailed)
19
+ return false;
20
+ try {
21
+ const mod = await import('web-tree-sitter');
22
+ Parser = mod.default || mod;
23
+ await Parser.init();
24
+ treeSitterReady = true;
25
+ return true;
26
+ }
27
+ catch {
28
+ treeSitterFailed = true;
29
+ Logger.debug('tree-sitter not available, falling back to text tokenization');
30
+ return false;
31
+ }
32
+ }
33
+ export const GRAMMAR_PATHS = {
34
+ '.ts': '../../vendor/grammars/tree-sitter-typescript.wasm',
35
+ '.tsx': '../../vendor/grammars/tree-sitter-tsx.wasm',
36
+ '.js': '../../vendor/grammars/tree-sitter-javascript.wasm',
37
+ '.jsx': '../../vendor/grammars/tree-sitter-javascript.wasm',
38
+ '.py': '../../vendor/grammars/tree-sitter-python.wasm',
39
+ '.go': '../../vendor/grammars/tree-sitter-go.wasm',
40
+ '.rs': '../../vendor/grammars/tree-sitter-rust.wasm',
41
+ };
42
+ // Cache loaded languages
43
+ export const languageCache = new Map();
44
+ /**
45
+ * AST node types that are structural even as leaf nodes.
46
+ * These carry semantic meaning without children.
47
+ */
48
+ export function isStructuralLeaf(type) {
49
+ const structural = new Set([
50
+ 'return', 'break', 'continue', 'yield', 'throw',
51
+ 'true', 'false', 'null', 'undefined', 'none',
52
+ 'self', 'this', 'super',
53
+ 'string', 'number', 'template_string',
54
+ // Operators
55
+ '=', '==', '===', '!=', '!==', '<', '>', '<=', '>=',
56
+ '+', '-', '*', '/', '%', '**',
57
+ '&&', '||', '!', '??',
58
+ '=>', '...', '?', ':',
59
+ ]);
60
+ return structural.has(type);
61
+ }
62
+ /**
63
+ * Fallback tokenizer when tree-sitter is not available.
64
+ * Uses normalized text → keyword/operator multiset.
65
+ */
66
+ export function textTokenize(normalized) {
67
+ const tokens = new Map();
68
+ const structural = normalized.match(/\b(if|else|for|while|return|const|let|var|function|class|import|export|async|await|try|catch|throw|new|switch|case|break|continue|yield|def|self)\b|[{}()\[\];,.:=<>!&|+\-*/%?]+/g) || [];
69
+ for (const token of structural) {
70
+ tokens.set(token, (tokens.get(token) || 0) + 1);
71
+ }
72
+ // Normalize all identifiers to a count (variable names don't matter)
73
+ const keywords = new Set([
74
+ 'if', 'else', 'for', 'while', 'return', 'const', 'let', 'var',
75
+ 'function', 'class', 'import', 'export', 'async', 'await',
76
+ 'try', 'catch', 'throw', 'new', 'switch', 'case', 'break',
77
+ 'continue', 'yield', 'def', 'self', 'true', 'false', 'null', 'undefined',
78
+ ]);
79
+ const identifiers = normalized.match(/\b[a-zA-Z_]\w*\b/g) || [];
80
+ let idCount = 0;
81
+ for (const id of identifiers) {
82
+ if (!keywords.has(id))
83
+ idCount++;
84
+ }
85
+ if (idCount > 0)
86
+ tokens.set('_ID_', idCount);
87
+ return tokens;
88
+ }
89
+ /**
90
+ * Walk an AST subtree and collect node types as a multiset.
91
+ *
92
+ * This is the core insight: two functions with different variable names
93
+ * but the same control flow produce the same node type multiset.
94
+ *
95
+ * Example:
96
+ * `function a(x) { if (x > 0) return x * 2; return 0; }`
97
+ * `function b(val) { if (val > 0) return val * 2; return 0; }`
98
+ *
99
+ * Both produce: {if_statement: 1, binary_expression: 2, return_statement: 2, ...}
100
+ * → Jaccard similarity = 1.0
101
+ */
102
+ export function collectASTNodeTypes(node) {
103
+ const types = new Map();
104
+ const walk = (n) => {
105
+ // Skip leaf nodes that are just identifiers/literals (noise)
106
+ // Keep structural node types only
107
+ if (n.childCount > 0 || isStructuralLeaf(n.type)) {
108
+ types.set(n.type, (types.get(n.type) || 0) + 1);
109
+ }
110
+ for (let i = 0; i < n.childCount; i++) {
111
+ walk(n.child(i));
112
+ }
113
+ };
114
+ walk(node);
115
+ return types;
116
+ }
117
+ /**
118
+ * Walk the AST tree to find a function/method node at a given line.
119
+ */
120
+ export function findFunctionNodeAtLine(rootNode, targetLine) {
121
+ const functionTypes = new Set([
122
+ 'function_declaration', 'method_definition', 'arrow_function',
123
+ 'function_definition', // Python
124
+ 'function_item', // Rust
125
+ 'method_declaration', // Java/C#
126
+ 'lexical_declaration', // const x = () => {}
127
+ ]);
128
+ let bestMatch = null;
129
+ const walk = (node) => {
130
+ // tree-sitter lines are 0-indexed, our lines are 1-indexed
131
+ if (functionTypes.has(node.type) && node.startPosition.row + 1 === targetLine) {
132
+ bestMatch = node;
133
+ return;
134
+ }
135
+ for (let i = 0; i < node.childCount; i++) {
136
+ walk(node.child(i));
137
+ if (bestMatch)
138
+ return;
139
+ }
140
+ };
141
+ walk(rootNode);
142
+ return bestMatch;
143
+ }
144
+ /**
145
+ * Normalize function body text for tokenization.
146
+ * Removes comments, normalizes whitespace, etc.
147
+ */
148
+ export function normalizeBody(body) {
149
+ return body
150
+ .replace(/\/\/.*/g, '')
151
+ .replace(/\/\*[\s\S]*?\*\//g, '')
152
+ .replace(/#.*/g, '')
153
+ .replace(/`[^`]*`/g, '"STR"')
154
+ .replace(/\basync\s+/g, '')
155
+ .replace(/\s+/g, ' ')
156
+ .replace(/['"]/g, '"')
157
+ .trim();
158
+ }
159
+ /**
160
+ * Extract function body from lines starting at startIndex.
161
+ * Handles brace matching.
162
+ */
163
+ export function extractFunctionBody(lines, startIndex) {
164
+ let braceDepth = 0;
165
+ let started = false;
166
+ const body = [];
167
+ for (let i = startIndex; i < lines.length; i++) {
168
+ const line = lines[i];
169
+ for (const ch of line) {
170
+ if (ch === '{') {
171
+ braceDepth++;
172
+ started = true;
173
+ }
174
+ if (ch === '}')
175
+ braceDepth--;
176
+ }
177
+ if (started)
178
+ body.push(line);
179
+ if (started && braceDepth === 0)
180
+ break;
181
+ }
182
+ return body;
183
+ }
184
+ /**
185
+ * Get Parser instance (may be null if tree-sitter not available).
186
+ */
187
+ export function getParser() {
188
+ return Parser;
189
+ }
190
+ /**
191
+ * Get the __dirname path for grammar loading.
192
+ */
193
+ export function getGrammarDir() {
194
+ return __dirname;
195
+ }
@@ -13,9 +13,6 @@
13
13
  * where VARNAME matches secret_env_name_patterns and lacks a safe public prefix.
14
14
  * 2. Literal key detection: actual live API key values embedded in source
15
15
  * (sk_live_..., sk-proj-..., AKIA..., ghp_..., etc.)
16
- *
17
- * @since v4.4.0
18
- * @see CWE-312 Cleartext Storage of Sensitive Information
19
16
  */
20
17
  import { Gate, GateContext } from './base.js';
21
18
  import { Failure, Provenance } from '../types/index.js';
@@ -13,126 +13,13 @@
13
13
  * where VARNAME matches secret_env_name_patterns and lacks a safe public prefix.
14
14
  * 2. Literal key detection: actual live API key values embedded in source
15
15
  * (sk_live_..., sk-proj-..., AKIA..., ghp_..., etc.)
16
- *
17
- * @since v4.4.0
18
- * @see CWE-312 Cleartext Storage of Sensitive Information
19
16
  */
20
17
  import { Gate } from './base.js';
21
18
  import { FileScanner } from '../utils/scanner.js';
22
19
  import { Logger } from '../utils/logger.js';
20
+ import { LITERAL_KEY_PATTERNS, SEVERITY_ORDER, extractEnvVarName, extractDestructuredEnvVars, } from './frontend-secret-patterns.js';
23
21
  import fs from 'fs-extra';
24
22
  import path from 'path';
25
- /**
26
- * Literal API key patterns — actual values that are always dangerous in frontend code.
27
- * These bypass the env-name heuristic and fire regardless of variable naming.
28
- */
29
- const LITERAL_KEY_PATTERNS = [
30
- {
31
- label: 'Stripe Live Secret Key',
32
- regex: /\bsk_live_[a-zA-Z0-9]{24,}\b/g,
33
- severity: 'critical',
34
- hint: 'Live Stripe secret key (sk_live_...) exposed in client bundle. ' +
35
- 'Attackers can charge cards without a frontend. Move to server-side only.',
36
- cwe: 'CWE-312',
37
- },
38
- {
39
- label: 'Stripe Live Restricted Key',
40
- regex: /\brk_live_[a-zA-Z0-9]{24,}\b/g,
41
- severity: 'critical',
42
- hint: 'Live Stripe restricted key (rk_live_...) must never appear in frontend code.',
43
- cwe: 'CWE-312',
44
- },
45
- {
46
- label: 'OpenAI API Key',
47
- regex: /\bsk-proj-[a-zA-Z0-9_-]{40,}\b/g,
48
- severity: 'critical',
49
- hint: 'OpenAI project API key (sk-proj-...) exposed in client bundle. ' +
50
- 'Attackers can make unlimited API calls at your expense.',
51
- cwe: 'CWE-312',
52
- },
53
- {
54
- label: 'OpenAI Legacy API Key',
55
- regex: /\bsk-[a-zA-Z0-9]{48,}\b/g,
56
- severity: 'critical',
57
- hint: 'OpenAI API key (sk-...) exposed in client bundle. Move to server-side proxy.',
58
- cwe: 'CWE-312',
59
- },
60
- {
61
- label: 'AWS Access Key ID',
62
- regex: /\bAKIA[A-Z2-7]{16}\b/g,
63
- severity: 'critical',
64
- hint: 'AWS Access Key ID exposed in client bundle. This grants AWS API access.',
65
- cwe: 'CWE-312',
66
- },
67
- {
68
- label: 'GitHub Personal Access Token',
69
- regex: /\bghp_[a-zA-Z0-9]{36,}\b/g,
70
- severity: 'critical',
71
- hint: 'GitHub PAT (ghp_...) in frontend code. Grants repo access to anyone who views bundle.',
72
- cwe: 'CWE-312',
73
- },
74
- {
75
- label: 'GitHub Fine-Grained PAT',
76
- regex: /\bgithub_pat_[a-zA-Z0-9_]{55,}\b/g,
77
- severity: 'critical',
78
- hint: 'GitHub fine-grained PAT (github_pat_...) must never appear in frontend code.',
79
- cwe: 'CWE-312',
80
- },
81
- {
82
- label: 'Anthropic API Key',
83
- regex: /\bsk-ant-api\d{2}-[a-zA-Z0-9_-]{90,}\b/g,
84
- severity: 'critical',
85
- hint: 'Anthropic API key exposed in client bundle. Move to a server-side proxy.',
86
- cwe: 'CWE-312',
87
- },
88
- {
89
- label: 'SendGrid API Key',
90
- regex: /\bSG\.[a-zA-Z0-9_-]{22,}\.[a-zA-Z0-9_-]{43,}\b/g,
91
- severity: 'critical',
92
- hint: 'SendGrid API key exposed in client bundle. Attackers can send spam as you.',
93
- cwe: 'CWE-312',
94
- },
95
- ];
96
- /** Severity ordering for threshold comparison */
97
- const SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
98
- /**
99
- * Extracts the env var name from a single-reference line.
100
- * process.env.VARNAME → "VARNAME"
101
- * process.env["VARNAME"] → "VARNAME"
102
- * import.meta.env.VARNAME → "VARNAME"
103
- * Returns null if no match.
104
- */
105
- function extractEnvVarName(line, checkProcessEnv, checkImportMetaEnv) {
106
- if (checkProcessEnv) {
107
- const dot = line.match(/process\.env\.([A-Z][A-Z0-9_]*)/);
108
- if (dot)
109
- return dot[1];
110
- const bracket = line.match(/process\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/);
111
- if (bracket)
112
- return bracket[1];
113
- }
114
- if (checkImportMetaEnv) {
115
- const vite = line.match(/import\.meta\.env\.([A-Z_][A-Z0-9_]*)/);
116
- if (vite)
117
- return vite[1];
118
- }
119
- return null;
120
- }
121
- /**
122
- * Finds all var names from destructuring:
123
- * const { STRIPE_SECRET_KEY, OPENAI_KEY } = process.env
124
- */
125
- function extractDestructuredEnvVars(line) {
126
- if (!/=\s*process\.env\b/.test(line))
127
- return [];
128
- const brace = line.match(/\{\s*([^}]+)\s*\}/);
129
- if (!brace)
130
- return [];
131
- return brace[1]
132
- .split(',')
133
- .map(s => s.split(':')[0].trim()) // handle "SECRET_KEY: renamed"
134
- .filter(s => /^[A-Z_][A-Z0-9_]*$/.test(s));
135
- }
136
23
  export class FrontendSecretExposureGate extends Gate {
137
24
  cfg;
138
25
  secretNamePatterns;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Frontend Secret Exposure Gate — Environment Variable and Literal Key Patterns
3
+ *
4
+ * Contains environment variable pattern definitions, framework-specific env patterns
5
+ * (Next.js, Vite, SvelteKit, etc.), and literal API key detection patterns.
6
+ */
7
+ /**
8
+ * Literal API key patterns — actual values that are always dangerous in frontend code.
9
+ * These bypass the env-name heuristic and fire regardless of variable naming.
10
+ */
11
+ export declare const LITERAL_KEY_PATTERNS: {
12
+ label: string;
13
+ regex: RegExp;
14
+ severity: 'critical' | 'high';
15
+ hint: string;
16
+ cwe: string;
17
+ }[];
18
+ /** Severity ordering for threshold comparison */
19
+ export declare const SEVERITY_ORDER: {
20
+ readonly critical: 0;
21
+ readonly high: 1;
22
+ readonly medium: 2;
23
+ readonly low: 3;
24
+ };
25
+ /**
26
+ * Extracts the env var name from a single-reference line.
27
+ */
28
+ export declare function extractEnvVarName(line: string, checkProcessEnv: boolean, checkImportMetaEnv: boolean): string | null;
29
+ /**
30
+ * Finds all var names from destructuring:
31
+ * const { STRIPE_SECRET_KEY, OPENAI_KEY } = process.env
32
+ */
33
+ export declare function extractDestructuredEnvVars(line: string): string[];
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Frontend Secret Exposure Gate — Environment Variable and Literal Key Patterns
3
+ *
4
+ * Contains environment variable pattern definitions, framework-specific env patterns
5
+ * (Next.js, Vite, SvelteKit, etc.), and literal API key detection patterns.
6
+ */
7
+ /**
8
+ * Literal API key patterns — actual values that are always dangerous in frontend code.
9
+ * These bypass the env-name heuristic and fire regardless of variable naming.
10
+ */
11
+ export const LITERAL_KEY_PATTERNS = [
12
+ {
13
+ label: 'Stripe Live Secret Key',
14
+ regex: /\bsk_live_[a-zA-Z0-9]{24,}\b/g,
15
+ severity: 'critical',
16
+ hint: 'Live Stripe secret key (sk_live_...) exposed in client bundle. ' +
17
+ 'Attackers can charge cards without a frontend. Move to server-side only.',
18
+ cwe: 'CWE-312',
19
+ },
20
+ {
21
+ label: 'Stripe Live Restricted Key',
22
+ regex: /\brk_live_[a-zA-Z0-9]{24,}\b/g,
23
+ severity: 'critical',
24
+ hint: 'Live Stripe restricted key (rk_live_...) must never appear in frontend code.',
25
+ cwe: 'CWE-312',
26
+ },
27
+ {
28
+ label: 'OpenAI API Key',
29
+ regex: /\bsk-proj-[a-zA-Z0-9_-]{40,}\b/g,
30
+ severity: 'critical',
31
+ hint: 'OpenAI project API key (sk-proj-...) exposed in client bundle. ' +
32
+ 'Attackers can make unlimited API calls at your expense.',
33
+ cwe: 'CWE-312',
34
+ },
35
+ {
36
+ label: 'OpenAI Legacy API Key',
37
+ regex: /\bsk-[a-zA-Z0-9]{48,}\b/g,
38
+ severity: 'critical',
39
+ hint: 'OpenAI API key (sk-...) exposed in client bundle. Move to server-side proxy.',
40
+ cwe: 'CWE-312',
41
+ },
42
+ {
43
+ label: 'AWS Access Key ID',
44
+ regex: /\bAKIA[A-Z2-7]{16}\b/g,
45
+ severity: 'critical',
46
+ hint: 'AWS Access Key ID exposed in client bundle. This grants AWS API access.',
47
+ cwe: 'CWE-312',
48
+ },
49
+ {
50
+ label: 'GitHub Personal Access Token',
51
+ regex: /\bghp_[a-zA-Z0-9]{36,}\b/g,
52
+ severity: 'critical',
53
+ hint: 'GitHub PAT (ghp_...) in frontend code. Grants repo access to anyone who views bundle.',
54
+ cwe: 'CWE-312',
55
+ },
56
+ {
57
+ label: 'GitHub Fine-Grained PAT',
58
+ regex: /\bgithub_pat_[a-zA-Z0-9_]{55,}\b/g,
59
+ severity: 'critical',
60
+ hint: 'GitHub fine-grained PAT (github_pat_...) must never appear in frontend code.',
61
+ cwe: 'CWE-312',
62
+ },
63
+ {
64
+ label: 'Anthropic API Key',
65
+ regex: /\bsk-ant-api\d{2}-[a-zA-Z0-9_-]{90,}\b/g,
66
+ severity: 'critical',
67
+ hint: 'Anthropic API key exposed in client bundle. Move to a server-side proxy.',
68
+ cwe: 'CWE-312',
69
+ },
70
+ {
71
+ label: 'SendGrid API Key',
72
+ regex: /\bSG\.[a-zA-Z0-9_-]{22,}\.[a-zA-Z0-9_-]{43,}\b/g,
73
+ severity: 'critical',
74
+ hint: 'SendGrid API key exposed in client bundle. Attackers can send spam as you.',
75
+ cwe: 'CWE-312',
76
+ },
77
+ ];
78
+ /** Severity ordering for threshold comparison */
79
+ export const SEVERITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
80
+ /**
81
+ * Extracts the env var name from a single-reference line.
82
+ */
83
+ export function extractEnvVarName(line, checkProcessEnv, checkImportMetaEnv) {
84
+ if (checkProcessEnv) {
85
+ const dot = line.match(/process\.env\.([A-Z][A-Z0-9_]*)/);
86
+ if (dot)
87
+ return dot[1];
88
+ const bracket = line.match(/process\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/);
89
+ if (bracket)
90
+ return bracket[1];
91
+ }
92
+ if (checkImportMetaEnv) {
93
+ const vite = line.match(/import\.meta\.env\.([A-Z_][A-Z0-9_]*)/);
94
+ if (vite)
95
+ return vite[1];
96
+ }
97
+ const sveltePrivate = line.match(/import\s+\{([^}]+)\}\s+from\s+['"]?\$env\/(?:static|dynamic)\/private['"]?/);
98
+ if (sveltePrivate) {
99
+ const firstVar = sveltePrivate[1].split(',')[0].trim();
100
+ if (/^[A-Z_][A-Z0-9_]*$/.test(firstVar))
101
+ return firstVar;
102
+ }
103
+ return null;
104
+ }
105
+ /**
106
+ * Finds all var names from destructuring:
107
+ * const { STRIPE_SECRET_KEY, OPENAI_KEY } = process.env
108
+ */
109
+ export function extractDestructuredEnvVars(line) {
110
+ if (!/=\s*process\.env\b/.test(line))
111
+ return [];
112
+ const brace = line.match(/\{\s*([^}]+)\s*\}/);
113
+ if (!brace)
114
+ return [];
115
+ return brace[1]
116
+ .split(',')
117
+ .map(s => s.split(':')[0].trim())
118
+ .filter(s => /^[A-Z_][A-Z0-9_]*$/.test(s));
119
+ }
@@ -15,12 +15,9 @@
15
15
  * Rust — std/core/alloc crates, Cargo.toml deps, use/extern crate statements
16
16
  * Java — java/javax/jakarta stdlib, build.gradle + pom.xml deps, import statements
17
17
  * Kotlin — kotlin/kotlinx stdlib, Gradle deps, import statements
18
- *
19
- * @since v2.16.0
20
- * @since v3.0.1 — Go stdlib fix, Ruby/C# strengthened, Rust/Java/Kotlin added
21
18
  */
22
- import { Gate, GateContext } from './base.js';
23
- import { Failure, Provenance } from '../types/index.js';
19
+ import { Gate, GateContext } from '../base.js';
20
+ import { Failure, Provenance } from '../../types/index.js';
24
21
  export interface HallucinatedImport {
25
22
  file: string;
26
23
  line: number;
@@ -39,33 +36,9 @@ export declare class HallucinatedImportsGate extends Gate {
39
36
  constructor(config?: HallucinatedImportsConfig);
40
37
  protected get provenance(): Provenance;
41
38
  run(context: GateContext): Promise<Failure[]>;
42
- private checkJSImports;
43
- private collectJSImportSpecs;
44
- private resolveJSDepsForFile;
45
- private resolveTsPathAlias;
46
- private matchTsPathRule;
47
- private resolveTsPathTarget;
48
- private resolveTsPathConfigForFile;
49
- private loadTsPathConfig;
50
- private readLooseJson;
51
- private checkPyImports;
52
- /**
53
- * Find Python source roots (directories that are on sys.path) by looking at
54
- * pyproject.toml, setup.cfg, or common patterns like src/ layouts.
55
- */
56
- private findPythonSourceRoots;
57
- /**
58
- * Load installed Python package names from pyproject.toml dependencies,
59
- * requirements.txt, setup.cfg, or Pipfile.
60
- */
61
- private loadPythonInstalledPackages;
62
39
  private resolveRelativeImport;
63
40
  private extractPackageName;
64
41
  private shouldIgnore;
65
- /**
66
- * Build candidate source paths for an import.
67
- * Handles ESM-style TS source imports like "./foo.js" that map to "./foo.ts" pre-build.
68
- */
69
42
  private buildImportCandidates;
70
43
  private shouldSkipFile;
71
44
  }