sec-gate 0.1.1 → 0.1.3
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/package.json +2 -1
- package/rules/custom-security.js +199 -0
- package/src/cli.js +16 -1
- package/src/commands/doctor.js +103 -0
- package/src/commands/scan.js +11 -4
- package/src/scanners/semgrep.js +17 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sec-gate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Pre-commit security gate for OWASP Top 10 2021 — SAST, SCA and misconfig checks for Node/Express, Go and React codebases",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Sundram Bhardwaj",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"bin/",
|
|
49
49
|
"src/",
|
|
50
50
|
"scripts/",
|
|
51
|
+
"rules/",
|
|
51
52
|
"vendor-bin/.gitkeep",
|
|
52
53
|
"README.md"
|
|
53
54
|
]
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sec-gate custom security rules
|
|
3
|
+
*
|
|
4
|
+
* These rules cover patterns NOT caught by @pensar/semgrep-node's owasp-top10 ruleset:
|
|
5
|
+
* 1. Hardcoded secrets (API keys, passwords, JWT secrets)
|
|
6
|
+
* 2. Insecure randomness (Math.random for tokens/sessions)
|
|
7
|
+
* 3. Prototype pollution
|
|
8
|
+
* 4. Sensitive data in localStorage
|
|
9
|
+
* 5. console.log with passwords/secrets
|
|
10
|
+
* 6. new Function() with dynamic input
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
// ─────────────────────────────────────────────────────────
|
|
17
|
+
// Rule definitions
|
|
18
|
+
// Each rule: { id, description, owasp, severity, test(line, lineNum, allLines) }
|
|
19
|
+
// Returns a finding object or null
|
|
20
|
+
// ─────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const RULES = [
|
|
23
|
+
|
|
24
|
+
// ── 1. Hardcoded secrets ──────────────────────────────
|
|
25
|
+
{
|
|
26
|
+
id: 'hardcoded-secret-assignment',
|
|
27
|
+
description: 'Hardcoded secret detected. Secrets should be loaded from environment variables, not hardcoded in source code.',
|
|
28
|
+
owasp: 'A02:2021 Cryptographic Failures',
|
|
29
|
+
severity: 'critical',
|
|
30
|
+
test(line) {
|
|
31
|
+
// Match: const/let/var API_KEY = "...", DB_PASSWORD = '...', etc.
|
|
32
|
+
return /(?:const|let|var)\s+(?:\w*(?:key|secret|password|passwd|pwd|token|api_key|jwt|auth|credential|private_key)\w*)\s*=\s*["'`][^"'`\s]{6,}/i.test(line);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
id: 'hardcoded-secret-object',
|
|
38
|
+
description: 'Hardcoded secret in object literal. Use environment variables instead.',
|
|
39
|
+
owasp: 'A02:2021 Cryptographic Failures',
|
|
40
|
+
severity: 'critical',
|
|
41
|
+
test(line) {
|
|
42
|
+
// Match: { password: "...", apiKey: "...", secret: "..." }
|
|
43
|
+
return /(?:password|passwd|pwd|secret|api_key|apikey|jwt_secret|private_key|auth_token)\s*:\s*["'`][^"'`\s]{6,}/i.test(line);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// ── 2. Insecure randomness ────────────────────────────
|
|
48
|
+
{
|
|
49
|
+
id: 'insecure-random-token',
|
|
50
|
+
// security-scan: disable rule-id: insecure-random-token reason: this is a rule description string, not actual Math.random() usage
|
|
51
|
+
description: 'Math.random() is not cryptographically secure. For tokens, session IDs or passwords use crypto.randomBytes() or crypto.getRandomValues() instead.',
|
|
52
|
+
owasp: 'A02:2021 Cryptographic Failures',
|
|
53
|
+
severity: 'high',
|
|
54
|
+
test(line) {
|
|
55
|
+
return /Math\.random\(\)/.test(line) &&
|
|
56
|
+
/(?:token|session|id|key|secret|password|nonce|salt|otp|code|csrf)/i.test(line);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
id: 'insecure-random-standalone',
|
|
62
|
+
// security-scan: disable rule-id: insecure-random-standalone reason: rule description string, not actual usage
|
|
63
|
+
description: 'Math.random() used in a security-sensitive context. Use crypto.randomBytes() for cryptographic purposes.',
|
|
64
|
+
owasp: 'A02:2021 Cryptographic Failures',
|
|
65
|
+
severity: 'medium',
|
|
66
|
+
test(line, lineNum, allLines) {
|
|
67
|
+
if (!(/Math\.random\(\)/.test(line))) return false;
|
|
68
|
+
// Check surrounding 3 lines for security context
|
|
69
|
+
const ctx = allLines.slice(Math.max(0, lineNum - 3), lineNum + 3).join(' ');
|
|
70
|
+
return /(?:token|session|secret|key|auth|crypto|password|nonce|salt)/i.test(ctx);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// ── 3. Prototype pollution ────────────────────────────
|
|
75
|
+
{
|
|
76
|
+
id: 'prototype-pollution',
|
|
77
|
+
description: 'Possible prototype pollution: assigning to a bracket-notation property using a variable key. Validate or whitelist keys before assignment.',
|
|
78
|
+
owasp: 'A03:2021 Injection',
|
|
79
|
+
severity: 'high',
|
|
80
|
+
test(line) {
|
|
81
|
+
// obj[userKey] = value or target[key] = val where key is variable
|
|
82
|
+
return /\w+\[\s*\w+\s*\]\s*=/.test(line) &&
|
|
83
|
+
!/\/\//.test(line.split('=')[0]); // not in a comment
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
{
|
|
88
|
+
id: 'proto-direct-access',
|
|
89
|
+
// security-scan: disable rule-id: proto-direct-access reason: description string contains __proto__ as text only, not as code
|
|
90
|
+
description: 'Direct __proto__ access detected. This can lead to prototype pollution.',
|
|
91
|
+
owasp: 'A03:2021 Injection',
|
|
92
|
+
severity: 'critical',
|
|
93
|
+
test(line) {
|
|
94
|
+
// security-scan: disable rule-id: proto-direct-access reason: __proto__ is inside a regex literal used as a detection pattern, not actual prototype access
|
|
95
|
+
return /__proto__/.test(line);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// ── 4. Sensitive data in localStorage ────────────────
|
|
100
|
+
{
|
|
101
|
+
id: 'localstorage-sensitive-data',
|
|
102
|
+
description: 'Sensitive data stored in localStorage. localStorage is accessible to any JS on the page (XSS). Use httpOnly cookies for tokens and passwords.',
|
|
103
|
+
owasp: 'A02:2021 Cryptographic Failures',
|
|
104
|
+
severity: 'high',
|
|
105
|
+
test(line) {
|
|
106
|
+
return /localStorage\.setItem\s*\(/.test(line) &&
|
|
107
|
+
/(?:password|passwd|pwd|token|secret|key|auth|jwt|session|credential)/i.test(line);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
{
|
|
112
|
+
id: 'sessionstorage-sensitive-data',
|
|
113
|
+
description: 'Sensitive data stored in sessionStorage. sessionStorage is accessible to XSS attacks. Use httpOnly cookies instead.',
|
|
114
|
+
owasp: 'A02:2021 Cryptographic Failures',
|
|
115
|
+
severity: 'high',
|
|
116
|
+
test(line) {
|
|
117
|
+
return /sessionStorage\.setItem\s*\(/.test(line) &&
|
|
118
|
+
/(?:password|passwd|pwd|token|secret|key|auth|jwt|credential)/i.test(line);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// ── 5. console.log with sensitive data ───────────────
|
|
123
|
+
{
|
|
124
|
+
id: 'console-log-sensitive',
|
|
125
|
+
description: 'Possible logging of sensitive data. Passwords, tokens and secrets should never be logged as they appear in log files and monitoring tools.',
|
|
126
|
+
owasp: 'A09:2021 Security Logging and Monitoring Failures',
|
|
127
|
+
severity: 'high',
|
|
128
|
+
test(line) {
|
|
129
|
+
return /console\.\s*(?:log|info|warn|error|debug)\s*\(/.test(line) &&
|
|
130
|
+
/(?:password|passwd|pwd|secret|token|api_?key|jwt|credential|private)/i.test(line);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// ── 6. new Function() with dynamic input ─────────────
|
|
135
|
+
{
|
|
136
|
+
id: 'new-function-injection',
|
|
137
|
+
// security-scan: disable rule-id: new-function-injection reason: this is a rule description string, not actual new Function() usage
|
|
138
|
+
description: 'new Function() with dynamic input is equivalent to eval(). An attacker can execute arbitrary JavaScript. Use a safe alternative.',
|
|
139
|
+
owasp: 'A03:2021 Injection',
|
|
140
|
+
severity: 'critical',
|
|
141
|
+
test(line) {
|
|
142
|
+
// new Function(variable) or new Function("..." + variable)
|
|
143
|
+
return /new\s+Function\s*\(/.test(line) &&
|
|
144
|
+
!/new\s+Function\s*\(\s*["'`][^"'`]*["'`]\s*\)/.test(line); // not pure string literal
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
// ─────────────────────────────────────────────────────────
|
|
151
|
+
// Scanner: run all rules against a file
|
|
152
|
+
// ─────────────────────────────────────────────────────────
|
|
153
|
+
function scanFileWithCustomRules(filePath) {
|
|
154
|
+
let content;
|
|
155
|
+
try {
|
|
156
|
+
// security-scan: disable rule-id: detect-non-literal-fs-filename reason: filePath comes from `git diff --cached --name-only`, not from user input
|
|
157
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
158
|
+
} catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const lines = content.split(/\r?\n/);
|
|
163
|
+
const findings = [];
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < lines.length; i++) {
|
|
166
|
+
const line = lines[i];
|
|
167
|
+
const lineNum = i + 1;
|
|
168
|
+
const trimmed = line.trim();
|
|
169
|
+
|
|
170
|
+
// Skip blank lines and pure comments
|
|
171
|
+
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('*')) continue;
|
|
172
|
+
|
|
173
|
+
// Skip lines with suppression tag
|
|
174
|
+
if (/security-scan:\s*disable/i.test(line)) continue;
|
|
175
|
+
|
|
176
|
+
// Also check the line immediately above for suppression
|
|
177
|
+
const prevLine = i > 0 ? lines[i - 1] : '';
|
|
178
|
+
if (/security-scan:\s*disable/i.test(prevLine)) continue;
|
|
179
|
+
|
|
180
|
+
for (const rule of RULES) {
|
|
181
|
+
if (rule.test(line, i, lines)) {
|
|
182
|
+
findings.push({
|
|
183
|
+
checkId: rule.id,
|
|
184
|
+
path: filePath,
|
|
185
|
+
line: lineNum,
|
|
186
|
+
message: rule.description,
|
|
187
|
+
severity: rule.severity,
|
|
188
|
+
owasp: rule.owasp,
|
|
189
|
+
raw: { line: trimmed }
|
|
190
|
+
});
|
|
191
|
+
break; // one finding per line per pass — avoid duplicates
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return findings;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = { scanFileWithCustomRules, RULES };
|
package/src/cli.js
CHANGED
|
@@ -2,7 +2,16 @@ const path = require('path');
|
|
|
2
2
|
|
|
3
3
|
function usage() {
|
|
4
4
|
// eslint-disable-next-line no-console
|
|
5
|
-
console.log(
|
|
5
|
+
console.log([
|
|
6
|
+
'sec-gate - OWASP Top 10 security gate',
|
|
7
|
+
'',
|
|
8
|
+
'Usage:',
|
|
9
|
+
' sec-gate install Install the pre-commit hook in this repo',
|
|
10
|
+
' sec-gate scan Scan all tracked files',
|
|
11
|
+
' sec-gate scan --staged Scan only staged files (used by pre-commit hook)',
|
|
12
|
+
' sec-gate doctor Check all components are installed and working',
|
|
13
|
+
''
|
|
14
|
+
].join('\n'));
|
|
6
15
|
}
|
|
7
16
|
|
|
8
17
|
function parseArgs(argv) {
|
|
@@ -38,6 +47,12 @@ async function run() {
|
|
|
38
47
|
return;
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
if (cmd === 'doctor') {
|
|
51
|
+
const { doctor } = require('./commands/doctor');
|
|
52
|
+
await doctor();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
41
56
|
usage();
|
|
42
57
|
process.exit(1);
|
|
43
58
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
function check(label, fn) {
|
|
6
|
+
try {
|
|
7
|
+
const result = fn();
|
|
8
|
+
console.log(` [OK] ${label}${result ? ': ' + result : ''}`);
|
|
9
|
+
return true;
|
|
10
|
+
} catch (err) {
|
|
11
|
+
console.log(` [FAIL] ${label}: ${err.message}`);
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function doctor() {
|
|
17
|
+
console.log('\nsec-gate doctor — checking all components\n');
|
|
18
|
+
|
|
19
|
+
// 1. sec-gate itself
|
|
20
|
+
check('sec-gate CLI', () => {
|
|
21
|
+
const pkg = require('../../package.json');
|
|
22
|
+
return `v${pkg.version}`;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// 2. @pensar/semgrep-node
|
|
26
|
+
console.log('\n--- SAST (Semgrep) ---');
|
|
27
|
+
const semgrepOk = check('@pensar/semgrep-node installed', () => {
|
|
28
|
+
const pkg = require('@pensar/semgrep-node');
|
|
29
|
+
return 'found';
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (semgrepOk) {
|
|
33
|
+
// Try actually loading the binary by doing a tiny scan on a temp file
|
|
34
|
+
await (async () => {
|
|
35
|
+
try {
|
|
36
|
+
const os = require('os');
|
|
37
|
+
const tmpFile = path.join(os.tmpdir(), 'sec-gate-test.js');
|
|
38
|
+
fs.writeFileSync(tmpFile, '// test\nconst x = 1;\n');
|
|
39
|
+
const scan = require('@pensar/semgrep-node').default;
|
|
40
|
+
await scan(tmpFile, { language: 'js', ruleSets: ['owasp-top10'] });
|
|
41
|
+
fs.unlinkSync(tmpFile);
|
|
42
|
+
console.log(' [OK] semgrep binary: working');
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.log(` [FAIL] semgrep binary: ${err.message}`);
|
|
45
|
+
console.log(' Fix: the semgrep binary may not be downloaded yet.');
|
|
46
|
+
console.log(' Try running `sec-gate scan` on a JS file once to trigger download.');
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. osv-scanner
|
|
52
|
+
console.log('\n--- SCA: Node/pnpm (OSV-Scanner) ---');
|
|
53
|
+
const ext = process.platform === 'win32' ? '.exe' : '';
|
|
54
|
+
const vendorOsv = path.join(__dirname, '..', '..', 'vendor-bin', `osv-scanner${ext}`);
|
|
55
|
+
|
|
56
|
+
check('osv-scanner binary (vendor-bin)', () => {
|
|
57
|
+
if (fs.existsSync(vendorOsv)) return vendorOsv;
|
|
58
|
+
throw new Error('not found in vendor-bin');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
check('osv-scanner executable', () => {
|
|
62
|
+
if (!fs.existsSync(vendorOsv)) throw new Error('binary missing');
|
|
63
|
+
const out = execFileSync(vendorOsv, ['--version'], { encoding: 'utf8' }).trim();
|
|
64
|
+
return out;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 4. govulncheck
|
|
68
|
+
console.log('\n--- SCA: Go (govulncheck) ---');
|
|
69
|
+
const vendorGo = path.join(__dirname, '..', '..', 'vendor-bin', `govulncheck${ext}`);
|
|
70
|
+
|
|
71
|
+
check('govulncheck binary (vendor-bin)', () => {
|
|
72
|
+
if (fs.existsSync(vendorGo)) return vendorGo;
|
|
73
|
+
throw new Error('not found — Go SCA will be skipped (Go may not be installed)');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 5. git hook
|
|
77
|
+
console.log('\n--- Pre-commit hook ---');
|
|
78
|
+
check('git available', () => {
|
|
79
|
+
execFileSync('git', ['--version'], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
80
|
+
return 'found';
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const repoRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
85
|
+
encoding: 'utf8',
|
|
86
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
87
|
+
}).trim();
|
|
88
|
+
|
|
89
|
+
const hookPath = path.join(repoRoot, '.git', 'hooks', 'pre-commit');
|
|
90
|
+
check('pre-commit hook installed', () => {
|
|
91
|
+
if (!fs.existsSync(hookPath)) throw new Error('not found — run `sec-gate install`');
|
|
92
|
+
const content = fs.readFileSync(hookPath, 'utf8');
|
|
93
|
+
if (!content.includes('installed-by: sec-gate')) throw new Error('hook exists but was not installed by sec-gate');
|
|
94
|
+
return hookPath;
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
console.log(' [SKIP] pre-commit hook: not inside a git repo');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log('\nsec-gate doctor done.\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { doctor };
|
package/src/commands/scan.js
CHANGED
|
@@ -4,10 +4,12 @@ const { runSemgrep } = require('../scanners/semgrep');
|
|
|
4
4
|
const { runOsvScanner } = require('../scanners/osv');
|
|
5
5
|
const { runGovulncheck } = require('../scanners/govulncheck');
|
|
6
6
|
const { applyInlineSuppressions } = require('../suppressions/inlineTag');
|
|
7
|
+
const { scanFileWithCustomRules } = require('../../rules/custom-security');
|
|
7
8
|
|
|
8
9
|
function formatFinding(f) {
|
|
9
10
|
const loc = f.line ? `${f.path}:${f.line}` : f.path;
|
|
10
|
-
|
|
11
|
+
const owasp = f.owasp ? ` (${f.owasp})` : '';
|
|
12
|
+
return `- ${loc} [${f.checkId}]${owasp}\n ${f.message}`;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
function isSemgrepTargetPath(p) {
|
|
@@ -34,16 +36,21 @@ async function scan({ staged }) {
|
|
|
34
36
|
console.log(`sec-gate: scan started (${staged ? 'staged files' : 'tracked files'})`);
|
|
35
37
|
|
|
36
38
|
const allFindings = [];
|
|
37
|
-
|
|
38
39
|
const semgrepTargets = (files || []).filter(isSemgrepTargetPath);
|
|
39
40
|
|
|
40
|
-
// SAST / misconfig
|
|
41
41
|
if (semgrepTargets.length > 0) {
|
|
42
|
+
// SAST — owasp-top10 via @pensar/semgrep-node
|
|
42
43
|
const sast = await runSemgrep({ files: semgrepTargets });
|
|
43
44
|
allFindings.push(...sast);
|
|
45
|
+
|
|
46
|
+
// Custom rules — patterns not covered by owasp-top10 ruleset
|
|
47
|
+
for (const filePath of semgrepTargets) {
|
|
48
|
+
const custom = scanFileWithCustomRules(filePath);
|
|
49
|
+
allFindings.push(...custom);
|
|
50
|
+
}
|
|
44
51
|
} else {
|
|
45
52
|
// eslint-disable-next-line no-console
|
|
46
|
-
console.log('sec-gate: no relevant staged/tracked source files
|
|
53
|
+
console.log('sec-gate: no relevant staged/tracked source files; skipping SAST');
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
// SCA (only when dependency lockfiles or go module files are staged)
|
package/src/scanners/semgrep.js
CHANGED
|
@@ -34,24 +34,37 @@ async function runSemgrep({ files }) {
|
|
|
34
34
|
|
|
35
35
|
for (const filePath of files) {
|
|
36
36
|
const lang = getLanguage(filePath);
|
|
37
|
+
// security-scan: disable rule-id: path-join-resolve-traversal reason: filePath comes from `git diff --cached --name-only` output, not from user input
|
|
37
38
|
const absPath = path.resolve(filePath);
|
|
38
39
|
|
|
39
40
|
try {
|
|
40
|
-
//
|
|
41
|
+
// eslint-disable-next-line no-console
|
|
42
|
+
console.log(`sec-gate: scanning ${filePath} (${lang}) with owasp-top10 rules...`);
|
|
43
|
+
|
|
41
44
|
const issues = await semgrepScan(absPath, {
|
|
42
45
|
language: lang,
|
|
43
46
|
ruleSets: ['owasp-top10']
|
|
44
47
|
});
|
|
45
48
|
|
|
49
|
+
if (issues.length > 0) {
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.log(`sec-gate: found ${issues.length} finding(s) in ${filePath}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
for (const issue of issues) {
|
|
47
55
|
allFindings.push(normalizeSemgrepNodeFinding(issue, filePath));
|
|
48
56
|
}
|
|
49
57
|
} catch (err) {
|
|
50
|
-
// If semgrep binary not yet downloaded for this platform, warn but don't crash.
|
|
51
58
|
if (err && err.message && err.message.includes('ENOENT')) {
|
|
52
|
-
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.warn(`sec-gate: WARNING — semgrep binary not found for language "${lang}"`);
|
|
61
|
+
// eslint-disable-next-line no-console
|
|
62
|
+
console.warn(' The semgrep binary needs to be downloaded by @pensar/semgrep-node.');
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.warn(' Run `sec-gate doctor` to diagnose, or re-install: npm i -g sec-gate');
|
|
53
65
|
} else {
|
|
54
|
-
|
|
66
|
+
// eslint-disable-next-line no-console
|
|
67
|
+
console.warn(`sec-gate: WARNING — semgrep scan failed for ${filePath}: ${err.message}`);
|
|
55
68
|
}
|
|
56
69
|
}
|
|
57
70
|
}
|