sec-gate 0.1.1 → 0.1.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.
- package/package.json +1 -1
- package/src/cli.js +16 -1
- package/src/commands/doctor.js +103 -0
- package/src/scanners/semgrep.js +17 -4
package/package.json
CHANGED
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/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
|
}
|