@kenkaiiii/ggcoder 5.23.2 → 5.24.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.
- package/dist/app-sidecar.js +32 -0
- package/dist/app-sidecar.js.map +1 -1
- package/dist/chat-agents/general.test.js +6 -3
- package/dist/chat-agents/general.test.js.map +1 -1
- package/dist/chat-agents/specialists.test.js +3 -2
- package/dist/chat-agents/specialists.test.js.map +1 -1
- package/dist/core/agent-session-add-dir.test.d.ts +2 -0
- package/dist/core/agent-session-add-dir.test.d.ts.map +1 -0
- package/dist/core/agent-session-add-dir.test.js +94 -0
- package/dist/core/agent-session-add-dir.test.js.map +1 -0
- package/dist/core/agent-session-compaction.test.js +4 -7
- package/dist/core/agent-session-compaction.test.js.map +1 -1
- package/dist/core/agent-session-marker-anchors.test.js +4 -7
- package/dist/core/agent-session-marker-anchors.test.js.map +1 -1
- package/dist/core/agent-session-memory-tail.test.js +4 -7
- package/dist/core/agent-session-memory-tail.test.js.map +1 -1
- package/dist/core/agent-session-queue.test.js +4 -7
- package/dist/core/agent-session-queue.test.js.map +1 -1
- package/dist/core/agent-session.d.ts +21 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +62 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/encode-cwd.d.ts +17 -0
- package/dist/core/encode-cwd.d.ts.map +1 -1
- package/dist/core/encode-cwd.js +20 -7
- package/dist/core/encode-cwd.js.map +1 -1
- package/dist/core/encode-cwd.test.js +20 -1
- package/dist/core/encode-cwd.test.js.map +1 -1
- package/dist/core/ideal-review.d.ts.map +1 -1
- package/dist/core/ideal-review.js +6 -1
- package/dist/core/ideal-review.js.map +1 -1
- package/dist/core/ideal-review.test.js +7 -2
- package/dist/core/ideal-review.test.js.map +1 -1
- package/dist/core/lsp/client.d.ts +50 -0
- package/dist/core/lsp/client.d.ts.map +1 -1
- package/dist/core/lsp/client.js +158 -13
- package/dist/core/lsp/client.js.map +1 -1
- package/dist/core/lsp/jsonrpc.d.ts +38 -1
- package/dist/core/lsp/jsonrpc.d.ts.map +1 -1
- package/dist/core/lsp/jsonrpc.js +13 -1
- package/dist/core/lsp/jsonrpc.js.map +1 -1
- package/dist/core/lsp/manager.d.ts.map +1 -1
- package/dist/core/lsp/manager.js +23 -2
- package/dist/core/lsp/manager.js.map +1 -1
- package/dist/core/lsp/manager.test.js +32 -1
- package/dist/core/lsp/manager.test.js.map +1 -1
- package/dist/core/lsp/test-support.d.ts +18 -0
- package/dist/core/lsp/test-support.d.ts.map +1 -0
- package/dist/core/lsp/test-support.js +27 -0
- package/dist/core/lsp/test-support.js.map +1 -0
- package/dist/core/lsp/uri.test.d.ts +2 -0
- package/dist/core/lsp/uri.test.d.ts.map +1 -0
- package/dist/core/lsp/uri.test.js +56 -0
- package/dist/core/lsp/uri.test.js.map +1 -0
- package/dist/core/lsp/windows.test.d.ts +2 -0
- package/dist/core/lsp/windows.test.d.ts.map +1 -0
- package/dist/core/lsp/windows.test.js +251 -0
- package/dist/core/lsp/windows.test.js.map +1 -0
- package/dist/core/mcp/resolve-stdio.d.ts +35 -0
- package/dist/core/mcp/resolve-stdio.d.ts.map +1 -1
- package/dist/core/mcp/resolve-stdio.js +80 -1
- package/dist/core/mcp/resolve-stdio.js.map +1 -1
- package/dist/core/mcp/resolve-stdio.test.js +99 -4
- package/dist/core/mcp/resolve-stdio.test.js.map +1 -1
- package/dist/core/network-guard.d.ts +47 -0
- package/dist/core/network-guard.d.ts.map +1 -0
- package/dist/core/network-guard.js +208 -0
- package/dist/core/network-guard.js.map +1 -0
- package/dist/core/network-guard.test.d.ts +2 -0
- package/dist/core/network-guard.test.d.ts.map +1 -0
- package/dist/core/network-guard.test.js +72 -0
- package/dist/core/network-guard.test.js.map +1 -0
- package/dist/core/persistent-shell.d.ts +3 -1
- package/dist/core/persistent-shell.d.ts.map +1 -1
- package/dist/core/persistent-shell.js +13 -3
- package/dist/core/persistent-shell.js.map +1 -1
- package/dist/core/process-manager-dev-server-repro.test.js +34 -12
- package/dist/core/process-manager-dev-server-repro.test.js.map +1 -1
- package/dist/core/process-manager.d.ts +1 -1
- package/dist/core/process-manager.d.ts.map +1 -1
- package/dist/core/process-manager.js +23 -13
- package/dist/core/process-manager.js.map +1 -1
- package/dist/core/project-discovery.d.ts +10 -0
- package/dist/core/project-discovery.d.ts.map +1 -1
- package/dist/core/project-discovery.js +42 -8
- package/dist/core/project-discovery.js.map +1 -1
- package/dist/core/project-discovery.test.js +102 -1
- package/dist/core/project-discovery.test.js.map +1 -1
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +55 -36
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/prompt-commands.test.js +49 -2
- package/dist/core/prompt-commands.test.js.map +1 -1
- package/dist/core/radio.test.js +4 -1
- package/dist/core/radio.test.js.map +1 -1
- package/dist/core/session-export.d.ts +98 -0
- package/dist/core/session-export.d.ts.map +1 -0
- package/dist/core/session-export.js +356 -0
- package/dist/core/session-export.js.map +1 -0
- package/dist/core/session-export.test.d.ts +2 -0
- package/dist/core/session-export.test.d.ts.map +1 -0
- package/dist/core/session-export.test.js +353 -0
- package/dist/core/session-export.test.js.map +1 -0
- package/dist/core/session-storage.d.ts.map +1 -1
- package/dist/core/session-storage.js +25 -1
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/session-storage.test.js +4 -1
- package/dist/core/session-storage.test.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +10 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills-routing.test.js +3 -1
- package/dist/core/skills-routing.test.js.map +1 -1
- package/dist/core/slash-commands.d.ts +10 -0
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +16 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/slash-commands.test.d.ts +2 -0
- package/dist/core/slash-commands.test.d.ts.map +1 -0
- package/dist/core/slash-commands.test.js +48 -0
- package/dist/core/slash-commands.test.js.map +1 -0
- package/dist/core/speed-benchmark.test.js +9 -3
- package/dist/core/speed-benchmark.test.js.map +1 -1
- package/dist/core/subagent-store.test.js +14 -2
- package/dist/core/subagent-store.test.js.map +1 -1
- package/dist/core/workspace-guard.d.ts +2 -0
- package/dist/core/workspace-guard.d.ts.map +1 -1
- package/dist/core/workspace-guard.js +4 -1
- package/dist/core/workspace-guard.js.map +1 -1
- package/dist/core/workspace-guard.test.js +21 -0
- package/dist/core/workspace-guard.test.js.map +1 -1
- package/dist/system-prompt.d.ts +11 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +19 -4
- package/dist/system-prompt.js.map +1 -1
- package/dist/system-prompt.test.js +9 -0
- package/dist/system-prompt.test.js.map +1 -1
- package/dist/test-support/fake-home.d.ts +20 -0
- package/dist/test-support/fake-home.d.ts.map +1 -0
- package/dist/test-support/fake-home.js +37 -0
- package/dist/test-support/fake-home.js.map +1 -0
- package/dist/tools/bash.d.ts +2 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +16 -4
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/bash.test.js +122 -7
- package/dist/tools/bash.test.js.map +1 -1
- package/dist/tools/edit-robustness.test.js +3 -1
- package/dist/tools/edit-robustness.test.js.map +1 -1
- package/dist/tools/find.d.ts.map +1 -1
- package/dist/tools/find.js +4 -1
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/grep.d.ts +3 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +135 -11
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/grep.test.js +53 -0
- package/dist/tools/grep.test.js.map +1 -1
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/overflow.test.js +4 -7
- package/dist/tools/overflow.test.js.map +1 -1
- package/dist/tools/path-utils.d.ts +15 -0
- package/dist/tools/path-utils.d.ts.map +1 -1
- package/dist/tools/path-utils.js +17 -0
- package/dist/tools/path-utils.js.map +1 -1
- package/dist/tools/path-utils.test.js +7 -2
- package/dist/tools/path-utils.test.js.map +1 -1
- package/dist/tools/search-code.d.ts.map +1 -1
- package/dist/tools/search-code.js +6 -4
- package/dist/tools/search-code.js.map +1 -1
- package/dist/tools/web-fetch.d.ts +2 -1
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +17 -6
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/web-fetch.test.js +34 -0
- package/dist/tools/web-fetch.test.js.map +1 -1
- package/dist/tools/web-search.d.ts +2 -1
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +21 -5
- package/dist/tools/web-search.js.map +1 -1
- package/dist/utils/process.d.ts +26 -3
- package/dist/utils/process.d.ts.map +1 -1
- package/dist/utils/process.js +68 -10
- package/dist/utils/process.js.map +1 -1
- package/dist/utils/process.test.d.ts +2 -0
- package/dist/utils/process.test.d.ts.map +1 -0
- package/dist/utils/process.test.js +42 -0
- package/dist/utils/process.test.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network egress allowlist.
|
|
3
|
+
*
|
|
4
|
+
* Two layers, with very different strength:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Real enforcement** — the agent's own egress paths (`web-fetch`,
|
|
7
|
+
* `web-search`) check every request URL and every redirect hop against the
|
|
8
|
+
* allowlist. Nothing gets out of those tools to a disallowed host.
|
|
9
|
+
*
|
|
10
|
+
* 2. **Defence in depth (bypassable by design)** — {@link extractCommandHosts}
|
|
11
|
+
* recognises the common network command shapes (`curl`, `wget`, `git`,
|
|
12
|
+
* `ssh`/`scp`, package installs) so `bash` can refuse an obvious egress to a
|
|
13
|
+
* disallowed host. It is *not* a sandbox: `python -c`, a shell variable, a
|
|
14
|
+
* base64'd URL, or any unrecognised tool walks straight past it. It exists
|
|
15
|
+
* to catch accidents, not to contain a hostile model. Real containment needs
|
|
16
|
+
* OS-level enforcement (sandbox-exec, Landlock/seccomp, a netns proxy).
|
|
17
|
+
*
|
|
18
|
+
* Deliberately allow-shaped, not deny-shaped: a command with no recognised host
|
|
19
|
+
* is never blocked, so ordinary work is unaffected.
|
|
20
|
+
*/
|
|
21
|
+
/** Host pattern match: exact, or `*.example.com` matching any subdomain. */
|
|
22
|
+
export function isHostAllowed(host, allow) {
|
|
23
|
+
const target = host.trim().toLowerCase().replace(/\.$/, "");
|
|
24
|
+
if (!target)
|
|
25
|
+
return false;
|
|
26
|
+
for (const raw of allow) {
|
|
27
|
+
const pattern = raw.trim().toLowerCase().replace(/\.$/, "");
|
|
28
|
+
if (!pattern)
|
|
29
|
+
continue;
|
|
30
|
+
if (pattern.startsWith("*.")) {
|
|
31
|
+
const suffix = pattern.slice(1); // ".example.com"
|
|
32
|
+
if (target.endsWith(suffix) && target.length > suffix.length)
|
|
33
|
+
return true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (target === pattern)
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
/** Hostname of a URL-ish token, or undefined when it isn't one. */
|
|
42
|
+
export function hostFromUrl(value) {
|
|
43
|
+
const candidate = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`;
|
|
44
|
+
try {
|
|
45
|
+
const host = new URL(candidate).hostname.toLowerCase();
|
|
46
|
+
return host || undefined;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Default registry hosts for package managers that install from the network. */
|
|
53
|
+
const REGISTRY_HOSTS = {
|
|
54
|
+
npm: "registry.npmjs.org",
|
|
55
|
+
pnpm: "registry.npmjs.org",
|
|
56
|
+
yarn: "registry.yarnpkg.com",
|
|
57
|
+
bun: "registry.npmjs.org",
|
|
58
|
+
pip: "pypi.org",
|
|
59
|
+
pip3: "pypi.org",
|
|
60
|
+
};
|
|
61
|
+
const INSTALL_SUBCOMMANDS = new Set(["install", "i", "add", "ci", "update", "upgrade", "dlx", "x"]);
|
|
62
|
+
const GIT_NETWORK_SUBCOMMANDS = new Set(["clone", "fetch", "pull", "push", "ls-remote"]);
|
|
63
|
+
function stripQuotes(token) {
|
|
64
|
+
const trimmed = token.trim();
|
|
65
|
+
if (trimmed.length >= 2 &&
|
|
66
|
+
((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
67
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'")))) {
|
|
68
|
+
return trimmed.slice(1, -1);
|
|
69
|
+
}
|
|
70
|
+
return trimmed;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Host part of an `scp`/`ssh` target: `[user@]host[:path]`.
|
|
74
|
+
* `requireRemoteMarker` distinguishes `scp file.txt host:/tmp` (only the second
|
|
75
|
+
* token is remote) from `ssh host` (a bare host is the target).
|
|
76
|
+
*/
|
|
77
|
+
function hostFromSshTarget(token, requireRemoteMarker = false) {
|
|
78
|
+
if (requireRemoteMarker && !token.includes("@") && !token.includes(":"))
|
|
79
|
+
return undefined;
|
|
80
|
+
const withoutUser = token.includes("@") ? token.slice(token.lastIndexOf("@") + 1) : token;
|
|
81
|
+
const host = withoutUser.split(":")[0];
|
|
82
|
+
if (!host || host.startsWith("-") || !/^[a-z0-9._-]+$/i.test(host))
|
|
83
|
+
return undefined;
|
|
84
|
+
// A bare local path (`./dir`, `dir`) has no dots-as-domain shape.
|
|
85
|
+
if (!host.includes(".") && host !== "localhost")
|
|
86
|
+
return undefined;
|
|
87
|
+
return host.toLowerCase();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Best-effort extraction of the hosts a shell command would contact.
|
|
91
|
+
* Returns an empty array when nothing recognisable is found — never guesses.
|
|
92
|
+
*/
|
|
93
|
+
export function extractCommandHosts(command) {
|
|
94
|
+
const hosts = new Set();
|
|
95
|
+
// Split on shell separators so `cd x && curl https://…` is inspected too.
|
|
96
|
+
for (const segment of command.split(/(?:&&|\|\||[;|\n])/)) {
|
|
97
|
+
const tokens = segment
|
|
98
|
+
.trim()
|
|
99
|
+
.split(/\s+/)
|
|
100
|
+
.map(stripQuotes)
|
|
101
|
+
.filter((t) => t.length > 0);
|
|
102
|
+
if (tokens.length === 0)
|
|
103
|
+
continue;
|
|
104
|
+
let index = 0;
|
|
105
|
+
if (tokens[index] === "sudo")
|
|
106
|
+
index++;
|
|
107
|
+
const program = (tokens[index] ?? "").split("/").pop() ?? "";
|
|
108
|
+
const args = tokens.slice(index + 1);
|
|
109
|
+
if (program === "curl" || program === "wget" || program === "http" || program === "https") {
|
|
110
|
+
for (const arg of args) {
|
|
111
|
+
if (arg.startsWith("-"))
|
|
112
|
+
continue;
|
|
113
|
+
const host = /^[a-z][a-z0-9+.-]*:\/\//i.test(arg) ? hostFromUrl(arg) : undefined;
|
|
114
|
+
if (host)
|
|
115
|
+
hosts.add(host);
|
|
116
|
+
}
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (program === "git") {
|
|
120
|
+
const sub = args.find((a) => !a.startsWith("-"));
|
|
121
|
+
if (!sub || !GIT_NETWORK_SUBCOMMANDS.has(sub))
|
|
122
|
+
continue;
|
|
123
|
+
for (const arg of args.slice(args.indexOf(sub) + 1)) {
|
|
124
|
+
if (arg.startsWith("-"))
|
|
125
|
+
continue;
|
|
126
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(arg)) {
|
|
127
|
+
const host = hostFromUrl(arg);
|
|
128
|
+
if (host)
|
|
129
|
+
hosts.add(host);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
// scp-style remote: git@github.com:owner/repo.git
|
|
133
|
+
if (arg.includes("@") && arg.includes(":")) {
|
|
134
|
+
const host = hostFromSshTarget(arg);
|
|
135
|
+
if (host)
|
|
136
|
+
hosts.add(host);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (program === "ssh" || program === "scp" || program === "sftp" || program === "rsync") {
|
|
142
|
+
// Only `ssh`'s first bare operand is a host; file-copy tools mark the
|
|
143
|
+
// remote side with `user@` or `host:path`.
|
|
144
|
+
const requireRemoteMarker = program !== "ssh";
|
|
145
|
+
for (const arg of args) {
|
|
146
|
+
if (arg.startsWith("-"))
|
|
147
|
+
continue;
|
|
148
|
+
const host = hostFromSshTarget(arg, requireRemoteMarker);
|
|
149
|
+
if (host) {
|
|
150
|
+
hosts.add(host);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const registry = REGISTRY_HOSTS[program];
|
|
157
|
+
if (registry) {
|
|
158
|
+
const sub = args.find((a) => !a.startsWith("-"));
|
|
159
|
+
if (sub && INSTALL_SUBCOMMANDS.has(sub))
|
|
160
|
+
hosts.add(registry);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return [...hosts];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check a shell command against the allowlist.
|
|
168
|
+
* @returns an error string when a recognised host is disallowed, else null.
|
|
169
|
+
*/
|
|
170
|
+
export function checkCommandNetwork(command, mode, allow) {
|
|
171
|
+
if (mode !== "allowlist")
|
|
172
|
+
return null;
|
|
173
|
+
const blocked = extractCommandHosts(command).filter((host) => !isHostAllowed(host, allow));
|
|
174
|
+
if (blocked.length === 0)
|
|
175
|
+
return null;
|
|
176
|
+
const allowed = allow.length > 0 ? allow.join(", ") : "(none)";
|
|
177
|
+
return (`Blocked by the network allowlist: ${blocked.join(", ")}. ` +
|
|
178
|
+
`Allowed hosts: ${allowed}. Ask the user to add the host to the networkAllow setting, ` +
|
|
179
|
+
`or accomplish the task without network access.`);
|
|
180
|
+
}
|
|
181
|
+
/** Check a URL the agent is about to fetch (or a redirect target). */
|
|
182
|
+
export function checkUrlNetwork(url, mode, allow) {
|
|
183
|
+
if (mode !== "allowlist")
|
|
184
|
+
return null;
|
|
185
|
+
const host = hostFromUrl(url);
|
|
186
|
+
if (!host)
|
|
187
|
+
return `Blocked by the network allowlist: ${url} has no resolvable host.`;
|
|
188
|
+
if (isHostAllowed(host, allow))
|
|
189
|
+
return null;
|
|
190
|
+
const allowed = allow.length > 0 ? allow.join(", ") : "(none)";
|
|
191
|
+
return (`Blocked by the network allowlist: ${host}. Allowed hosts: ${allowed}. ` +
|
|
192
|
+
`Ask the user to add the host to the networkAllow setting.`);
|
|
193
|
+
}
|
|
194
|
+
/** Check a URL against a possibly-absent policy. */
|
|
195
|
+
export function checkUrlPolicy(url, getPolicy) {
|
|
196
|
+
const policy = getPolicy?.();
|
|
197
|
+
if (!policy)
|
|
198
|
+
return null;
|
|
199
|
+
return checkUrlNetwork(url, policy.mode, policy.allow);
|
|
200
|
+
}
|
|
201
|
+
/** Check a shell command against a possibly-absent policy. */
|
|
202
|
+
export function checkCommandPolicy(command, getPolicy) {
|
|
203
|
+
const policy = getPolicy?.();
|
|
204
|
+
if (!policy)
|
|
205
|
+
return null;
|
|
206
|
+
return checkCommandNetwork(command, policy.mode, policy.allow);
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=network-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-guard.js","sourceRoot":"","sources":["../../src/core/network-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-guard.test.d.ts","sourceRoot":"","sources":["../../src/core/network-guard.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { checkCommandNetwork, checkUrlNetwork, extractCommandHosts, isHostAllowed, } from "./network-guard.js";
|
|
3
|
+
describe("isHostAllowed", () => {
|
|
4
|
+
it("matches exact hosts case-insensitively", () => {
|
|
5
|
+
expect(isHostAllowed("GitHub.com", ["github.com"])).toBe(true);
|
|
6
|
+
expect(isHostAllowed("github.com", ["GITHUB.COM"])).toBe(true);
|
|
7
|
+
expect(isHostAllowed("gitlab.com", ["github.com"])).toBe(false);
|
|
8
|
+
});
|
|
9
|
+
it("matches a leading *. wildcard against subdomains only", () => {
|
|
10
|
+
expect(isHostAllowed("api.github.com", ["*.github.com"])).toBe(true);
|
|
11
|
+
expect(isHostAllowed("a.b.github.com", ["*.github.com"])).toBe(true);
|
|
12
|
+
expect(isHostAllowed("github.com", ["*.github.com"])).toBe(false);
|
|
13
|
+
expect(isHostAllowed("evilgithub.com", ["*.github.com"])).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
it("rejects everything against an empty allowlist", () => {
|
|
16
|
+
expect(isHostAllowed("github.com", [])).toBe(false);
|
|
17
|
+
expect(isHostAllowed("", ["github.com"])).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe("extractCommandHosts", () => {
|
|
21
|
+
it.each([
|
|
22
|
+
["curl -sSL https://example.com/x.sh", ["example.com"]],
|
|
23
|
+
["wget https://files.example.org/a.tar.gz -O a.tgz", ["files.example.org"]],
|
|
24
|
+
["git clone https://github.com/owner/repo.git", ["github.com"]],
|
|
25
|
+
["git clone git@gitlab.com:owner/repo.git", ["gitlab.com"]],
|
|
26
|
+
["git push origin main", []],
|
|
27
|
+
["ssh deploy@prod.example.com 'uptime'", ["prod.example.com"]],
|
|
28
|
+
["scp file.txt deploy@prod.example.com:/tmp", ["prod.example.com"]],
|
|
29
|
+
["npm install left-pad", ["registry.npmjs.org"]],
|
|
30
|
+
["pnpm add -D vitest", ["registry.npmjs.org"]],
|
|
31
|
+
["yarn add react", ["registry.yarnpkg.com"]],
|
|
32
|
+
["pip install requests", ["pypi.org"]],
|
|
33
|
+
["cd repo && curl https://api.example.com/v1", ["api.example.com"]],
|
|
34
|
+
])("extracts hosts from %s", (command, expected) => {
|
|
35
|
+
expect(extractCommandHosts(command).sort()).toEqual(expected.sort());
|
|
36
|
+
});
|
|
37
|
+
it.each([
|
|
38
|
+
"ls -la",
|
|
39
|
+
"npm run build",
|
|
40
|
+
"git status",
|
|
41
|
+
"git commit -m 'curl https://example.com'",
|
|
42
|
+
"echo hello",
|
|
43
|
+
"python3 script.py",
|
|
44
|
+
"rm -rf node_modules",
|
|
45
|
+
])("finds no host in %s (no false blocks)", (command) => {
|
|
46
|
+
expect(extractCommandHosts(command)).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("checkCommandNetwork", () => {
|
|
50
|
+
it("is a total no-op when the mode is off", () => {
|
|
51
|
+
expect(checkCommandNetwork("curl https://evil.example", "off", [])).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
it("blocks a disallowed host and names the setting", () => {
|
|
54
|
+
const error = checkCommandNetwork("curl https://evil.example/x", "allowlist", ["github.com"]);
|
|
55
|
+
expect(error).toContain("evil.example");
|
|
56
|
+
expect(error).toContain("networkAllow");
|
|
57
|
+
});
|
|
58
|
+
it("allows an allow-listed host and unrecognised commands", () => {
|
|
59
|
+
expect(checkCommandNetwork("git clone https://github.com/o/r.git", "allowlist", ["github.com"])).toBeNull();
|
|
60
|
+
expect(checkCommandNetwork("make build", "allowlist", [])).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("checkUrlNetwork", () => {
|
|
64
|
+
it("is a total no-op when the mode is off", () => {
|
|
65
|
+
expect(checkUrlNetwork("https://evil.example", "off", [])).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
it("enforces the allowlist on a URL", () => {
|
|
68
|
+
expect(checkUrlNetwork("https://api.github.com/x", "allowlist", ["*.github.com"])).toBeNull();
|
|
69
|
+
expect(checkUrlNetwork("https://evil.example/x", "allowlist", ["*.github.com"])).toContain("evil.example");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=network-guard.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-guard.test.js","sourceRoot":"","sources":["../../src/core/network-guard.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ResolveShellOpts } from "./shell.js";
|
|
1
2
|
export interface PersistentRunResult {
|
|
2
3
|
exitCode: number | "TIMEOUT";
|
|
3
4
|
output: string;
|
|
@@ -6,10 +7,11 @@ export declare class PersistentShell {
|
|
|
6
7
|
private readonly cwd;
|
|
7
8
|
private readonly env;
|
|
8
9
|
private readonly maxOutputBytes;
|
|
10
|
+
private readonly shellOpts?;
|
|
9
11
|
private child;
|
|
10
12
|
private buffer;
|
|
11
13
|
private busy;
|
|
12
|
-
constructor(cwd: string, env: NodeJS.ProcessEnv, maxOutputBytes: number);
|
|
14
|
+
constructor(cwd: string, env: NodeJS.ProcessEnv, maxOutputBytes: number, shellOpts?: ResolveShellOpts | undefined);
|
|
13
15
|
/** True while a previous persistent command is still running. */
|
|
14
16
|
get isBusy(): boolean;
|
|
15
17
|
private ensureChild;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistent-shell.d.ts","sourceRoot":"","sources":["../../src/core/persistent-shell.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"persistent-shell.d.ts","sourceRoot":"","sources":["../../src/core/persistent-shell.ts"],"names":[],"mappings":"AAaA,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGjE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,eAAe;IAMxB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAR7B,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,IAAI,CAAS;gBAGF,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,cAAc,EAAE,MAAM,EACtB,SAAS,CAAC,EAAE,gBAAgB,YAAA;IAG/C,iEAAiE;IACjE,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,OAAO,CAAC,WAAW;IAyBnB;;;;;;OAMG;IACH,GAAG,CACD,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,mBAAmB,CAAC;IAgG/B,iEAAiE;IACjE,IAAI,IAAI,IAAI;CAKb"}
|
|
@@ -11,18 +11,21 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { resolveShell } from "./shell.js";
|
|
14
15
|
import { killProcessTree } from "../utils/process.js";
|
|
15
16
|
export class PersistentShell {
|
|
16
17
|
cwd;
|
|
17
18
|
env;
|
|
18
19
|
maxOutputBytes;
|
|
20
|
+
shellOpts;
|
|
19
21
|
child = null;
|
|
20
22
|
buffer = "";
|
|
21
23
|
busy = false;
|
|
22
|
-
constructor(cwd, env, maxOutputBytes) {
|
|
24
|
+
constructor(cwd, env, maxOutputBytes, shellOpts) {
|
|
23
25
|
this.cwd = cwd;
|
|
24
26
|
this.env = env;
|
|
25
27
|
this.maxOutputBytes = maxOutputBytes;
|
|
28
|
+
this.shellOpts = shellOpts;
|
|
26
29
|
}
|
|
27
30
|
/** True while a previous persistent command is still running. */
|
|
28
31
|
get isBusy() {
|
|
@@ -33,11 +36,18 @@ export class PersistentShell {
|
|
|
33
36
|
return this.child;
|
|
34
37
|
}
|
|
35
38
|
// Fresh session: no rc files so startup is fast and deterministic.
|
|
36
|
-
|
|
39
|
+
//
|
|
40
|
+
// Spawn the SAME bash the one-shot path resolved, not a bare `bash`: on
|
|
41
|
+
// Windows the caller only reaches persist mode when Git Bash was found,
|
|
42
|
+
// but Git for Windows puts `cmd\` on PATH and `bash.exe` in `bin\` — so a
|
|
43
|
+
// bare `bash` spawn is ENOENT and every persist-mode command failed.
|
|
44
|
+
const child = spawn(resolveShell("", this.shellOpts).file, ["--norc", "--noprofile"], {
|
|
37
45
|
cwd: this.cwd,
|
|
38
46
|
stdio: ["pipe", "pipe", "pipe"],
|
|
39
47
|
env: this.env,
|
|
40
|
-
detached
|
|
48
|
+
// Windows has no process groups; `detached` there only orphans the shell
|
|
49
|
+
// past a parent crash instead of grouping it for a clean tree-kill.
|
|
50
|
+
detached: process.platform !== "win32",
|
|
41
51
|
});
|
|
42
52
|
// Don't let a lingering session shell keep the parent process alive.
|
|
43
53
|
child.unref();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistent-shell.js","sourceRoot":"","sources":["../../src/core/persistent-shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAOtD,MAAM,OAAO,eAAe;IAMP;IACA;IACA;
|
|
1
|
+
{"version":3,"file":"persistent-shell.js","sourceRoot":"","sources":["../../src/core/persistent-shell.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,10 @@ import path from "node:path";
|
|
|
5
5
|
import { ProcessManager } from "./process-manager.js";
|
|
6
6
|
async function waitForOutput(manager, id, predicate) {
|
|
7
7
|
let combined = "";
|
|
8
|
-
|
|
8
|
+
// 200 x 100ms = 20s. The old 5s budget was enough on a developer machine but
|
|
9
|
+
// not on a loaded Windows CI runner, where spawning node and binding a port
|
|
10
|
+
// is markedly slower — the test failed there on timing, not behavior.
|
|
11
|
+
for (let i = 0; i < 200; i += 1) {
|
|
9
12
|
const result = await manager.readOutput(id);
|
|
10
13
|
combined += result.output;
|
|
11
14
|
if (predicate(combined))
|
|
@@ -79,8 +82,12 @@ describe("ProcessManager dev-server lifecycle repro", () => {
|
|
|
79
82
|
process.env.GG_TEST_SHOULD_NOT_LEAK = oldSecret;
|
|
80
83
|
}
|
|
81
84
|
});
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
// Windows has no process groups: signalling the wrapper leaves its whole
|
|
86
|
+
// descendant tree (the dev server everyone actually wants dead) running. So
|
|
87
|
+
// stop() force-kills the PID tree with taskkill FIRST, and reports honestly
|
|
88
|
+
// when the process is still alive after the 5s grace window.
|
|
89
|
+
it("force-kills the PID tree with taskkill on Windows", async () => {
|
|
90
|
+
const taskkill = vi.fn().mockReturnValue({ status: 1 });
|
|
84
91
|
manager = new ProcessManager({
|
|
85
92
|
platform: "win32",
|
|
86
93
|
kill: vi.fn(() => {
|
|
@@ -92,11 +99,10 @@ describe("ProcessManager dev-server lifecycle repro", () => {
|
|
|
92
99
|
const started = await manager.start(`${JSON.stringify(process.execPath)} -e "setInterval(()=>{},1000)"`, tmpDir);
|
|
93
100
|
try {
|
|
94
101
|
const stopped = await manager.stop(started.id);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
expect(
|
|
98
|
-
|
|
99
|
-
});
|
|
102
|
+
// The mocked taskkill never really kills the child, so the 5s window
|
|
103
|
+
// elapses and the user is told the truth instead of "stopped".
|
|
104
|
+
expect(stopped).toContain("Failed to stop process");
|
|
105
|
+
expect(taskkill).toHaveBeenCalledWith(expect.stringMatching(/taskkill\.exe$/), ["/PID", String(started.pid), "/T", "/F"], expect.objectContaining({ stdio: "ignore", windowsHide: true }));
|
|
100
106
|
}
|
|
101
107
|
finally {
|
|
102
108
|
// This test deliberately mocks `kill` and `spawnSync`, so neither the
|
|
@@ -105,7 +111,8 @@ describe("ProcessManager dev-server lifecycle repro", () => {
|
|
|
105
111
|
// this suite orphans a live `node -e setInterval` process forever.
|
|
106
112
|
killRealProcessTree(started.pid);
|
|
107
113
|
}
|
|
108
|
-
|
|
114
|
+
// stop() waits out its full 5s grace window before reporting failure.
|
|
115
|
+
}, 45_000);
|
|
109
116
|
it("starts, reads, and stops a long-running Node HTTP server through the worker background path", async () => {
|
|
110
117
|
manager = new ProcessManager();
|
|
111
118
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "gg-dev-server-repro-"));
|
|
@@ -122,7 +129,11 @@ describe("ProcessManager dev-server lifecycle repro", () => {
|
|
|
122
129
|
` clearInterval(interval);\n` +
|
|
123
130
|
` server.close(() => process.exit(0));\n` +
|
|
124
131
|
`});\n`);
|
|
125
|
-
|
|
132
|
+
// Both paths MUST be quoted. Unquoted, bash eats the backslashes in a
|
|
133
|
+
// Windows path: `C:\hostedtoolcache\…\node.exe` reached the shell as
|
|
134
|
+
// `C:hostedtoolcache…node.exe` and failed with "command not found". Every
|
|
135
|
+
// other manager.start call in this file already quotes; this one did not.
|
|
136
|
+
const started = await manager.start(`${JSON.stringify(process.execPath)} ${JSON.stringify(fixture)}`, tmpDir);
|
|
126
137
|
expect(started.pid).toBeGreaterThan(0);
|
|
127
138
|
expect(started.logFile).toMatch(/\.log$/);
|
|
128
139
|
const initial = await waitForOutput(manager, started.id, (output) => output.includes("DEV_SERVER_READY"));
|
|
@@ -136,8 +147,19 @@ describe("ProcessManager dev-server lifecycle repro", () => {
|
|
|
136
147
|
const final = await manager.readOutput(started.id, true);
|
|
137
148
|
expect(final.isRunning).toBe(false);
|
|
138
149
|
expect(final.exitCode).not.toBeNull();
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
if (process.platform === "win32") {
|
|
151
|
+
// Windows has no SIGTERM. `stop()` force-kills the PID tree with taskkill
|
|
152
|
+
// /F precisely because there is no process group and no graceful signal
|
|
153
|
+
// to send, so a SIGTERM handler CANNOT run and the server gets no chance
|
|
154
|
+
// to clean up. That is a real, unavoidable platform difference — what
|
|
155
|
+
// matters (asserted above) is that the process and its children are
|
|
156
|
+
// genuinely dead, which is the failure mode users actually hit.
|
|
157
|
+
expect(final.output).toContain("DEV_SERVER_READY");
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
expect(final.output).toContain("DEV_SERVER_SIGTERM");
|
|
161
|
+
}
|
|
162
|
+
}, 45_000);
|
|
141
163
|
const posixIt = process.platform === "win32" ? it.skip : it;
|
|
142
164
|
posixIt("kills the whole detached process group on POSIX/WSL shutdown", async () => {
|
|
143
165
|
manager = new ProcessManager();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-manager-dev-server-repro.test.js","sourceRoot":"","sources":["../../src/core/process-manager-dev-server-repro.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,KAAK,UAAU,aAAa,CAC1B,OAAuB,EACvB,EAAU,EACV,SAAsC;IAEtC,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,
|
|
1
|
+
{"version":3,"file":"process-manager-dev-server-repro.test.js","sourceRoot":"","sources":["../../src/core/process-manager-dev-server-repro.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../src/core/process-manager.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"process-manager.d.ts","sourceRoot":"","sources":["../../src/core/process-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAWpD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,IAAI,CAAC,EAAE,OAAO,OAAO,CAAC,IAAI,CAAC;IAC3B,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;CAC9B;AAWD,qBAAa,cAAc;IAIb,OAAO,CAAC,QAAQ,CAAC,GAAG;IAHhC,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,QAAQ,CAAmC;gBAEtB,GAAG,GAAE,iBAAsB;IAElD,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAiDzD,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgC5E;;;;;OAKG;IACG,SAAS,CACb,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,OAAO,CAAA;KAAO,GAC5C,OAAO,CAAC,MAAM,CAAC;IAoCZ,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8CvC,IAAI,IAAI,iBAAiB,EAAE;IAW3B,WAAW,IAAI,IAAI;CASpB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import fsp from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
@@ -9,13 +9,12 @@ import { getSafeToolEnv } from "../tools/safe-env.js";
|
|
|
9
9
|
import { resolveShell } from "./shell.js";
|
|
10
10
|
const BG_DIR = path.join(os.homedir(), ".gg", "bg");
|
|
11
11
|
function stopProcessTree(pid, ops = {}) {
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
stdio: "ignore",
|
|
15
|
-
});
|
|
12
|
+
if (ops.killProcessTree) {
|
|
13
|
+
ops.killProcessTree(pid);
|
|
16
14
|
return;
|
|
17
15
|
}
|
|
18
|
-
|
|
16
|
+
// killProcessTree is itself platform-aware (taskkill /T /F on Windows).
|
|
17
|
+
killProcessTree(pid, { platform: ops.platform, kill: ops.kill, spawnSync: ops.spawnSync });
|
|
19
18
|
}
|
|
20
19
|
export class ProcessManager {
|
|
21
20
|
ops;
|
|
@@ -139,19 +138,27 @@ export class ProcessManager {
|
|
|
139
138
|
if (!child || proc.exitCode !== null) {
|
|
140
139
|
return `Process ${id} already exited (code ${proc.exitCode})`;
|
|
141
140
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
const isWindows = (this.ops.platform ?? process.platform) === "win32";
|
|
142
|
+
if (isWindows) {
|
|
143
|
+
// Windows has no process groups and no real SIGTERM: signalling the
|
|
144
|
+
// wrapper only orphans its descendants. Force-kill the PID tree up front.
|
|
145
|
+
stopProcessTree(proc.pid, this.ops);
|
|
145
146
|
}
|
|
146
|
-
|
|
147
|
+
else {
|
|
148
|
+
// SIGTERM the group first so POSIX children get a chance to clean up.
|
|
147
149
|
try {
|
|
148
|
-
(this.ops.kill ?? process.kill)(proc.pid, "SIGTERM");
|
|
150
|
+
(this.ops.kill ?? process.kill)(-proc.pid, "SIGTERM");
|
|
149
151
|
}
|
|
150
152
|
catch {
|
|
151
|
-
|
|
153
|
+
try {
|
|
154
|
+
(this.ops.kill ?? process.kill)(proc.pid, "SIGTERM");
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return `Process ${id} already exited`;
|
|
158
|
+
}
|
|
152
159
|
}
|
|
153
160
|
}
|
|
154
|
-
// Wait up to 5s, then
|
|
161
|
+
// Wait up to 5s, then hard-kill a surviving tree.
|
|
155
162
|
const exited = await new Promise((resolve) => {
|
|
156
163
|
const timeout = setTimeout(() => resolve(false), 5000);
|
|
157
164
|
child.on("close", () => {
|
|
@@ -160,6 +167,9 @@ export class ProcessManager {
|
|
|
160
167
|
});
|
|
161
168
|
});
|
|
162
169
|
if (!exited) {
|
|
170
|
+
if (isWindows) {
|
|
171
|
+
return `Failed to stop process ${id}: it did not exit within 5 seconds and may still be running.`;
|
|
172
|
+
}
|
|
163
173
|
stopProcessTree(proc.pid, this.ops);
|
|
164
174
|
}
|
|
165
175
|
return `Process ${id} stopped`;
|