sovr-mcp-proxy 6.0.1 → 7.0.0

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.
@@ -0,0 +1,365 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/commandNormalizer.ts
21
+ var commandNormalizer_exports = {};
22
+ __export(commandNormalizer_exports, {
23
+ getBaseCommand: () => getBaseCommand,
24
+ hasDestructiveEquivalent: () => hasDestructiveEquivalent,
25
+ hasEncodingTricks: () => hasEncodingTricks,
26
+ normalize: () => normalize,
27
+ summarize: () => summarize
28
+ });
29
+ module.exports = __toCommonJS(commandNormalizer_exports);
30
+ var SHELL_WRAPPERS = [
31
+ // bash -c "cmd" / sh -c "cmd"
32
+ {
33
+ pattern: /^(?:bash|sh|zsh|dash|ksh)\s+-c\s+(?:"([^"]+)"|'([^']+)'|(\S+))/,
34
+ extract: (m) => m[1] || m[2] || m[3] || "",
35
+ name: "shell -c"
36
+ },
37
+ // env cmd args...
38
+ {
39
+ pattern: /^env\s+(?:-\S+\s+)*(?:\S+=\S+\s+)*(.+)/,
40
+ extract: (m) => m[1] || "",
41
+ name: "env"
42
+ },
43
+ // xargs cmd
44
+ {
45
+ pattern: /^xargs\s+(?:-\S+\s+)*(.+)/,
46
+ extract: (m) => m[1] || "",
47
+ name: "xargs"
48
+ },
49
+ // sudo cmd
50
+ {
51
+ pattern: /^sudo\s+(?:-\S+\s+)*(.+)/,
52
+ extract: (m) => m[1] || "",
53
+ name: "sudo"
54
+ },
55
+ // nohup cmd
56
+ {
57
+ pattern: /^nohup\s+(.+?)(?:\s*&\s*)?$/,
58
+ extract: (m) => m[1] || "",
59
+ name: "nohup"
60
+ },
61
+ // timeout N cmd
62
+ {
63
+ pattern: /^timeout\s+\d+[smhd]?\s+(.+)/,
64
+ extract: (m) => m[1] || "",
65
+ name: "timeout"
66
+ },
67
+ // nice -n N cmd
68
+ {
69
+ pattern: /^nice\s+(?:-n\s+\d+\s+)?(.+)/,
70
+ extract: (m) => m[1] || "",
71
+ name: "nice"
72
+ },
73
+ // strace / ltrace cmd
74
+ {
75
+ pattern: /^(?:strace|ltrace)\s+(?:-\S+\s+)*(.+)/,
76
+ extract: (m) => m[1] || "",
77
+ name: "trace"
78
+ },
79
+ // watch -n N cmd
80
+ {
81
+ pattern: /^watch\s+(?:-n\s+\d+\s+)?(.+)/,
82
+ extract: (m) => m[1] || "",
83
+ name: "watch"
84
+ }
85
+ ];
86
+ var ENCODING_PATTERNS = [
87
+ // echo "base64" | base64 -d | bash
88
+ {
89
+ pattern: /base64\s+(?:-d|--decode)/,
90
+ name: "base64-decode",
91
+ risk: "Command may be hidden via base64 encoding"
92
+ },
93
+ // printf '\x72\x6d' (hex encoding)
94
+ {
95
+ pattern: /printf\s+.*\\x[0-9a-fA-F]{2}/,
96
+ name: "hex-printf",
97
+ risk: "Command may be hidden via hex encoding in printf"
98
+ },
99
+ // $'\x72\x6d' (ANSI-C quoting)
100
+ {
101
+ pattern: /\$'[^']*\\x[0-9a-fA-F]{2}/,
102
+ name: "ansi-c-quoting",
103
+ risk: "Command may be hidden via ANSI-C quoting"
104
+ },
105
+ // python -c "import os; os.system('rm -rf /')"
106
+ {
107
+ pattern: /python[23]?\s+-c\s+/,
108
+ name: "python-exec",
109
+ risk: "Arbitrary code execution via Python"
110
+ },
111
+ // perl -e "system('rm -rf /')"
112
+ {
113
+ pattern: /perl\s+-e\s+/,
114
+ name: "perl-exec",
115
+ risk: "Arbitrary code execution via Perl"
116
+ },
117
+ // ruby -e "system('rm -rf /')"
118
+ {
119
+ pattern: /ruby\s+-e\s+/,
120
+ name: "ruby-exec",
121
+ risk: "Arbitrary code execution via Ruby"
122
+ },
123
+ // eval "cmd"
124
+ {
125
+ pattern: /\beval\s+/,
126
+ name: "eval",
127
+ risk: "Dynamic command evaluation"
128
+ },
129
+ // curl ... | bash
130
+ {
131
+ pattern: /curl\s+.*\|\s*(?:bash|sh|zsh)/,
132
+ name: "curl-pipe-shell",
133
+ risk: "Remote code execution via curl | bash"
134
+ },
135
+ // wget ... -O - | bash
136
+ {
137
+ pattern: /wget\s+.*\|\s*(?:bash|sh|zsh)/,
138
+ name: "wget-pipe-shell",
139
+ risk: "Remote code execution via wget | bash"
140
+ }
141
+ ];
142
+ var DESTRUCTIVE_EQUIVALENTS = [
143
+ // find / -delete (equivalent to rm -rf)
144
+ { pattern: /find\s+.*-delete/, equivalent: "rm -rf", description: "find -delete is equivalent to rm -rf" },
145
+ // find / -exec rm {} (equivalent to rm -rf)
146
+ { pattern: /find\s+.*-exec\s+rm/, equivalent: "rm -rf", description: "find -exec rm is equivalent to rm -rf" },
147
+ // TRUNCATE TABLE (equivalent to DELETE FROM)
148
+ { pattern: /TRUNCATE\s+TABLE/i, equivalent: "DELETE FROM", description: "TRUNCATE TABLE is equivalent to DELETE FROM" },
149
+ // dd if=/dev/zero of=/ (disk wipe)
150
+ { pattern: /dd\s+.*of=\//, equivalent: "disk-wipe", description: "dd writing to root is destructive" },
151
+ // mkfs (format disk)
152
+ { pattern: /mkfs/, equivalent: "disk-format", description: "mkfs formats a disk" },
153
+ // shred (secure delete)
154
+ { pattern: /shred\s+/, equivalent: "secure-delete", description: "shred securely deletes files" },
155
+ // chmod 777 / (open permissions)
156
+ { pattern: /chmod\s+777\s+\//, equivalent: "open-permissions", description: "chmod 777 / opens all permissions" },
157
+ // chown root (change ownership)
158
+ { pattern: /chown\s+.*\//, equivalent: "change-ownership", description: "chown on root paths is dangerous" },
159
+ // iptables -F (flush firewall)
160
+ { pattern: /iptables\s+-F/, equivalent: "flush-firewall", description: "iptables -F flushes all firewall rules" }
161
+ ];
162
+ function normalize(command) {
163
+ const original = command;
164
+ const segments = [];
165
+ const suspicion_reasons = [];
166
+ const trimmed = command.trim();
167
+ if (!trimmed) {
168
+ return { segments: [], suspicious: false, suspicion_reasons: [], original };
169
+ }
170
+ for (const enc of ENCODING_PATTERNS) {
171
+ if (enc.pattern.test(trimmed)) {
172
+ suspicion_reasons.push(`${enc.name}: ${enc.risk}`);
173
+ }
174
+ }
175
+ for (const deq of DESTRUCTIVE_EQUIVALENTS) {
176
+ if (deq.pattern.test(trimmed)) {
177
+ suspicion_reasons.push(`Destructive equivalent detected: ${deq.description}`);
178
+ }
179
+ }
180
+ const chainSegments = splitChains(trimmed);
181
+ for (const chainSeg of chainSegments) {
182
+ const pipeSegments = splitPipes(chainSeg);
183
+ for (const pipeSeg of pipeSegments) {
184
+ const unwrapped = unwrapCommand(pipeSeg.trim());
185
+ if (unwrapped.unwrapped) {
186
+ segments.push({
187
+ raw: pipeSeg.trim(),
188
+ effective: unwrapped.effective,
189
+ type: "unwrapped",
190
+ warnings: unwrapped.warnings
191
+ });
192
+ const inner = normalize(unwrapped.effective);
193
+ for (const innerSeg of inner.segments) {
194
+ if (innerSeg.effective !== unwrapped.effective) {
195
+ segments.push(innerSeg);
196
+ }
197
+ }
198
+ suspicion_reasons.push(...inner.suspicion_reasons);
199
+ } else {
200
+ const type = pipeSegments.length > 1 ? "pipe-segment" : chainSegments.length > 1 ? "chain-segment" : "direct";
201
+ segments.push({
202
+ raw: pipeSeg.trim(),
203
+ effective: pipeSeg.trim(),
204
+ type,
205
+ warnings: []
206
+ });
207
+ }
208
+ }
209
+ }
210
+ const subshells = extractSubshells(trimmed);
211
+ for (const sub of subshells) {
212
+ segments.push({
213
+ raw: trimmed,
214
+ effective: sub,
215
+ type: "subshell",
216
+ warnings: ["Subshell command extracted for evaluation"]
217
+ });
218
+ }
219
+ return {
220
+ segments,
221
+ suspicious: suspicion_reasons.length > 0,
222
+ suspicion_reasons,
223
+ original
224
+ };
225
+ }
226
+ function splitChains(command) {
227
+ return splitRespectingQuotes(command, /\s*(?:&&|\|\||;)\s*/);
228
+ }
229
+ function splitPipes(command) {
230
+ return splitRespectingQuotes(command, /\s*\|(?!\|)\s*/);
231
+ }
232
+ function splitRespectingQuotes(input, delimiter) {
233
+ const segments = [];
234
+ let current = "";
235
+ let inSingleQuote = false;
236
+ let inDoubleQuote = false;
237
+ let escaped = false;
238
+ let i = 0;
239
+ while (i < input.length) {
240
+ const char = input[i];
241
+ if (escaped) {
242
+ current += char;
243
+ escaped = false;
244
+ i++;
245
+ continue;
246
+ }
247
+ if (char === "\\") {
248
+ escaped = true;
249
+ current += char;
250
+ i++;
251
+ continue;
252
+ }
253
+ if (char === "'" && !inDoubleQuote) {
254
+ inSingleQuote = !inSingleQuote;
255
+ current += char;
256
+ i++;
257
+ continue;
258
+ }
259
+ if (char === '"' && !inSingleQuote) {
260
+ inDoubleQuote = !inDoubleQuote;
261
+ current += char;
262
+ i++;
263
+ continue;
264
+ }
265
+ if (!inSingleQuote && !inDoubleQuote) {
266
+ const remaining = input.slice(i);
267
+ const match = remaining.match(delimiter);
268
+ if (match && match.index === 0) {
269
+ if (current.trim()) {
270
+ segments.push(current.trim());
271
+ }
272
+ current = "";
273
+ i += match[0].length;
274
+ continue;
275
+ }
276
+ }
277
+ current += char;
278
+ i++;
279
+ }
280
+ if (current.trim()) {
281
+ segments.push(current.trim());
282
+ }
283
+ return segments.length > 0 ? segments : [input];
284
+ }
285
+ function unwrapCommand(command) {
286
+ for (const wrapper of SHELL_WRAPPERS) {
287
+ const match = command.match(wrapper.pattern);
288
+ if (match) {
289
+ const inner = wrapper.extract(match);
290
+ if (inner) {
291
+ return {
292
+ unwrapped: true,
293
+ effective: inner.trim(),
294
+ wrapper: wrapper.name,
295
+ warnings: [`Unwrapped from ${wrapper.name}: "${command}" \u2192 "${inner.trim()}"`]
296
+ };
297
+ }
298
+ }
299
+ }
300
+ return { unwrapped: false, effective: command, warnings: [] };
301
+ }
302
+ function extractSubshells(command) {
303
+ const subshells = [];
304
+ const dollarParen = /\$\(([^)]+)\)/g;
305
+ let match;
306
+ while ((match = dollarParen.exec(command)) !== null) {
307
+ if (match[1]) subshells.push(match[1].trim());
308
+ }
309
+ const backtick = /`([^`]+)`/g;
310
+ while ((match = backtick.exec(command)) !== null) {
311
+ if (match[1]) subshells.push(match[1].trim());
312
+ }
313
+ return subshells;
314
+ }
315
+ function getBaseCommand(command) {
316
+ const trimmed = command.trim();
317
+ const firstSpace = trimmed.indexOf(" ");
318
+ return firstSpace === -1 ? trimmed : trimmed.slice(0, firstSpace);
319
+ }
320
+ function hasDestructiveEquivalent(command) {
321
+ const matches = [];
322
+ for (const deq of DESTRUCTIVE_EQUIVALENTS) {
323
+ if (deq.pattern.test(command)) {
324
+ matches.push({ equivalent: deq.equivalent, description: deq.description });
325
+ }
326
+ }
327
+ return { found: matches.length > 0, matches };
328
+ }
329
+ function hasEncodingTricks(command) {
330
+ const tricks = [];
331
+ for (const enc of ENCODING_PATTERNS) {
332
+ if (enc.pattern.test(command)) {
333
+ tricks.push({ name: enc.name, risk: enc.risk });
334
+ }
335
+ }
336
+ return { found: tricks.length > 0, tricks };
337
+ }
338
+ function summarize(result) {
339
+ const lines = [];
340
+ lines.push(`Original: ${result.original}`);
341
+ lines.push(`Segments: ${result.segments.length}`);
342
+ lines.push(`Suspicious: ${result.suspicious}`);
343
+ if (result.suspicion_reasons.length > 0) {
344
+ lines.push(`Suspicion reasons:`);
345
+ for (const reason of result.suspicion_reasons) {
346
+ lines.push(` - ${reason}`);
347
+ }
348
+ }
349
+ lines.push(`Segments:`);
350
+ for (const seg of result.segments) {
351
+ lines.push(` [${seg.type}] ${seg.effective}`);
352
+ for (const w of seg.warnings) {
353
+ lines.push(` \u26A0 ${w}`);
354
+ }
355
+ }
356
+ return lines.join("\n");
357
+ }
358
+ // Annotate the CommonJS export names for ESM import in node:
359
+ 0 && (module.exports = {
360
+ getBaseCommand,
361
+ hasDestructiveEquivalent,
362
+ hasEncodingTricks,
363
+ normalize,
364
+ summarize
365
+ });
@@ -0,0 +1,336 @@
1
+ // src/commandNormalizer.ts
2
+ var SHELL_WRAPPERS = [
3
+ // bash -c "cmd" / sh -c "cmd"
4
+ {
5
+ pattern: /^(?:bash|sh|zsh|dash|ksh)\s+-c\s+(?:"([^"]+)"|'([^']+)'|(\S+))/,
6
+ extract: (m) => m[1] || m[2] || m[3] || "",
7
+ name: "shell -c"
8
+ },
9
+ // env cmd args...
10
+ {
11
+ pattern: /^env\s+(?:-\S+\s+)*(?:\S+=\S+\s+)*(.+)/,
12
+ extract: (m) => m[1] || "",
13
+ name: "env"
14
+ },
15
+ // xargs cmd
16
+ {
17
+ pattern: /^xargs\s+(?:-\S+\s+)*(.+)/,
18
+ extract: (m) => m[1] || "",
19
+ name: "xargs"
20
+ },
21
+ // sudo cmd
22
+ {
23
+ pattern: /^sudo\s+(?:-\S+\s+)*(.+)/,
24
+ extract: (m) => m[1] || "",
25
+ name: "sudo"
26
+ },
27
+ // nohup cmd
28
+ {
29
+ pattern: /^nohup\s+(.+?)(?:\s*&\s*)?$/,
30
+ extract: (m) => m[1] || "",
31
+ name: "nohup"
32
+ },
33
+ // timeout N cmd
34
+ {
35
+ pattern: /^timeout\s+\d+[smhd]?\s+(.+)/,
36
+ extract: (m) => m[1] || "",
37
+ name: "timeout"
38
+ },
39
+ // nice -n N cmd
40
+ {
41
+ pattern: /^nice\s+(?:-n\s+\d+\s+)?(.+)/,
42
+ extract: (m) => m[1] || "",
43
+ name: "nice"
44
+ },
45
+ // strace / ltrace cmd
46
+ {
47
+ pattern: /^(?:strace|ltrace)\s+(?:-\S+\s+)*(.+)/,
48
+ extract: (m) => m[1] || "",
49
+ name: "trace"
50
+ },
51
+ // watch -n N cmd
52
+ {
53
+ pattern: /^watch\s+(?:-n\s+\d+\s+)?(.+)/,
54
+ extract: (m) => m[1] || "",
55
+ name: "watch"
56
+ }
57
+ ];
58
+ var ENCODING_PATTERNS = [
59
+ // echo "base64" | base64 -d | bash
60
+ {
61
+ pattern: /base64\s+(?:-d|--decode)/,
62
+ name: "base64-decode",
63
+ risk: "Command may be hidden via base64 encoding"
64
+ },
65
+ // printf '\x72\x6d' (hex encoding)
66
+ {
67
+ pattern: /printf\s+.*\\x[0-9a-fA-F]{2}/,
68
+ name: "hex-printf",
69
+ risk: "Command may be hidden via hex encoding in printf"
70
+ },
71
+ // $'\x72\x6d' (ANSI-C quoting)
72
+ {
73
+ pattern: /\$'[^']*\\x[0-9a-fA-F]{2}/,
74
+ name: "ansi-c-quoting",
75
+ risk: "Command may be hidden via ANSI-C quoting"
76
+ },
77
+ // python -c "import os; os.system('rm -rf /')"
78
+ {
79
+ pattern: /python[23]?\s+-c\s+/,
80
+ name: "python-exec",
81
+ risk: "Arbitrary code execution via Python"
82
+ },
83
+ // perl -e "system('rm -rf /')"
84
+ {
85
+ pattern: /perl\s+-e\s+/,
86
+ name: "perl-exec",
87
+ risk: "Arbitrary code execution via Perl"
88
+ },
89
+ // ruby -e "system('rm -rf /')"
90
+ {
91
+ pattern: /ruby\s+-e\s+/,
92
+ name: "ruby-exec",
93
+ risk: "Arbitrary code execution via Ruby"
94
+ },
95
+ // eval "cmd"
96
+ {
97
+ pattern: /\beval\s+/,
98
+ name: "eval",
99
+ risk: "Dynamic command evaluation"
100
+ },
101
+ // curl ... | bash
102
+ {
103
+ pattern: /curl\s+.*\|\s*(?:bash|sh|zsh)/,
104
+ name: "curl-pipe-shell",
105
+ risk: "Remote code execution via curl | bash"
106
+ },
107
+ // wget ... -O - | bash
108
+ {
109
+ pattern: /wget\s+.*\|\s*(?:bash|sh|zsh)/,
110
+ name: "wget-pipe-shell",
111
+ risk: "Remote code execution via wget | bash"
112
+ }
113
+ ];
114
+ var DESTRUCTIVE_EQUIVALENTS = [
115
+ // find / -delete (equivalent to rm -rf)
116
+ { pattern: /find\s+.*-delete/, equivalent: "rm -rf", description: "find -delete is equivalent to rm -rf" },
117
+ // find / -exec rm {} (equivalent to rm -rf)
118
+ { pattern: /find\s+.*-exec\s+rm/, equivalent: "rm -rf", description: "find -exec rm is equivalent to rm -rf" },
119
+ // TRUNCATE TABLE (equivalent to DELETE FROM)
120
+ { pattern: /TRUNCATE\s+TABLE/i, equivalent: "DELETE FROM", description: "TRUNCATE TABLE is equivalent to DELETE FROM" },
121
+ // dd if=/dev/zero of=/ (disk wipe)
122
+ { pattern: /dd\s+.*of=\//, equivalent: "disk-wipe", description: "dd writing to root is destructive" },
123
+ // mkfs (format disk)
124
+ { pattern: /mkfs/, equivalent: "disk-format", description: "mkfs formats a disk" },
125
+ // shred (secure delete)
126
+ { pattern: /shred\s+/, equivalent: "secure-delete", description: "shred securely deletes files" },
127
+ // chmod 777 / (open permissions)
128
+ { pattern: /chmod\s+777\s+\//, equivalent: "open-permissions", description: "chmod 777 / opens all permissions" },
129
+ // chown root (change ownership)
130
+ { pattern: /chown\s+.*\//, equivalent: "change-ownership", description: "chown on root paths is dangerous" },
131
+ // iptables -F (flush firewall)
132
+ { pattern: /iptables\s+-F/, equivalent: "flush-firewall", description: "iptables -F flushes all firewall rules" }
133
+ ];
134
+ function normalize(command) {
135
+ const original = command;
136
+ const segments = [];
137
+ const suspicion_reasons = [];
138
+ const trimmed = command.trim();
139
+ if (!trimmed) {
140
+ return { segments: [], suspicious: false, suspicion_reasons: [], original };
141
+ }
142
+ for (const enc of ENCODING_PATTERNS) {
143
+ if (enc.pattern.test(trimmed)) {
144
+ suspicion_reasons.push(`${enc.name}: ${enc.risk}`);
145
+ }
146
+ }
147
+ for (const deq of DESTRUCTIVE_EQUIVALENTS) {
148
+ if (deq.pattern.test(trimmed)) {
149
+ suspicion_reasons.push(`Destructive equivalent detected: ${deq.description}`);
150
+ }
151
+ }
152
+ const chainSegments = splitChains(trimmed);
153
+ for (const chainSeg of chainSegments) {
154
+ const pipeSegments = splitPipes(chainSeg);
155
+ for (const pipeSeg of pipeSegments) {
156
+ const unwrapped = unwrapCommand(pipeSeg.trim());
157
+ if (unwrapped.unwrapped) {
158
+ segments.push({
159
+ raw: pipeSeg.trim(),
160
+ effective: unwrapped.effective,
161
+ type: "unwrapped",
162
+ warnings: unwrapped.warnings
163
+ });
164
+ const inner = normalize(unwrapped.effective);
165
+ for (const innerSeg of inner.segments) {
166
+ if (innerSeg.effective !== unwrapped.effective) {
167
+ segments.push(innerSeg);
168
+ }
169
+ }
170
+ suspicion_reasons.push(...inner.suspicion_reasons);
171
+ } else {
172
+ const type = pipeSegments.length > 1 ? "pipe-segment" : chainSegments.length > 1 ? "chain-segment" : "direct";
173
+ segments.push({
174
+ raw: pipeSeg.trim(),
175
+ effective: pipeSeg.trim(),
176
+ type,
177
+ warnings: []
178
+ });
179
+ }
180
+ }
181
+ }
182
+ const subshells = extractSubshells(trimmed);
183
+ for (const sub of subshells) {
184
+ segments.push({
185
+ raw: trimmed,
186
+ effective: sub,
187
+ type: "subshell",
188
+ warnings: ["Subshell command extracted for evaluation"]
189
+ });
190
+ }
191
+ return {
192
+ segments,
193
+ suspicious: suspicion_reasons.length > 0,
194
+ suspicion_reasons,
195
+ original
196
+ };
197
+ }
198
+ function splitChains(command) {
199
+ return splitRespectingQuotes(command, /\s*(?:&&|\|\||;)\s*/);
200
+ }
201
+ function splitPipes(command) {
202
+ return splitRespectingQuotes(command, /\s*\|(?!\|)\s*/);
203
+ }
204
+ function splitRespectingQuotes(input, delimiter) {
205
+ const segments = [];
206
+ let current = "";
207
+ let inSingleQuote = false;
208
+ let inDoubleQuote = false;
209
+ let escaped = false;
210
+ let i = 0;
211
+ while (i < input.length) {
212
+ const char = input[i];
213
+ if (escaped) {
214
+ current += char;
215
+ escaped = false;
216
+ i++;
217
+ continue;
218
+ }
219
+ if (char === "\\") {
220
+ escaped = true;
221
+ current += char;
222
+ i++;
223
+ continue;
224
+ }
225
+ if (char === "'" && !inDoubleQuote) {
226
+ inSingleQuote = !inSingleQuote;
227
+ current += char;
228
+ i++;
229
+ continue;
230
+ }
231
+ if (char === '"' && !inSingleQuote) {
232
+ inDoubleQuote = !inDoubleQuote;
233
+ current += char;
234
+ i++;
235
+ continue;
236
+ }
237
+ if (!inSingleQuote && !inDoubleQuote) {
238
+ const remaining = input.slice(i);
239
+ const match = remaining.match(delimiter);
240
+ if (match && match.index === 0) {
241
+ if (current.trim()) {
242
+ segments.push(current.trim());
243
+ }
244
+ current = "";
245
+ i += match[0].length;
246
+ continue;
247
+ }
248
+ }
249
+ current += char;
250
+ i++;
251
+ }
252
+ if (current.trim()) {
253
+ segments.push(current.trim());
254
+ }
255
+ return segments.length > 0 ? segments : [input];
256
+ }
257
+ function unwrapCommand(command) {
258
+ for (const wrapper of SHELL_WRAPPERS) {
259
+ const match = command.match(wrapper.pattern);
260
+ if (match) {
261
+ const inner = wrapper.extract(match);
262
+ if (inner) {
263
+ return {
264
+ unwrapped: true,
265
+ effective: inner.trim(),
266
+ wrapper: wrapper.name,
267
+ warnings: [`Unwrapped from ${wrapper.name}: "${command}" \u2192 "${inner.trim()}"`]
268
+ };
269
+ }
270
+ }
271
+ }
272
+ return { unwrapped: false, effective: command, warnings: [] };
273
+ }
274
+ function extractSubshells(command) {
275
+ const subshells = [];
276
+ const dollarParen = /\$\(([^)]+)\)/g;
277
+ let match;
278
+ while ((match = dollarParen.exec(command)) !== null) {
279
+ if (match[1]) subshells.push(match[1].trim());
280
+ }
281
+ const backtick = /`([^`]+)`/g;
282
+ while ((match = backtick.exec(command)) !== null) {
283
+ if (match[1]) subshells.push(match[1].trim());
284
+ }
285
+ return subshells;
286
+ }
287
+ function getBaseCommand(command) {
288
+ const trimmed = command.trim();
289
+ const firstSpace = trimmed.indexOf(" ");
290
+ return firstSpace === -1 ? trimmed : trimmed.slice(0, firstSpace);
291
+ }
292
+ function hasDestructiveEquivalent(command) {
293
+ const matches = [];
294
+ for (const deq of DESTRUCTIVE_EQUIVALENTS) {
295
+ if (deq.pattern.test(command)) {
296
+ matches.push({ equivalent: deq.equivalent, description: deq.description });
297
+ }
298
+ }
299
+ return { found: matches.length > 0, matches };
300
+ }
301
+ function hasEncodingTricks(command) {
302
+ const tricks = [];
303
+ for (const enc of ENCODING_PATTERNS) {
304
+ if (enc.pattern.test(command)) {
305
+ tricks.push({ name: enc.name, risk: enc.risk });
306
+ }
307
+ }
308
+ return { found: tricks.length > 0, tricks };
309
+ }
310
+ function summarize(result) {
311
+ const lines = [];
312
+ lines.push(`Original: ${result.original}`);
313
+ lines.push(`Segments: ${result.segments.length}`);
314
+ lines.push(`Suspicious: ${result.suspicious}`);
315
+ if (result.suspicion_reasons.length > 0) {
316
+ lines.push(`Suspicion reasons:`);
317
+ for (const reason of result.suspicion_reasons) {
318
+ lines.push(` - ${reason}`);
319
+ }
320
+ }
321
+ lines.push(`Segments:`);
322
+ for (const seg of result.segments) {
323
+ lines.push(` [${seg.type}] ${seg.effective}`);
324
+ for (const w of seg.warnings) {
325
+ lines.push(` \u26A0 ${w}`);
326
+ }
327
+ }
328
+ return lines.join("\n");
329
+ }
330
+ export {
331
+ getBaseCommand,
332
+ hasDestructiveEquivalent,
333
+ hasEncodingTricks,
334
+ normalize,
335
+ summarize
336
+ };