sec-gate 0.1.9 → 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 +6 -3
- package/rules/custom-security.js +410 -247
- package/scripts/postinstall.js +5 -4
- package/scripts/preuninstall.js +164 -0
- package/src/commands/install.js +5 -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');
|