sec-gate 0.2.0 → 0.3.4
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 +188 -127
- package/package.json +3 -2
- package/scripts/postinstall.js +5 -4
- package/scripts/preuninstall.js +164 -0
- package/src/commands/install.js +5 -4
package/README.md
CHANGED
|
@@ -1,228 +1,289 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="https://readme-typing-svg.demolab.com?font=Fira+Code&weight=700&size=28&pause=1000&color=FF6B6B¢er=true&vCenter=true&width=600&lines=sec-gate+%F0%9F%94%90;OWASP+Top+10+Security+Gate;Block+Vulnerabilities+Before+Commit" alt="sec-gate" />
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- **SAST** — static analysis of JS/TS/Go/React code via Semgrep (OWASP Top 10 rules + Express misconfig rules)
|
|
7
|
-
- **SCA** — dependency vulnerability scanning via OSV-Scanner (pnpm) and govulncheck (Go)
|
|
8
|
-
- **Misconfig** — CORS, headers, auth bypass patterns
|
|
5
|
+
<br/>
|
|
9
6
|
|
|
10
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/sec-gate)
|
|
8
|
+
[](https://www.npmjs.com/package/sec-gate)
|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://nodejs.org)
|
|
11
|
+
[](https://owasp.org/Top10/)
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
<br/>
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
> **A pre-commit security gate that automatically blocks vulnerable code before every `git commit`.**
|
|
16
|
+
> Covers SAST · SCA · Misconfigurations · SQL Injection · Hardcoded Secrets and more.
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
<br/>
|
|
17
19
|
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
+
```
|
|
21
|
+
git commit → sec-gate scans → vulnerability? → BLOCKED ✗
|
|
22
|
+
→ clean? → committed ✓
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
> You only run this once per machine, not once per project.
|
|
25
|
+
</div>
|
|
25
26
|
|
|
26
27
|
---
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
## ⚡ Quick Start
|
|
29
30
|
|
|
30
31
|
```bash
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```
|
|
32
|
+
# Step 1 — Install globally (once per machine)
|
|
33
|
+
npm install -g sec-gate
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
# Step 2 — Hook into your repo (once per clone)
|
|
36
|
+
cd your-project
|
|
37
|
+
sec-gate install
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
# Step 3 — Commit as normal — scans run automatically
|
|
40
|
+
git commit -m "your changes"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> **That's it.** No config needed. No extra tools to install. Everything is bundled.
|
|
38
44
|
|
|
39
45
|
---
|
|
40
46
|
|
|
41
|
-
|
|
47
|
+
## 🛡️ What gets scanned
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
git add src/services/payment.js src/routes/user.js
|
|
45
|
-
git commit -m "feat: add payment service" # scan fires automatically here
|
|
46
|
-
```
|
|
49
|
+
<div align="center">
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
| Layer | Tool | What it catches |
|
|
52
|
+
|:---:|:---:|:---|
|
|
53
|
+
|  | Semgrep + AST rules | SQL injection, XSS, command injection, hardcoded secrets |
|
|
54
|
+
|  | OSV-Scanner | Known CVEs in npm/pnpm/yarn dependencies |
|
|
55
|
+
|  | govulncheck | Known CVEs in Go modules |
|
|
56
|
+
|  | acorn AST walker | Prototype pollution, insecure random, eval injection |
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
</div>
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
---
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
# On a fresh machine or a fresh clone:
|
|
56
|
-
npm install -g sec-gate # Step 1 — install tool globally (once per machine)
|
|
57
|
-
cd fmt-os # go into your project
|
|
58
|
-
sec-gate install # Step 2 — hook up this repo (once per clone)
|
|
62
|
+
## 🔴 What blocked output looks like
|
|
59
63
|
|
|
60
|
-
# Now develop as normal:
|
|
61
|
-
git add .
|
|
62
|
-
git commit -m "my changes" # Step 3 — scan runs automatically here
|
|
63
64
|
```
|
|
65
|
+
sec-gate: scan started (staged files)
|
|
66
|
+
sec-gate: excluding 3 high-noise rule(s)
|
|
67
|
+
sec-gate: scanning src/services/payment.js (js) with owasp-top10 rules...
|
|
64
68
|
|
|
65
|
-
|
|
69
|
+
sec-gate: SECURITY FINDINGS (commit blocked):
|
|
70
|
+
|
|
71
|
+
- src/services/payment.js:40 [CRITICAL] [sql-injection-template-literal] (A03:2021 Injection)
|
|
72
|
+
SQL query built with template literal interpolation.
|
|
73
|
+
Use parameterized queries: sequelize.query(sql, { replacements: [...] })
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
- src/services/payment.js:82 [LOW] [insecure-object-assign] (A01:2021)
|
|
76
|
+
Object.assign with potentially user-controlled data.
|
|
68
77
|
|
|
78
|
+
- package-lock.json [OSV:GHSA-r5fr-rjxr-66jc]
|
|
79
|
+
lodash: vulnerable to Code Injection via _.template
|
|
69
80
|
```
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
sec-gate scan
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
│ against OWASP Top 10 + Express rules │
|
|
79
|
-
├─────────────────────────────────────────────────────┤
|
|
80
|
-
│ SCA — OSV-Scanner checks pnpm-lock.yaml │
|
|
81
|
-
│ govulncheck checks go.mod │
|
|
82
|
-
│ (only when those files are staged) │
|
|
83
|
-
└─────────────────────────────────────────────────────┘
|
|
84
|
-
↓
|
|
85
|
-
Inline suppression tags filtered out
|
|
86
|
-
↓
|
|
87
|
-
Any findings? → commit BLOCKED, findings printed
|
|
88
|
-
No findings? → commit proceeds
|
|
81
|
+
|
|
82
|
+
## 🟢 What a clean commit looks like
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
sec-gate: scan started (staged files)
|
|
86
|
+
sec-gate: excluding 3 high-noise rule(s)
|
|
87
|
+
sec-gate: all checks passed — no vulnerabilities found by sec-gate
|
|
88
|
+
sec-gate: checks ran: SAST (3 files), SCA-node (package-lock.json)
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
93
|
-
##
|
|
93
|
+
## 🗂️ OWASP Top 10 (2021) Coverage
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
sec-gate --help
|
|
95
|
+
<div align="center">
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
| # | Category | Status |
|
|
98
|
+
|:---:|:---|:---:|
|
|
99
|
+
| A01 | Broken Access Control |  |
|
|
100
|
+
| A02 | Cryptographic Failures |  |
|
|
101
|
+
| A03 | Injection (SQL · XSS · CMD) |  |
|
|
102
|
+
| A04 | Insecure Design |  |
|
|
103
|
+
| A05 | Security Misconfiguration |  |
|
|
104
|
+
| A06 | Vulnerable Components |  |
|
|
105
|
+
| A07 | Authentication Failures |  |
|
|
106
|
+
| A08 | Software Integrity Failures |  |
|
|
107
|
+
| A09 | Security Logging Failures |  |
|
|
108
|
+
| A10 | Server-Side Request Forgery |  |
|
|
109
|
+
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 🔧 All Commands
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
sec-gate install # Install/inject pre-commit hook (auto-detects husky, lefthook etc.)
|
|
118
|
+
sec-gate scan # Scan all tracked files
|
|
119
|
+
sec-gate scan --staged # Scan only staged files
|
|
120
|
+
sec-gate doctor # Diagnose installation issues
|
|
121
|
+
sec-gate --version # Print installed version
|
|
122
|
+
sec-gate --help # Show help
|
|
102
123
|
```
|
|
103
124
|
|
|
104
125
|
---
|
|
105
126
|
|
|
106
|
-
##
|
|
127
|
+
## 🔕 Suppressing False Positives
|
|
107
128
|
|
|
108
|
-
|
|
129
|
+
Two formats supported — use whichever you prefer:
|
|
109
130
|
|
|
131
|
+
**Short format** _(quick)_
|
|
110
132
|
```js
|
|
111
|
-
//
|
|
112
|
-
|
|
133
|
+
// sec-gate-disable: sql-injection-template-literal
|
|
134
|
+
const rawQuery = `SELECT * FROM payments WHERE status = '${status}'`;
|
|
113
135
|
```
|
|
114
136
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
137
|
+
**Long format** _(recommended for PRs — shows reason)_
|
|
138
|
+
```js
|
|
139
|
+
// security-scan: disable rule-id: sql-injection-template-literal reason: status validated against enum
|
|
140
|
+
const rawQuery = `SELECT * FROM payments WHERE status = '${status}'`;
|
|
118
141
|
```
|
|
119
142
|
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
**Suppress all rules on a line**
|
|
122
144
|
```js
|
|
123
|
-
//
|
|
124
|
-
|
|
145
|
+
// sec-gate-disable: *
|
|
146
|
+
dangerousLegacyFunction();
|
|
125
147
|
```
|
|
126
148
|
|
|
127
149
|
---
|
|
128
150
|
|
|
129
|
-
## Configuration (
|
|
151
|
+
## ⚙️ Configuration (`.sec-gate.yml`)
|
|
130
152
|
|
|
131
|
-
Create
|
|
153
|
+
Create this file in your project root to tune the scanner:
|
|
132
154
|
|
|
133
155
|
```yaml
|
|
134
156
|
# .sec-gate.yml
|
|
135
157
|
|
|
136
|
-
#
|
|
158
|
+
# Block only on high/critical findings
|
|
137
159
|
severity_threshold: high
|
|
138
160
|
|
|
139
|
-
# Exclude specific
|
|
161
|
+
# Exclude specific rules globally
|
|
140
162
|
exclude_rules:
|
|
141
163
|
- path-join-resolve-traversal
|
|
142
164
|
- detect-non-literal-regexp
|
|
143
|
-
- detect-non-literal-fs-filename
|
|
144
165
|
|
|
145
|
-
# Skip test
|
|
166
|
+
# Skip test and mock files
|
|
146
167
|
exclude_paths:
|
|
147
168
|
- "**/__tests__/**"
|
|
148
169
|
- "**/*.test.js"
|
|
149
|
-
- "**/*.spec.ts"
|
|
150
170
|
- "**/mocks/**"
|
|
151
171
|
|
|
152
|
-
#
|
|
172
|
+
# Toggle scanners
|
|
153
173
|
sca: true
|
|
154
|
-
|
|
155
|
-
# Disable custom rules
|
|
156
174
|
custom_rules: true
|
|
157
175
|
```
|
|
158
176
|
|
|
159
|
-
|
|
177
|
+
<details>
|
|
178
|
+
<summary>📋 All severity threshold options</summary>
|
|
179
|
+
|
|
180
|
+
| Value | Blocks on |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `all` (default) | Every finding |
|
|
183
|
+
| `high` | High + Critical only |
|
|
184
|
+
| `critical` | Critical only |
|
|
185
|
+
| `medium` | Medium + High + Critical |
|
|
186
|
+
| `low` | Everything (same as all) |
|
|
187
|
+
|
|
188
|
+
</details>
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 🪝 Hook Manager Support
|
|
193
|
+
|
|
194
|
+
`sec-gate install` automatically detects your hook manager — no manual config needed:
|
|
195
|
+
|
|
196
|
+
<div align="center">
|
|
197
|
+
|
|
198
|
+
| Tool | Detection | Auto-injected |
|
|
199
|
+
|:---:|:---:|:---:|
|
|
200
|
+
|  | `.husky/` directory | ✅ `.husky/pre-commit` |
|
|
201
|
+
|  | `package.json` hooks | ✅ prepended to command |
|
|
202
|
+
|  | `lefthook.yml` | ✅ priority 1 command |
|
|
203
|
+
|  | `package.json` | ✅ prepended to command |
|
|
204
|
+
| -43B883?style=flat-square) | `.pre-commit-config.yaml` | ✅ local hook entry |
|
|
205
|
+
|  | no manager | ✅ `.git/hooks/pre-commit` |
|
|
206
|
+
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 🔒 Supported Package Managers
|
|
212
|
+
|
|
213
|
+
<div align="center">
|
|
214
|
+
|
|
215
|
+
[](https://www.npmjs.com)
|
|
216
|
+
[](https://pnpm.io)
|
|
217
|
+
[](https://yarnpkg.com)
|
|
218
|
+
[](https://go.dev)
|
|
219
|
+
|
|
220
|
+
</div>
|
|
160
221
|
|
|
161
222
|
---
|
|
162
223
|
|
|
163
|
-
##
|
|
224
|
+
## 🚨 Emergency Bypass
|
|
164
225
|
|
|
165
226
|
```bash
|
|
227
|
+
# Skip the scan for this commit only (emergency use only)
|
|
166
228
|
SEC_GATE_SKIP=1 git commit -m "emergency fix"
|
|
167
229
|
```
|
|
168
230
|
|
|
231
|
+
> ⚠️ This only skips the **local** pre-commit hook. CI will still catch it.
|
|
232
|
+
|
|
169
233
|
---
|
|
170
234
|
|
|
171
|
-
## Auto-
|
|
235
|
+
## 👥 Team Auto-Setup
|
|
172
236
|
|
|
173
|
-
|
|
237
|
+
Add to your project's `package.json` so every developer gets the hook automatically on `npm install`:
|
|
174
238
|
|
|
175
239
|
```json
|
|
176
|
-
|
|
177
|
-
"
|
|
240
|
+
{
|
|
241
|
+
"scripts": {
|
|
242
|
+
"prepare": "sec-gate install"
|
|
243
|
+
}
|
|
178
244
|
}
|
|
179
245
|
```
|
|
180
246
|
|
|
181
|
-
Then
|
|
247
|
+
Then new developer onboarding is just:
|
|
182
248
|
|
|
183
249
|
```bash
|
|
184
|
-
npm install -g sec-gate #
|
|
185
|
-
npm install #
|
|
250
|
+
npm install -g sec-gate # once per machine
|
|
251
|
+
npm install # installs hook automatically via prepare script
|
|
186
252
|
```
|
|
187
253
|
|
|
188
|
-
No need to remember `sec-gate install` separately — `npm install` handles it.
|
|
189
|
-
|
|
190
|
-
> Tip: document these two commands in your project's `CONTRIBUTING.md` so every new joiner knows the setup.
|
|
191
|
-
|
|
192
254
|
---
|
|
193
255
|
|
|
194
|
-
##
|
|
256
|
+
## 🏗️ How it works internally
|
|
195
257
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
258
|
+
```
|
|
259
|
+
git commit
|
|
260
|
+
│
|
|
261
|
+
▼
|
|
262
|
+
pre-commit hook
|
|
263
|
+
│
|
|
264
|
+
├── Load .sec-gate.yml config
|
|
265
|
+
│
|
|
266
|
+
├── SAST ──► Semgrep (owasp-top10)
|
|
267
|
+
│ ──► AST walker (acorn) — SQL injection, secrets, prototype pollution
|
|
268
|
+
│
|
|
269
|
+
├── SCA ──► osv-scanner (npm/pnpm/yarn lockfile)
|
|
270
|
+
│ ──► govulncheck (go.mod)
|
|
271
|
+
│
|
|
272
|
+
├── Apply inline suppressions (sec-gate-disable / security-scan: disable)
|
|
273
|
+
│
|
|
274
|
+
├── Apply config filters (exclude_rules, exclude_paths, severity_threshold)
|
|
275
|
+
│
|
|
276
|
+
├── Findings? → exit 1 → commit BLOCKED ✗
|
|
277
|
+
└── Clean? → exit 0 → commit proceeds ✓
|
|
278
|
+
```
|
|
200
279
|
|
|
201
280
|
---
|
|
202
281
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
| # | Category | How covered |
|
|
206
|
-
|---|---|---|
|
|
207
|
-
| A01 | Broken Access Control | Semgrep `owasp-top10` ruleset |
|
|
208
|
-
| A02 | Cryptographic Failures | Semgrep `owasp-top10` ruleset |
|
|
209
|
-
| A03 | Injection | Semgrep `owasp-top10` ruleset |
|
|
210
|
-
| A04 | Insecure Design | Semgrep `owasp-top10` ruleset |
|
|
211
|
-
| A05 | Security Misconfiguration | Semgrep `owasp-top10` + Express rules |
|
|
212
|
-
| A06 | Vulnerable Components | OSV-Scanner (pnpm) + govulncheck (Go) |
|
|
213
|
-
| A07 | Authentication Failures | Semgrep `owasp-top10` ruleset |
|
|
214
|
-
| A08 | Software Integrity Failures | Semgrep `owasp-top10` ruleset |
|
|
215
|
-
| A09 | Logging Failures | Semgrep `owasp-top10` ruleset |
|
|
216
|
-
| A10 | Server-Side Request Forgery | Semgrep `owasp-top10` ruleset |
|
|
217
|
-
|
|
218
|
-
---
|
|
282
|
+
<div align="center">
|
|
219
283
|
|
|
220
|
-
|
|
284
|
+
**Built with ❤️ to make security automatic, not optional.**
|
|
221
285
|
|
|
222
|
-
|
|
286
|
+
[](https://www.npmjs.com/package/sec-gate)
|
|
287
|
+
[](https://github.com/SUNDRAMBHARDWAJ/sec-gate)
|
|
223
288
|
|
|
224
|
-
|
|
225
|
-
# Install Go: https://go.dev/dl/
|
|
226
|
-
# Then re-run:
|
|
227
|
-
npm install -g sec-gate
|
|
228
|
-
```
|
|
289
|
+
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sec-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.4",
|
|
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",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"node": ">=18"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"postinstall": "node scripts/postinstall.js"
|
|
19
|
+
"postinstall": "node scripts/postinstall.js",
|
|
20
|
+
"preuninstall": "node scripts/preuninstall.js"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"@pensar/semgrep-node": "^1.2.4",
|
package/scripts/postinstall.js
CHANGED
|
@@ -185,8 +185,9 @@ function buildStandaloneHook() {
|
|
|
185
185
|
' sec-gate scan --staged',
|
|
186
186
|
' exit $?',
|
|
187
187
|
'else',
|
|
188
|
-
' echo "sec-gate: not found in PATH
|
|
189
|
-
'
|
|
188
|
+
' echo "sec-gate: WARNING — sec-gate not found in PATH, security scan skipped."',
|
|
189
|
+
' echo "sec-gate: To re-enable scanning run: npm install -g sec-gate"',
|
|
190
|
+
' exit 0',
|
|
190
191
|
'fi',
|
|
191
192
|
''
|
|
192
193
|
].join('\n');
|
|
@@ -202,8 +203,8 @@ function buildHuskyInjectionBlock() {
|
|
|
202
203
|
' SEC_GATE_EXIT=$?',
|
|
203
204
|
' if [ $SEC_GATE_EXIT -ne 0 ]; then exit $SEC_GATE_EXIT; fi',
|
|
204
205
|
' else',
|
|
205
|
-
' echo "sec-gate: not found in PATH
|
|
206
|
-
'
|
|
206
|
+
' echo "sec-gate: WARNING — sec-gate not found in PATH, security scan skipped."',
|
|
207
|
+
' echo "sec-gate: To re-enable scanning run: npm install -g sec-gate"',
|
|
207
208
|
' fi',
|
|
208
209
|
'fi',
|
|
209
210
|
'# end-sec-gate',
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// security-scan: disable rule-id: detect-non-literal-fs-filename reason: all paths derived from git rev-parse, never user input
|
|
3
|
+
// security-scan: disable rule-id: path-join-resolve-traversal reason: all paths derived from git rev-parse, never user input
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* sec-gate preuninstall script
|
|
7
|
+
*
|
|
8
|
+
* Runs automatically when the developer executes:
|
|
9
|
+
* npm uninstall -g sec-gate
|
|
10
|
+
* pnpm remove -g sec-gate
|
|
11
|
+
* yarn global remove sec-gate
|
|
12
|
+
*
|
|
13
|
+
* WHAT IT DOES:
|
|
14
|
+
* - Finds the git repo the developer is currently inside (if any)
|
|
15
|
+
* - Locates the pre-commit hook file that sec-gate injected into
|
|
16
|
+
* - Removes ONLY the sec-gate block (between HOOK_MARKER and END_MARKER)
|
|
17
|
+
* - If the file becomes empty / only a shebang after removal, deletes it
|
|
18
|
+
* - Never touches anything outside the sec-gate markers
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { execFileSync } = require('child_process');
|
|
24
|
+
|
|
25
|
+
const HOOK_MARKER = '# installed-by: sec-gate';
|
|
26
|
+
const END_MARKER = '# end-sec-gate';
|
|
27
|
+
|
|
28
|
+
// ─────────────────────────────────────────────────────────
|
|
29
|
+
// Helpers
|
|
30
|
+
// ─────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function getRepoRoot() {
|
|
33
|
+
try {
|
|
34
|
+
return execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
37
|
+
}).trim();
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the actual pre-commit hook path git would use,
|
|
45
|
+
* identical logic to install.js so we always find the same file.
|
|
46
|
+
*/
|
|
47
|
+
function resolveHookPath(repoRoot) {
|
|
48
|
+
let hooksDir;
|
|
49
|
+
try {
|
|
50
|
+
const configured = execFileSync(
|
|
51
|
+
'git', ['config', '--local', 'core.hooksPath'],
|
|
52
|
+
{ encoding: 'utf8', cwd: repoRoot, stdio: ['ignore', 'pipe', 'ignore'] }
|
|
53
|
+
).trim();
|
|
54
|
+
if (configured) {
|
|
55
|
+
hooksDir = path.isAbsolute(configured)
|
|
56
|
+
? configured
|
|
57
|
+
: path.join(repoRoot, configured);
|
|
58
|
+
|
|
59
|
+
// Husky shim redirect (same as install.js)
|
|
60
|
+
const huskyShimDir = path.join(repoRoot, '.husky', '_');
|
|
61
|
+
if (hooksDir === huskyShimDir || hooksDir.startsWith(huskyShimDir + path.sep)) {
|
|
62
|
+
hooksDir = path.join(repoRoot, '.husky');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch { /* no custom hooksPath configured */ }
|
|
66
|
+
|
|
67
|
+
if (!hooksDir) hooksDir = path.join(repoRoot, '.git', 'hooks');
|
|
68
|
+
return path.join(hooksDir, 'pre-commit');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Remove the sec-gate block from content string.
|
|
73
|
+
* Handles two cases:
|
|
74
|
+
* 1. Standalone hook — entire file starts with HOOK_MARKER
|
|
75
|
+
* 2. Injected block — block is between HOOK_MARKER and END_MARKER
|
|
76
|
+
*/
|
|
77
|
+
function removeSecGateBlock(content) {
|
|
78
|
+
const lines = content.split('\n');
|
|
79
|
+
|
|
80
|
+
// Find marker boundaries
|
|
81
|
+
let startIdx = -1;
|
|
82
|
+
let endIdx = -1;
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < lines.length; i++) {
|
|
85
|
+
if (lines[i].trim() === HOOK_MARKER && startIdx === -1) startIdx = i;
|
|
86
|
+
if (lines[i].trim() === END_MARKER && startIdx !== -1) { endIdx = i; break; }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Case: standalone hook — HOOK_MARKER appears right after the shebang (line 0 or 1)
|
|
90
|
+
// In this case the whole file is sec-gate's, so signal full removal
|
|
91
|
+
if (startIdx !== -1 && startIdx <= 1 && endIdx === -1) {
|
|
92
|
+
// No END_MARKER means the whole file is ours (standalone format)
|
|
93
|
+
return null; // caller should delete the file
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Case: no markers found — nothing to remove
|
|
97
|
+
if (startIdx === -1) return content;
|
|
98
|
+
|
|
99
|
+
// Case: injected block — remove from startIdx to endIdx (inclusive)
|
|
100
|
+
// Also eat the blank line immediately before the marker if present
|
|
101
|
+
const removeFrom = (startIdx > 0 && lines[startIdx - 1].trim() === '') ? startIdx - 1 : startIdx;
|
|
102
|
+
const removeTo = endIdx !== -1 ? endIdx : lines.length - 1;
|
|
103
|
+
|
|
104
|
+
lines.splice(removeFrom, removeTo - removeFrom + 1);
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns true if the file content is effectively empty
|
|
110
|
+
* (only shebang and/or blank lines remain).
|
|
111
|
+
*/
|
|
112
|
+
function isEffectivelyEmpty(content) {
|
|
113
|
+
const meaningful = content.split('\n').filter(
|
|
114
|
+
(l) => l.trim() !== '' && !l.trim().startsWith('#!')
|
|
115
|
+
);
|
|
116
|
+
return meaningful.length === 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─────────────────────────────────────────────────────────
|
|
120
|
+
// Main
|
|
121
|
+
// ─────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
function main() {
|
|
124
|
+
const repoRoot = getRepoRoot();
|
|
125
|
+
if (!repoRoot) {
|
|
126
|
+
console.log('sec-gate: not inside a git repo — nothing to clean up');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const hookPath = resolveHookPath(repoRoot);
|
|
131
|
+
|
|
132
|
+
if (!fs.existsSync(hookPath)) {
|
|
133
|
+
console.log('sec-gate: no pre-commit hook found — nothing to clean up');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const original = fs.readFileSync(hookPath, 'utf8');
|
|
138
|
+
|
|
139
|
+
if (!original.includes(HOOK_MARKER)) {
|
|
140
|
+
console.log('sec-gate: pre-commit hook was not installed by sec-gate — leaving it untouched');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const cleaned = removeSecGateBlock(original);
|
|
145
|
+
|
|
146
|
+
if (cleaned === null || isEffectivelyEmpty(cleaned)) {
|
|
147
|
+
// The whole file was sec-gate's — remove it entirely
|
|
148
|
+
fs.unlinkSync(hookPath);
|
|
149
|
+
console.log(`sec-gate: removed pre-commit hook from ${hookPath}`);
|
|
150
|
+
} else {
|
|
151
|
+
fs.writeFileSync(hookPath, cleaned, { encoding: 'utf8', mode: 0o755 });
|
|
152
|
+
console.log(`sec-gate: removed sec-gate block from ${hookPath}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log('sec-gate: cleanup complete. Goodbye!');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
main();
|
|
160
|
+
} catch (err) {
|
|
161
|
+
// Never block the uninstall itself
|
|
162
|
+
console.warn('sec-gate preuninstall warning:', err.message);
|
|
163
|
+
process.exit(0);
|
|
164
|
+
}
|
package/src/commands/install.js
CHANGED
|
@@ -59,8 +59,8 @@ function secGateShellBlock() {
|
|
|
59
59
|
' _SG_EXIT=$?',
|
|
60
60
|
' if [ $_SG_EXIT -ne 0 ]; then exit $_SG_EXIT; fi',
|
|
61
61
|
' else',
|
|
62
|
-
' echo "sec-gate: not found
|
|
63
|
-
'
|
|
62
|
+
' echo "sec-gate: WARNING — sec-gate not found in PATH, security scan skipped."',
|
|
63
|
+
' echo "sec-gate: To re-enable scanning run: npm install -g sec-gate"',
|
|
64
64
|
' fi',
|
|
65
65
|
'fi',
|
|
66
66
|
END_MARKER,
|
|
@@ -87,8 +87,9 @@ function standaloneHook() {
|
|
|
87
87
|
' sec-gate scan --staged',
|
|
88
88
|
' exit $?',
|
|
89
89
|
'else',
|
|
90
|
-
' echo "sec-gate: not found
|
|
91
|
-
'
|
|
90
|
+
' echo "sec-gate: WARNING — sec-gate not found in PATH, security scan skipped."',
|
|
91
|
+
' echo "sec-gate: To re-enable scanning run: npm install -g sec-gate"',
|
|
92
|
+
' exit 0',
|
|
92
93
|
'fi',
|
|
93
94
|
''
|
|
94
95
|
].join('\n');
|