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.
@@ -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. Run: npm install -g sec-gate"',
189
- ' exit 1',
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. Run: npm install -g sec-gate"',
206
- ' exit 1',
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
+ }
@@ -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. Run: npm install -g sec-gate"',
63
- ' exit 1',
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. Run: npm install -g sec-gate"',
91
- ' exit 1',
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');