@kenkaiiii/ggcoder 5.23.3 → 5.25.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.
Files changed (90) hide show
  1. package/dist/app-sidecar.js +318 -12
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/agent-session-add-dir.test.d.ts +2 -0
  4. package/dist/core/agent-session-add-dir.test.d.ts.map +1 -0
  5. package/dist/core/agent-session-add-dir.test.js +110 -0
  6. package/dist/core/agent-session-add-dir.test.js.map +1 -0
  7. package/dist/core/agent-session.d.ts +29 -0
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +74 -4
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/local-endpoint-store.d.ts +50 -0
  12. package/dist/core/local-endpoint-store.d.ts.map +1 -0
  13. package/dist/core/local-endpoint-store.js +201 -0
  14. package/dist/core/local-endpoint-store.js.map +1 -0
  15. package/dist/core/local-endpoint-store.test.d.ts +2 -0
  16. package/dist/core/local-endpoint-store.test.d.ts.map +1 -0
  17. package/dist/core/local-endpoint-store.test.js +131 -0
  18. package/dist/core/local-endpoint-store.test.js.map +1 -0
  19. package/dist/core/network-guard.d.ts +47 -0
  20. package/dist/core/network-guard.d.ts.map +1 -0
  21. package/dist/core/network-guard.js +208 -0
  22. package/dist/core/network-guard.js.map +1 -0
  23. package/dist/core/network-guard.test.d.ts +2 -0
  24. package/dist/core/network-guard.test.d.ts.map +1 -0
  25. package/dist/core/network-guard.test.js +72 -0
  26. package/dist/core/network-guard.test.js.map +1 -0
  27. package/dist/core/prompt-commands.d.ts.map +1 -1
  28. package/dist/core/prompt-commands.js +55 -36
  29. package/dist/core/prompt-commands.js.map +1 -1
  30. package/dist/core/prompt-commands.test.js +49 -2
  31. package/dist/core/prompt-commands.test.js.map +1 -1
  32. package/dist/core/session-export.d.ts +98 -0
  33. package/dist/core/session-export.d.ts.map +1 -0
  34. package/dist/core/session-export.js +356 -0
  35. package/dist/core/session-export.js.map +1 -0
  36. package/dist/core/session-export.test.d.ts +2 -0
  37. package/dist/core/session-export.test.d.ts.map +1 -0
  38. package/dist/core/session-export.test.js +353 -0
  39. package/dist/core/session-export.test.js.map +1 -0
  40. package/dist/core/settings-manager.d.ts +5 -0
  41. package/dist/core/settings-manager.d.ts.map +1 -1
  42. package/dist/core/settings-manager.js +10 -0
  43. package/dist/core/settings-manager.js.map +1 -1
  44. package/dist/core/slash-commands.d.ts +18 -0
  45. package/dist/core/slash-commands.d.ts.map +1 -1
  46. package/dist/core/slash-commands.js +32 -0
  47. package/dist/core/slash-commands.js.map +1 -1
  48. package/dist/core/slash-commands.test.d.ts +2 -0
  49. package/dist/core/slash-commands.test.d.ts.map +1 -0
  50. package/dist/core/slash-commands.test.js +59 -0
  51. package/dist/core/slash-commands.test.js.map +1 -0
  52. package/dist/core/workspace-guard.d.ts +2 -0
  53. package/dist/core/workspace-guard.d.ts.map +1 -1
  54. package/dist/core/workspace-guard.js +4 -1
  55. package/dist/core/workspace-guard.js.map +1 -1
  56. package/dist/core/workspace-guard.test.js +21 -0
  57. package/dist/core/workspace-guard.test.js.map +1 -1
  58. package/dist/system-prompt.d.ts +11 -1
  59. package/dist/system-prompt.d.ts.map +1 -1
  60. package/dist/system-prompt.js +19 -4
  61. package/dist/system-prompt.js.map +1 -1
  62. package/dist/system-prompt.test.js +9 -0
  63. package/dist/system-prompt.test.js.map +1 -1
  64. package/dist/tools/bash.d.ts +2 -1
  65. package/dist/tools/bash.d.ts.map +1 -1
  66. package/dist/tools/bash.js +9 -1
  67. package/dist/tools/bash.js.map +1 -1
  68. package/dist/tools/bash.test.js +19 -0
  69. package/dist/tools/bash.test.js.map +1 -1
  70. package/dist/tools/grep.d.ts +3 -1
  71. package/dist/tools/grep.d.ts.map +1 -1
  72. package/dist/tools/grep.js +132 -10
  73. package/dist/tools/grep.js.map +1 -1
  74. package/dist/tools/grep.test.js +53 -0
  75. package/dist/tools/grep.test.js.map +1 -1
  76. package/dist/tools/index.d.ts +6 -0
  77. package/dist/tools/index.d.ts.map +1 -1
  78. package/dist/tools/index.js +3 -3
  79. package/dist/tools/index.js.map +1 -1
  80. package/dist/tools/web-fetch.d.ts +2 -1
  81. package/dist/tools/web-fetch.d.ts.map +1 -1
  82. package/dist/tools/web-fetch.js +17 -6
  83. package/dist/tools/web-fetch.js.map +1 -1
  84. package/dist/tools/web-fetch.test.js +34 -0
  85. package/dist/tools/web-fetch.test.js.map +1 -1
  86. package/dist/tools/web-search.d.ts +2 -1
  87. package/dist/tools/web-search.d.ts.map +1 -1
  88. package/dist/tools/web-search.js +21 -5
  89. package/dist/tools/web-search.js.map +1 -1
  90. package/package.json +4 -4
