@rigour-labs/core 5.0.1 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/gates/agent-team.d.ts +0 -1
- package/dist/gates/agent-team.js +0 -1
- package/dist/gates/checkpoint.d.ts +0 -2
- package/dist/gates/checkpoint.js +0 -2
- package/dist/gates/context-window-artifacts.d.ts +6 -2
- package/dist/gates/context-window-artifacts.js +107 -31
- package/dist/gates/deep-analysis.d.ts +2 -0
- package/dist/gates/deep-analysis.js +41 -11
- package/dist/gates/dependency.d.ts +0 -2
- package/dist/gates/dependency.js +23 -5
- package/dist/gates/deprecated-apis.d.ts +0 -2
- package/dist/gates/deprecated-apis.js +33 -20
- package/dist/gates/duplication-drift/index.d.ts +61 -0
- package/dist/gates/duplication-drift/index.js +240 -0
- package/dist/gates/duplication-drift/similarity.d.ts +68 -0
- package/dist/gates/duplication-drift/similarity.js +177 -0
- package/dist/gates/duplication-drift/tokenizer.d.ts +55 -0
- package/dist/gates/duplication-drift/tokenizer.js +195 -0
- package/dist/gates/frontend-secret-exposure.d.ts +0 -3
- package/dist/gates/frontend-secret-exposure.js +1 -114
- package/dist/gates/frontend-secret-patterns.d.ts +33 -0
- package/dist/gates/frontend-secret-patterns.js +119 -0
- package/dist/gates/{hallucinated-imports.d.ts → hallucinated-imports/index.d.ts} +2 -29
- package/dist/gates/hallucinated-imports/index.js +174 -0
- package/dist/gates/hallucinated-imports/js-resolver.d.ts +45 -0
- package/dist/gates/hallucinated-imports/js-resolver.js +320 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.d.ts +28 -0
- package/dist/gates/hallucinated-imports/manifest-discovery.js +114 -0
- package/dist/gates/hallucinated-imports/python-resolver.d.ts +24 -0
- package/dist/gates/hallucinated-imports/python-resolver.js +306 -0
- package/dist/gates/hallucinated-imports-lang.d.ts +2 -2
- package/dist/gates/hallucinated-imports-lang.js +269 -34
- package/dist/gates/hallucinated-imports.test.js +1 -2
- package/dist/gates/inconsistent-error-handling.d.ts +0 -5
- package/dist/gates/inconsistent-error-handling.js +15 -144
- package/dist/gates/language-adapters/csharp-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/csharp-adapter.js +211 -0
- package/dist/gates/language-adapters/go-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/go-adapter.js +195 -0
- package/dist/gates/language-adapters/index.d.ts +15 -0
- package/dist/gates/language-adapters/index.js +16 -0
- package/dist/gates/language-adapters/java-adapter.d.ts +16 -0
- package/dist/gates/language-adapters/java-adapter.js +237 -0
- package/dist/gates/language-adapters/js-adapter.d.ts +26 -0
- package/dist/gates/language-adapters/js-adapter.js +279 -0
- package/dist/gates/language-adapters/python-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/python-adapter.js +183 -0
- package/dist/gates/language-adapters/registry.d.ts +26 -0
- package/dist/gates/language-adapters/registry.js +65 -0
- package/dist/gates/language-adapters/ruby-adapter.d.ts +25 -0
- package/dist/gates/language-adapters/ruby-adapter.js +217 -0
- package/dist/gates/language-adapters/rust-adapter.d.ts +27 -0
- package/dist/gates/language-adapters/rust-adapter.js +235 -0
- package/dist/gates/language-adapters/types.d.ts +60 -0
- package/dist/gates/language-adapters/types.js +22 -0
- package/dist/gates/logic-drift-extractors.d.ts +15 -0
- package/dist/gates/logic-drift-extractors.js +34 -0
- package/dist/gates/logic-drift.d.ts +0 -30
- package/dist/gates/logic-drift.js +39 -129
- package/dist/gates/phantom-apis.d.ts +0 -2
- package/dist/gates/phantom-apis.js +49 -20
- package/dist/gates/promise-safety.d.ts +0 -1
- package/dist/gates/promise-safety.js +14 -2
- package/dist/gates/runner.js +51 -22
- package/dist/gates/security-patterns-data.d.ts +14 -0
- package/dist/gates/security-patterns-data.js +235 -0
- package/dist/gates/security-patterns.d.ts +17 -3
- package/dist/gates/security-patterns.js +80 -211
- package/dist/gates/side-effect-analysis/categorizer.d.ts +32 -0
- package/dist/gates/side-effect-analysis/categorizer.js +83 -0
- package/dist/gates/{side-effect-analysis.d.ts → side-effect-analysis/index.d.ts} +3 -5
- package/dist/gates/{side-effect-analysis.js → side-effect-analysis/index.js} +33 -45
- package/dist/gates/side-effect-analysis/scope-tracker.d.ts +37 -0
- package/dist/gates/side-effect-analysis/scope-tracker.js +40 -0
- package/dist/gates/side-effect-helpers/index.d.ts +4 -0
- package/dist/gates/side-effect-helpers/index.js +4 -0
- package/dist/gates/side-effect-helpers/pattern-detection.d.ts +123 -0
- package/dist/gates/{side-effect-helpers.js → side-effect-helpers/pattern-detection.js} +22 -468
- package/dist/gates/side-effect-helpers/resource-tracking.d.ts +80 -0
- package/dist/gates/side-effect-helpers/resource-tracking.js +281 -0
- package/dist/gates/side-effect-helpers/scope-analysis.d.ts +21 -0
- package/dist/gates/side-effect-helpers/scope-analysis.js +146 -0
- package/dist/gates/side-effect-helpers/types.d.ts +38 -0
- package/dist/gates/side-effect-helpers/types.js +41 -0
- package/dist/gates/side-effect-rules.d.ts +0 -1
- package/dist/gates/side-effect-rules.js +0 -1
- package/dist/gates/style-drift-rules.d.ts +86 -0
- package/dist/gates/style-drift-rules.js +103 -0
- package/dist/gates/style-drift.d.ts +7 -16
- package/dist/gates/style-drift.js +101 -119
- package/dist/gates/test-quality-matchers.d.ts +53 -0
- package/dist/gates/test-quality-matchers.js +86 -0
- package/dist/gates/test-quality.d.ts +0 -3
- package/dist/gates/test-quality.js +47 -44
- package/dist/hooks/checker.d.ts +0 -1
- package/dist/hooks/checker.js +0 -2
- package/dist/hooks/dlp-templates.d.ts +0 -1
- package/dist/hooks/dlp-templates.js +0 -4
- package/dist/hooks/index.d.ts +0 -2
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/input-validator.d.ts +0 -1
- package/dist/hooks/input-validator.js +0 -1
- package/dist/hooks/input-validator.test.js +0 -1
- package/dist/hooks/standalone-checker.d.ts +0 -1
- package/dist/hooks/standalone-checker.js +0 -1
- package/dist/hooks/standalone-dlp-checker.d.ts +0 -1
- package/dist/hooks/standalone-dlp-checker.js +0 -1
- package/dist/hooks/templates.d.ts +0 -1
- package/dist/hooks/templates.js +0 -1
- package/dist/hooks/types.d.ts +0 -1
- package/dist/hooks/types.js +0 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/services/adaptive-thresholds.d.ts +0 -2
- package/dist/services/adaptive-thresholds.js +0 -2
- package/dist/services/filesystem-cache.d.ts +0 -1
- package/dist/services/filesystem-cache.js +0 -1
- package/dist/services/score-history.d.ts +0 -1
- package/dist/services/score-history.js +0 -1
- package/dist/services/temporal-drift.d.ts +1 -2
- package/dist/services/temporal-drift.js +7 -8
- package/dist/storage/db.d.ts +23 -7
- package/dist/storage/db.js +116 -55
- package/dist/storage/findings.d.ts +4 -3
- package/dist/storage/findings.js +13 -20
- package/dist/storage/local-memory.d.ts +4 -4
- package/dist/storage/local-memory.js +20 -22
- package/dist/storage/patterns.d.ts +5 -5
- package/dist/storage/patterns.js +20 -26
- package/dist/storage/scans.d.ts +6 -6
- package/dist/storage/scans.js +12 -21
- package/dist/types/index.d.ts +1 -0
- package/dist/utils/scanner.js +1 -1
- package/package.json +7 -8
- package/dist/gates/duplication-drift.d.ts +0 -128
- package/dist/gates/duplication-drift.js +0 -585
- package/dist/gates/hallucinated-imports.js +0 -641
- 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 '
|
|
23
|
-
import { Failure, Provenance } from '
|
|
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
|
}
|