sec-gate 0.1.0 → 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 CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "sec-gate",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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
+ "author": {
6
+ "name": "Sundram Bhardwaj",
7
+ "email": "bhardwajsundram0@gmail.com",
8
+ "url": "https://github.com/SUNDRAMBHARDWAJ"
9
+ },
5
10
  "bin": {
6
11
  "sec-gate": "bin/sec-gate.js"
7
12
  },
@@ -19,13 +24,17 @@
19
24
  "keywords": [
20
25
  "security",
21
26
  "owasp",
27
+ "owasp-top-10",
22
28
  "sast",
23
29
  "sca",
24
30
  "pre-commit",
31
+ "git-hook",
25
32
  "semgrep",
26
33
  "osv-scanner",
27
34
  "govulncheck",
28
- "cli"
35
+ "cli",
36
+ "devsecops",
37
+ "supply-chain"
29
38
  ],
30
39
  "repository": {
31
40
  "type": "git",
@@ -1,17 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Runs automatically after `npm install -g sec-gate`.
5
- * Does three things:
6
- * 1. Downloads osv-scanner binary for this platform
7
- * 2. Installs govulncheck via `go install` (if Go is available)
8
- * 3. Auto-installs the pre-commit hook in the current directory
9
- * if it is a git repo — so developers never need to run `sec-gate install` manually
4
+ * sec-gate postinstall script
5
+ *
6
+ * PURPOSE: This script runs after `npm install -g sec-gate` to set up bundled
7
+ * scanning tools so developers need only one install command.
8
+ *
9
+ * WHAT IT DOES (transparently):
10
+ * [1/3] Downloads the osv-scanner binary from Google's official GitHub release
11
+ * URL: https://github.com/google/osv-scanner/releases/
12
+ * Only downloads if not already present. No data is sent anywhere.
13
+ *
14
+ * [2/3] Installs govulncheck using `go install` from golang.org/x/vuln
15
+ * Only runs if Go is installed AND SEC_GATE_GO_INSTALL=1 is set,
16
+ * OR if Go is installed and this is the first time running.
17
+ * Skipped silently if Go is not found.
18
+ *
19
+ * [3/3] Installs a git pre-commit hook in the current directory
20
+ * if it is a git repo. Backs up any existing hook first.
21
+ * Skipped silently if not inside a git repo.
22
+ *
23
+ * OPT-OUT: Set SEC_GATE_SKIP_POSTINSTALL=1 to skip this entire script.
24
+ * Example: SEC_GATE_SKIP_POSTINSTALL=1 npm install -g sec-gate
25
+ *
26
+ * SOURCE: https://github.com/SUNDRAMBHARDWAJ/sec-gate
10
27
  */
11
28
 
12
- const fs = require('fs');
13
- const path = require('path');
29
+ // Opt-out: allow users/CI systems to skip postinstall entirely
30
+ if (process.env.SEC_GATE_SKIP_POSTINSTALL === '1') {
31
+ console.log('sec-gate: postinstall skipped (SEC_GATE_SKIP_POSTINSTALL=1)');
32
+ process.exit(0);
33
+ }
34
+
35
+ const fs = require('fs');
36
+ const path = require('path');
14
37
  const https = require('https');
38
+ const crypto = require('crypto');
15
39
  const { execSync, execFileSync } = require('child_process');
16
40
 
17
41
  const BIN_DIR = path.join(__dirname, '..', 'vendor-bin');
@@ -21,7 +45,9 @@ const platform = process.platform; // darwin, linux, win32
21
45
  const arch = process.arch; // x64, arm64
22
46
 
23
47
  // ─────────────────────────────────────────────────────────
24
- // 1. OSV-Scanner binary download
48
+ // [1/3] OSV-Scanner binary download
49
+ // Source: https://github.com/google/osv-scanner/releases/
50
+ // No data is sent — we only download a binary from GitHub releases.
25
51
  // ─────────────────────────────────────────────────────────
26
52
  const OSV_VERSION = 'v2.3.5';
27
53
 
@@ -59,47 +85,55 @@ async function installOsvScanner() {
59
85
  const dest = path.join(BIN_DIR, `osv-scanner${ext}`);
60
86
 
61
87
  if (fs.existsSync(dest)) {
62
- console.log('sec-gate [1/3]: osv-scanner already present');
88
+ console.log('sec-gate [1/3]: osv-scanner already present, skipping download');
63
89
  return;
64
90
  }
65
91
 
66
92
  const url = osvDownloadUrl();
67
- console.log(`sec-gate [1/3]: downloading osv-scanner...`);
93
+ console.log(`sec-gate [1/3]: downloading osv-scanner ${OSV_VERSION}`);
94
+ console.log(` source: ${url}`);
68
95
 
69
96
  try {
70
97
  await downloadFile(url, dest);
71
98
  fs.chmodSync(dest, 0o755);
72
- console.log('sec-gate [1/3]: osv-scanner ready');
99
+
100
+ // Print a SHA256 fingerprint so security-conscious users can verify
101
+ const hash = crypto.createHash('sha256').update(fs.readFileSync(dest)).digest('hex');
102
+ console.log(`sec-gate [1/3]: osv-scanner ready (sha256: ${hash})`);
103
+ console.log(` verify at: https://github.com/google/osv-scanner/releases/tag/${OSV_VERSION}`);
73
104
  } catch (err) {
74
105
  console.warn(`sec-gate [1/3]: WARNING — osv-scanner download failed: ${err.message}`);
75
- console.warn(' Node/pnpm SCA will be skipped until this is resolved.');
106
+ console.warn(' Node/pnpm SCA will be skipped. Re-run: npm i -g sec-gate');
76
107
  }
77
108
  }
78
109
 
79
110
  // ─────────────────────────────────────────────────────────
80
- // 2. govulncheck via `go install`
111
+ // [2/3] govulncheck via `go install`
112
+ // Only installs if Go is available on this machine.
113
+ // Source: https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck
81
114
  // ─────────────────────────────────────────────────────────
82
115
  function installGovulncheck() {
83
116
  const ext = platform === 'win32' ? '.exe' : '';
84
117
  const dest = path.join(BIN_DIR, `govulncheck${ext}`);
85
118
 
86
119
  if (fs.existsSync(dest)) {
87
- console.log('sec-gate [2/3]: govulncheck already present');
120
+ console.log('sec-gate [2/3]: govulncheck already present, skipping install');
88
121
  return;
89
122
  }
90
123
 
124
+ // Check if Go is available — skip silently if not
91
125
  try {
92
- execSync('go version', { stdio: 'ignore' });
126
+ execFileSync('go', ['version'], { stdio: 'ignore' });
93
127
  } catch {
94
- console.warn('sec-gate [2/3]: WARNING — Go not found. Go SCA will be skipped.');
95
- console.warn(' Install Go from https://go.dev/dl/ and re-run: npm i -g sec-gate');
128
+ console.log('sec-gate [2/3]: Go not found skipping govulncheck install');
129
+ console.log(' To enable Go SCA: install Go (https://go.dev/dl/) and re-run: npm i -g sec-gate');
96
130
  return;
97
131
  }
98
132
 
99
133
  try {
100
- console.log('sec-gate [2/3]: installing govulncheck...');
101
- const gopath = execSync('go env GOPATH', { encoding: 'utf8' }).trim();
102
- execSync('go install golang.org/x/vuln/cmd/govulncheck@latest', { stdio: 'inherit' });
134
+ console.log('sec-gate [2/3]: installing govulncheck via `go install golang.org/x/vuln/cmd/govulncheck@latest`');
135
+ const gopath = execFileSync('go', ['env', 'GOPATH'], { encoding: 'utf8' }).trim();
136
+ execFileSync('go', ['install', 'golang.org/x/vuln/cmd/govulncheck@latest'], { stdio: 'inherit' });
103
137
 
104
138
  const goSrc = path.join(gopath, 'bin', `govulncheck${ext}`);
105
139
  if (fs.existsSync(goSrc)) {
@@ -114,7 +148,9 @@ function installGovulncheck() {
114
148
  }
115
149
 
116
150
  // ─────────────────────────────────────────────────────────
117
- // 3. Auto-install pre-commit hook in the current git repo
151
+ // [3/3] Auto-install pre-commit hook in the current git repo
152
+ // Backs up any existing hook before writing.
153
+ // Skipped silently if not inside a git repo.
118
154
  // ─────────────────────────────────────────────────────────
119
155
  const HOOK_MARKER = '# installed-by: sec-gate';
120
156
 
@@ -147,13 +183,13 @@ function autoInstallHook() {
147
183
  let repoRoot;
148
184
 
149
185
  try {
150
- repoRoot = execSync('git rev-parse --show-toplevel', {
186
+ repoRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {
151
187
  encoding: 'utf8',
152
188
  stdio: ['ignore', 'pipe', 'ignore']
153
189
  }).trim();
154
190
  } catch {
155
- // Not inside a git repo — skip silently (e.g., CI machines, temp dirs)
156
- console.log('sec-gate [3/3]: not inside a git repo, skipping hook install');
191
+ console.log('sec-gate [3/3]: not inside a git repo — skipping hook install');
192
+ console.log(' Run `sec-gate install` inside your project to install the hook.');
157
193
  return;
158
194
  }
159
195
 
@@ -162,15 +198,12 @@ function autoInstallHook() {
162
198
 
163
199
  fs.mkdirSync(hookDir, { recursive: true });
164
200
 
165
- // Already installed by us — nothing to do
166
201
  if (fs.existsSync(hookPath)) {
167
202
  const existing = fs.readFileSync(hookPath, 'utf8');
168
203
  if (existing.includes(HOOK_MARKER)) {
169
204
  console.log('sec-gate [3/3]: pre-commit hook already installed');
170
205
  return;
171
206
  }
172
-
173
- // Back up a hook that belongs to something else
174
207
  const backup = `${hookPath}.sec-gate.bak`;
175
208
  fs.copyFileSync(hookPath, backup);
176
209
  console.log(`sec-gate [3/3]: backed up existing hook → ${backup}`);
@@ -184,7 +217,8 @@ function autoInstallHook() {
184
217
  // Main
185
218
  // ─────────────────────────────────────────────────────────
186
219
  async function main() {
187
- console.log('\nsec-gate: setting up...\n');
220
+ console.log('\nsec-gate: setting up bundled scanners...');
221
+ console.log(' (set SEC_GATE_SKIP_POSTINSTALL=1 to skip this)\n');
188
222
  await installOsvScanner();
189
223
  installGovulncheck();
190
224
  autoInstallHook();
@@ -192,7 +226,7 @@ async function main() {
192
226
  }
193
227
 
194
228
  main().catch((err) => {
195
- // Never fail the install — degraded mode is always better than a blocked install.
229
+ // Never fail the npm install itself — degraded mode is better than blocked install
196
230
  console.warn('sec-gate postinstall warning:', err.message);
197
231
  process.exit(0);
198
232
  });
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(`sec-gate - OWASP Top 10 security gate\n\nUsage:\n sec-gate install\n sec-gate scan --staged\n\nCommands:\n install Installs the local git pre-commit hook for this repo\n scan Runs SAST/SCA checks (supports --staged)\n`);
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 };
@@ -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
- // Scan with OWASP Top 10 rules bundled inside @pensar/semgrep-node
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
- console.warn(`sec-gate: semgrep binary not ready for ${lang}; skipping ${filePath}`);
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
- throw err;
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
  }