@@ -0,0 +1,131 @@
1
+ import fs from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import { AuthStorage } from "@kenkaiiii/gg-core";
6
+ import { LocalEndpointError, addCustomEndpoint, listAllEndpoints, listCustomEndpoints, normalizeLocalBaseUrl, removeCustomEndpoint, syncEndpointCredentials, } from "./local-endpoint-store.js";
7
+ let dir;
8
+ let opts;
9
+ let auth;
10
+ // Both file paths are injected rather than redirected via $HOME: os.homedir()
11
+ // reads USERPROFILE on Windows, so a HOME override silently writes to the real
12
+ // ~/.gg there — which leaks state between tests and pollutes the user's files.
13
+ beforeEach(async () => {
14
+ dir = await fs.mkdtemp(path.join(os.tmpdir(), "gg-local-endpoints-"));
15
+ auth = new AuthStorage(path.join(dir, "auth.json"));
16
+ opts = { settingsFile: path.join(dir, "gg-app.json"), auth };
17
+ });
18
+ afterEach(async () => {
19
+ await fs.rm(dir, { recursive: true, force: true });
20
+ });
21
+ async function settings() {
22
+ return JSON.parse(await fs.readFile(opts.settingsFile, "utf-8"));
23
+ }
24
+ describe("normalizeLocalBaseUrl", () => {
25
+ it("accepts the shapes users actually paste", () => {
26
+ expect(normalizeLocalBaseUrl("127.0.0.1:1234")).toBe("http://127.0.0.1:1234/v1");
27
+ expect(normalizeLocalBaseUrl("http://localhost:8000")).toBe("http://localhost:8000/v1");
28
+ expect(normalizeLocalBaseUrl("http://localhost:8000/v1")).toBe("http://localhost:8000/v1");
29
+ expect(normalizeLocalBaseUrl(" http://localhost:8000/v1/ ")).toBe("http://localhost:8000/v1");
30
+ expect(normalizeLocalBaseUrl("http://box.lan:8080/v1/chat/completions")).toBe("http://box.lan:8080/v1");
31
+ expect(normalizeLocalBaseUrl("http://box.lan:8080/v1/models")).toBe("http://box.lan:8080/v1");
32
+ // A gateway on a sub-path keeps the path.
33
+ expect(normalizeLocalBaseUrl("https://gw.example.com/openai")).toBe("https://gw.example.com/openai/v1");
34
+ });
35
+ it("rejects empty, malformed, and non-http URLs", () => {
36
+ expect(() => normalizeLocalBaseUrl(" ")).toThrow(LocalEndpointError);
37
+ expect(() => normalizeLocalBaseUrl("http://")).toThrow(LocalEndpointError);
38
+ expect(() => normalizeLocalBaseUrl("ftp://host/v1")).toThrow(LocalEndpointError);
39
+ });
40
+ });
41
+ describe("custom endpoints", () => {
42
+ it("adds an endpoint, persists it, and writes its auth credential", async () => {
43
+ const endpoint = await addCustomEndpoint({ label: "Workstation", baseUrl: "192.168.1.4:8000", apiKey: "secret" }, opts);
44
+ expect(endpoint).toMatchObject({
45
+ id: "custom-192-168-1-4-8000",
46
+ label: "Workstation",
47
+ baseUrl: "http://192.168.1.4:8000/v1",
48
+ kind: "custom",
49
+ custom: true,
50
+ apiKey: "secret",
51
+ });
52
+ expect(await listCustomEndpoints(opts)).toEqual([endpoint]);
53
+ const creds = await auth.getCredentials("local:custom-192-168-1-4-8000");
54
+ expect(creds).toMatchObject({ accessToken: "secret", baseUrl: "http://192.168.1.4:8000/v1" });
55
+ expect(creds.expiresAt).toBeGreaterThan(Date.now());
56
+ expect(await auth.hasProviderAuth("local")).toBe(true);
57
+ });
58
+ it("defaults the label to the host and the key to a placeholder", async () => {
59
+ await addCustomEndpoint({ baseUrl: "http://localhost:9999" }, opts);
60
+ const stored = (await listCustomEndpoints(opts))[0];
61
+ expect(stored.label).toBe("localhost:9999");
62
+ expect(stored.apiKey).toBeUndefined();
63
+ expect(await auth.getCredentials("local:custom-localhost-9999")).toMatchObject({
64
+ accessToken: "local",
65
+ });
66
+ });
67
+ it("updates in place when the same host is re-added", async () => {
68
+ await addCustomEndpoint({ baseUrl: "http://localhost:9999", apiKey: "wrong" }, opts);
69
+ await addCustomEndpoint({ label: "Fixed", baseUrl: "http://localhost:9999/v1", apiKey: "right" }, opts);
70
+ const stored = await listCustomEndpoints(opts);
71
+ expect(stored).toHaveLength(1);
72
+ expect(stored[0]).toMatchObject({ label: "Fixed", apiKey: "right" });
73
+ expect(await auth.getCredentials("local:custom-localhost-9999")).toMatchObject({
74
+ accessToken: "right",
75
+ });
76
+ });
77
+ it("refuses a URL already covered by a built-in endpoint", async () => {
78
+ await expect(addCustomEndpoint({ baseUrl: "http://127.0.0.1:11434/v1" }, opts)).rejects.toThrow(/already probed automatically as Ollama/);
79
+ });
80
+ it("removes an endpoint and its credential", async () => {
81
+ await addCustomEndpoint({ baseUrl: "http://localhost:9999" }, opts);
82
+ await removeCustomEndpoint("custom-localhost-9999", opts);
83
+ expect(await listCustomEndpoints(opts)).toEqual([]);
84
+ expect(await auth.getCredentials("local:custom-localhost-9999")).toBeUndefined();
85
+ expect(await auth.hasProviderAuth("local")).toBe(false);
86
+ });
87
+ it("refuses to remove a built-in or unknown endpoint", async () => {
88
+ await expect(removeCustomEndpoint("ollama", opts)).rejects.toThrow(/built-in/);
89
+ await expect(removeCustomEndpoint("custom-nope", opts)).rejects.toThrow(/Unknown local/);
90
+ });
91
+ it("preserves unrelated gg-app.json keys", async () => {
92
+ await fs.writeFile(opts.settingsFile, JSON.stringify({ projectsRoot: "/tmp/projects", autopilot: { "/a": true } }), "utf-8");
93
+ await addCustomEndpoint({ baseUrl: "http://localhost:9999" }, opts);
94
+ expect(await settings()).toMatchObject({
95
+ projectsRoot: "/tmp/projects",
96
+ autopilot: { "/a": true },
97
+ });
98
+ await removeCustomEndpoint("custom-localhost-9999", opts);
99
+ expect(await settings()).toMatchObject({ projectsRoot: "/tmp/projects" });
100
+ });
101
+ it("ignores a corrupt settings file instead of throwing", async () => {
102
+ await fs.writeFile(opts.settingsFile, "{not json", "utf-8");
103
+ expect(await listCustomEndpoints(opts)).toEqual([]);
104
+ });
105
+ it("lists built-in endpoints before custom ones", async () => {
106
+ await addCustomEndpoint({ baseUrl: "http://localhost:9999" }, opts);
107
+ expect((await listAllEndpoints(opts)).map((e) => e.id)).toEqual([
108
+ "ollama",
109
+ "lmstudio",
110
+ "llamacpp",
111
+ "vllm",
112
+ "custom-localhost-9999",
113
+ ]);
114
+ });
115
+ });
116
+ describe("syncEndpointCredentials", () => {
117
+ it("writes a baseUrl-carrying credential for every endpoint", async () => {
118
+ await syncEndpointCredentials(await listAllEndpoints(opts), opts);
119
+ expect((await auth.listLocalEndpointIds()).sort()).toEqual([
120
+ "llamacpp",
121
+ "lmstudio",
122
+ "ollama",
123
+ "vllm",
124
+ ]);
125
+ expect(await auth.getCredentials("local:ollama")).toMatchObject({
126
+ baseUrl: "http://127.0.0.1:11434/v1",
127
+ accessToken: "local",
128
+ });
129
+ });
130
+ });
131
+ //# sourceMappingURL=local-endpoint-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-endpoint-store.test.js","sourceRoot":"","sources":["../../src/core/local-endpoint-store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
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 declare function isHostAllowed(host: string, allow: readonly string[]): boolean;
23
+ /** Hostname of a URL-ish token, or undefined when it isn't one. */
24
+ export declare function hostFromUrl(value: string): string | undefined;
25
+ /**
26
+ * Best-effort extraction of the hosts a shell command would contact.
27
+ * Returns an empty array when nothing recognisable is found — never guesses.
28
+ */
29
+ export declare function extractCommandHosts(command: string): string[];
30
+ /**
31
+ * Check a shell command against the allowlist.
32
+ * @returns an error string when a recognised host is disallowed, else null.
33
+ */
34
+ export declare function checkCommandNetwork(command: string, mode: "off" | "allowlist", allow: readonly string[]): string | null;
35
+ /** Check a URL the agent is about to fetch (or a redirect target). */
36
+ export declare function checkUrlNetwork(url: string, mode: "off" | "allowlist", allow: readonly string[]): string | null;
37
+ /** Resolved egress policy, read lazily so a settings change applies live. */
38
+ export interface NetworkPolicy {
39
+ mode: "off" | "allowlist";
40
+ allow: readonly string[];
41
+ }
42
+ export type GetNetworkPolicy = () => NetworkPolicy | undefined;
43
+ /** Check a URL against a possibly-absent policy. */
44
+ export declare function checkUrlPolicy(url: string, getPolicy?: GetNetworkPolicy): string | null;
45
+ /** Check a shell command against a possibly-absent policy. */
46
+ export declare function checkCommandPolicy(command: string, getPolicy?: GetNetworkPolicy): string | null;
47
+ //# sourceMappingURL=network-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-guard.d.ts","sourceRoot":"","sources":["../../src/core/network-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAc7E;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ7D;AA0CD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAoE7D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,KAAK,GAAG,WAAW,EACzB,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CAUf;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,KAAK,GAAG,WAAW,EACzB,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CAUf;AAED,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,KAAK,GAAG,WAAW,CAAC;IAC1B,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,aAAa,GAAG,SAAS,CAAC;AAE/D,oDAAoD;AACpD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAIvF;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAI/F"}
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,KAAwB;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC5D,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;YAClD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YAC1E,SAAS;QACX,CAAC;QACD,IAAI,MAAM,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,MAAM,SAAS,GAAG,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,KAAK,EAAE,CAAC;IACtF,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QACvD,OAAO,IAAI,IAAI,SAAS,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,cAAc,GAA2B;IAC7C,GAAG,EAAE,oBAAoB;IACzB,IAAI,EAAE,oBAAoB;IAC1B,IAAI,EAAE,sBAAsB;IAC5B,GAAG,EAAE,oBAAoB;IACzB,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,UAAU;CACjB,CAAC;AAEF,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;AACpG,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC;AAEzF,SAAS,WAAW,CAAC,KAAa;IAChC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IACE,OAAO,CAAC,MAAM,IAAI,CAAC;QACnB,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YACjD,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EACrD,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,KAAa,EAAE,mBAAmB,GAAG,KAAK;IACnE,IAAI,mBAAmB,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1F,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC1F,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACrF,kEAAkE;IAClE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,0EAA0E;IAC1E,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC1D,MAAM,MAAM,GAAG,OAAO;aACnB,IAAI,EAAE;aACN,KAAK,CAAC,KAAK,CAAC;aACZ,GAAG,CAAC,WAAW,CAAC;aAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC/B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAElC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM;YAAE,KAAK,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAErC,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;YAC1F,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAClC,MAAM,IAAI,GAAG,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;gBACjF,IAAI,IAAI;oBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;YACjD,IAAI,CAAC,GAAG,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAS;YACxD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBACpD,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAClC,IAAI,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACzC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;oBAC9B,IAAI,IAAI;wBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAC1B,SAAS;gBACX,CAAC;gBACD,kDAAkD;gBAClD,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC3C,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;oBACpC,IAAI,IAAI;wBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC5B,CAAC;YACH,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;YACxF,sEAAsE;YACtE,2CAA2C;YAC3C,MAAM,mBAAmB,GAAG,OAAO,KAAK,KAAK,CAAC;YAC9C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAClC,MAAM,IAAI,GAAG,iBAAiB,CAAC,GAAG,EAAE,mBAAmB,CAAC,CAAC;gBACzD,IAAI,IAAI,EAAE,CAAC;oBACT,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAChB,MAAM;gBACR,CAAC;YACH,CAAC;YACD,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;YACjD,IAAI,GAAG,IAAI,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7D,SAAS;QACX,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAe,EACf,IAAyB,EACzB,KAAwB;IAExB,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAC3F,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC/D,OAAO,CACL,qCAAqC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QAC3D,kBAAkB,OAAO,8DAA8D;QACvF,gDAAgD,CACjD,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAC7B,GAAW,EACX,IAAyB,EACzB,KAAwB;IAExB,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,IAAI;QAAE,OAAO,qCAAqC,GAAG,0BAA0B,CAAC;IACrF,IAAI,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC/D,OAAO,CACL,qCAAqC,IAAI,oBAAoB,OAAO,IAAI;QACxE,2DAA2D,CAC5D,CAAC;AACJ,CAAC;AAUD,oDAAoD;AACpD,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,SAA4B;IACtE,MAAM,MAAM,GAAG,SAAS,EAAE,EAAE,CAAC;IAC7B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,OAAO,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,SAA4B;IAC9E,MAAM,MAAM,GAAG,SAAS,EAAE,EAAE,CAAC;IAC7B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,OAAO,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=network-guard.test.d.ts.map
@@ -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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,aAAa,GACd,MAAM,oBAAoB,CAAC;AAE5B,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/D,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/D,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CAAC,aAAa,CAAC,gBAAgB,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,aAAa,CAAC,gBAAgB,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrE,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClE,MAAM,CAAC,aAAa,CAAC,gBAAgB,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpD,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,IAAI,CAAqB;QAC1B,CAAC,oCAAoC,EAAE,CAAC,aAAa,CAAC,CAAC;QACvD,CAAC,kDAAkD,EAAE,CAAC,mBAAmB,CAAC,CAAC;QAC3E,CAAC,6CAA6C,EAAE,CAAC,YAAY,CAAC,CAAC;QAC/D,CAAC,yCAAyC,EAAE,CAAC,YAAY,CAAC,CAAC;QAC3D,CAAC,sBAAsB,EAAE,EAAE,CAAC;QAC5B,CAAC,sCAAsC,EAAE,CAAC,kBAAkB,CAAC,CAAC;QAC9D,CAAC,2CAA2C,EAAE,CAAC,kBAAkB,CAAC,CAAC;QACnE,CAAC,sBAAsB,EAAE,CAAC,oBAAoB,CAAC,CAAC;QAChD,CAAC,oBAAoB,EAAE,CAAC,oBAAoB,CAAC,CAAC;QAC9C,CAAC,gBAAgB,EAAE,CAAC,sBAAsB,CAAC,CAAC;QAC5C,CAAC,sBAAsB,EAAE,CAAC,UAAU,CAAC,CAAC;QACtC,CAAC,4CAA4C,EAAE,CAAC,iBAAiB,CAAC,CAAC;KACpE,CAAC,CAAC,wBAAwB,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;QACjD,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,IAAI,CAAC;QACN,QAAQ;QACR,eAAe;QACf,YAAY;QACZ,0CAA0C;QAC1C,YAAY;QACZ,mBAAmB;QACnB,qBAAqB;KACtB,CAAC,CAAC,uCAAuC,EAAE,CAAC,OAAO,EAAE,EAAE;QACtD,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,mBAAmB,CAAC,2BAA2B,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACjF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,KAAK,GAAG,mBAAmB,CAAC,6BAA6B,EAAE,WAAW,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;QAC9F,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,MAAM,CACJ,mBAAmB,CAAC,sCAAsC,EAAE,WAAW,EAAE,CAAC,YAAY,CAAC,CAAC,CACzF,CAAC,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,mBAAmB,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,CAAC,eAAe,CAAC,sBAAsB,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,eAAe,CAAC,0BAA0B,EAAE,WAAW,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC9F,MAAM,CAAC,eAAe,CAAC,wBAAwB,EAAE,WAAW,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,CACxF,cAAc,CACf,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-commands.d.ts","sourceRoot":"","sources":["../../src/core/prompt-commands.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AA4BD,eAAO,MAAM,eAAe,EAAE,aAAa,EAihB1C,CAAC;AAEF,gDAAgD;AAChD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAExE"}
1
+ {"version":3,"file":"prompt-commands.d.ts","sourceRoot":"","sources":["../../src/core/prompt-commands.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AA+BD,eAAO,MAAM,eAAe,EAAE,aAAa,EAiiB1C,CAAC;AAEF,gDAAgD;AAChD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAExE"}