@aria-cli/tools 1.0.9 → 1.0.11
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/package.json +9 -5
- package/src/__tests__/web-fetch-download.test.ts +0 -433
- package/src/__tests__/web-tools.test.ts +0 -619
- package/src/ask-user-interaction.ts +0 -33
- package/src/cache/web-cache.ts +0 -110
- package/src/definitions/arion.ts +0 -118
- package/src/definitions/browser/browser.ts +0 -502
- package/src/definitions/browser/index.ts +0 -5
- package/src/definitions/browser/pw-downloads.ts +0 -142
- package/src/definitions/browser/pw-interactions.ts +0 -282
- package/src/definitions/browser/pw-responses.ts +0 -98
- package/src/definitions/browser/pw-session.ts +0 -405
- package/src/definitions/browser/pw-shared.ts +0 -85
- package/src/definitions/browser/pw-snapshot.ts +0 -383
- package/src/definitions/browser/pw-state.ts +0 -101
- package/src/definitions/browser/types.ts +0 -203
- package/src/definitions/code-intelligence.ts +0 -526
- package/src/definitions/core.ts +0 -118
- package/src/definitions/delegation.ts +0 -567
- package/src/definitions/deploy.ts +0 -73
- package/src/definitions/filesystem.ts +0 -217
- package/src/definitions/frg.ts +0 -67
- package/src/definitions/index.ts +0 -28
- package/src/definitions/memory.ts +0 -150
- package/src/definitions/messaging.ts +0 -734
- package/src/definitions/meta.ts +0 -392
- package/src/definitions/network.ts +0 -179
- package/src/definitions/outlook.ts +0 -318
- package/src/definitions/patch/apply-patch.ts +0 -235
- package/src/definitions/patch/fuzzy-match.ts +0 -217
- package/src/definitions/patch/index.ts +0 -1
- package/src/definitions/patch/patch-parser.ts +0 -297
- package/src/definitions/patch/sandbox-paths.ts +0 -129
- package/src/definitions/process/index.ts +0 -5
- package/src/definitions/process/process-registry.ts +0 -303
- package/src/definitions/process/process.ts +0 -456
- package/src/definitions/process/pty-keys.ts +0 -298
- package/src/definitions/process/session-slug.ts +0 -147
- package/src/definitions/quip.ts +0 -225
- package/src/definitions/search.ts +0 -67
- package/src/definitions/session-history.ts +0 -79
- package/src/definitions/shell.ts +0 -202
- package/src/definitions/slack.ts +0 -211
- package/src/definitions/web.ts +0 -119
- package/src/executors/apply-patch.ts +0 -1035
- package/src/executors/arion.ts +0 -199
- package/src/executors/code-intelligence.ts +0 -1179
- package/src/executors/deploy.ts +0 -1066
- package/src/executors/filesystem.ts +0 -1428
- package/src/executors/frg-freshness.ts +0 -743
- package/src/executors/frg.ts +0 -394
- package/src/executors/index.ts +0 -280
- package/src/executors/learning-meta.ts +0 -1367
- package/src/executors/lsp-client.ts +0 -355
- package/src/executors/memory.ts +0 -978
- package/src/executors/meta.ts +0 -293
- package/src/executors/process-registry.ts +0 -570
- package/src/executors/pty-session-store.ts +0 -43
- package/src/executors/pty.ts +0 -342
- package/src/executors/restart.ts +0 -133
- package/src/executors/search-freshness.ts +0 -249
- package/src/executors/search-types.ts +0 -98
- package/src/executors/search.ts +0 -89
- package/src/executors/self-diagnose.ts +0 -552
- package/src/executors/session-history.ts +0 -435
- package/src/executors/shell-safety.ts +0 -519
- package/src/executors/shell.ts +0 -1243
- package/src/executors/utils.ts +0 -40
- package/src/executors/web.ts +0 -786
- package/src/extraction/content-extraction.ts +0 -281
- package/src/extraction/index.ts +0 -5
- package/src/headless-control-contract.ts +0 -1149
- package/src/index.ts +0 -788
- package/src/local-control-http-auth.ts +0 -2
- package/src/mcp/client.ts +0 -218
- package/src/mcp/connection.ts +0 -568
- package/src/mcp/index.ts +0 -11
- package/src/mcp/jsonrpc.ts +0 -195
- package/src/mcp/types.ts +0 -199
- package/src/network-control-adapter.ts +0 -88
- package/src/network-runtime/address-types.ts +0 -218
- package/src/network-runtime/db-owner-fencing.ts +0 -91
- package/src/network-runtime/delivery-receipts.ts +0 -372
- package/src/network-runtime/direct-endpoint-authority.ts +0 -35
- package/src/network-runtime/index.ts +0 -316
- package/src/network-runtime/local-control-contract.ts +0 -784
- package/src/network-runtime/node-store-contract.ts +0 -46
- package/src/network-runtime/pair-route-contract.ts +0 -97
- package/src/network-runtime/peer-capabilities.ts +0 -48
- package/src/network-runtime/peer-principal-ref.ts +0 -20
- package/src/network-runtime/peer-state-machine.ts +0 -160
- package/src/network-runtime/protocol-schemas.ts +0 -265
- package/src/network-runtime/runtime-bootstrap-contract.ts +0 -83
- package/src/outlook/desktop-session.ts +0 -409
- package/src/policy.ts +0 -171
- package/src/providers/brave.ts +0 -80
- package/src/providers/duckduckgo.ts +0 -199
- package/src/providers/exa.ts +0 -85
- package/src/providers/firecrawl.ts +0 -77
- package/src/providers/index.ts +0 -8
- package/src/providers/jina.ts +0 -70
- package/src/providers/router.ts +0 -121
- package/src/providers/search-provider.ts +0 -74
- package/src/providers/tavily.ts +0 -74
- package/src/quip/desktop-session.ts +0 -435
- package/src/registry/index.ts +0 -1
- package/src/registry/registry.ts +0 -905
- package/src/runtime-socket-local-control-client.ts +0 -632
- package/src/security/dns-normalization.ts +0 -34
- package/src/security/dns-pinning.ts +0 -138
- package/src/security/external-content.ts +0 -129
- package/src/security/ssrf.ts +0 -207
- package/src/slack/desktop-session.ts +0 -493
- package/src/tool-factory.ts +0 -91
- package/src/types.ts +0 -1341
- package/src/utils/retry.ts +0 -163
- package/src/utils/safe-parse-json.ts +0 -176
- package/src/utils/url.ts +0 -20
- package/tests/benchmarks/registry.bench.ts +0 -57
- package/tests/cache/web-cache.test.ts +0 -147
- package/tests/critical-integration.test.ts +0 -1465
- package/tests/definitions/apply-patch.test.ts +0 -586
- package/tests/definitions/browser.test.ts +0 -495
- package/tests/definitions/delegation-pause-resume.test.ts +0 -758
- package/tests/definitions/execution.test.ts +0 -671
- package/tests/definitions/messaging-inbox-scope.test.ts +0 -229
- package/tests/definitions/messaging.test.ts +0 -1468
- package/tests/definitions/outlook.test.ts +0 -30
- package/tests/definitions/process.test.ts +0 -469
- package/tests/definitions/slack.test.ts +0 -28
- package/tests/definitions/tool-inventory.test.ts +0 -218
- package/tests/e2e/delegation-quest-orchestration.e2e.test.ts +0 -433
- package/tests/e2e/memory-tool-discovery-contract.e2e.test.ts +0 -81
- package/tests/executors/apply-patch.test.ts +0 -538
- package/tests/executors/arion.test.ts +0 -309
- package/tests/executors/conversation-primitives.test.ts +0 -250
- package/tests/executors/deploy.test.ts +0 -746
- package/tests/executors/filesystem-tools.test.ts +0 -357
- package/tests/executors/filesystem.test.ts +0 -959
- package/tests/executors/frg-freshness.test.ts +0 -136
- package/tests/executors/frg-merge.test.ts +0 -70
- package/tests/executors/frg-session-content.test.ts +0 -40
- package/tests/executors/frg.test.ts +0 -56
- package/tests/executors/memory-bugfixes.test.ts +0 -257
- package/tests/executors/memory-real-memoria.integration.test.ts +0 -316
- package/tests/executors/memory.test.ts +0 -853
- package/tests/executors/meta-tools.test.ts +0 -411
- package/tests/executors/meta.test.ts +0 -683
- package/tests/executors/path-containment.test.ts +0 -51
- package/tests/executors/process-registry.test.ts +0 -505
- package/tests/executors/pty.test.ts +0 -664
- package/tests/executors/quest-security.test.ts +0 -249
- package/tests/executors/read-file-media.test.ts +0 -230
- package/tests/executors/recall-knowledge-schema.test.ts +0 -209
- package/tests/executors/recall-tags.test.ts +0 -278
- package/tests/executors/remember-null-safety.contract.test.ts +0 -41
- package/tests/executors/restart.test.ts +0 -67
- package/tests/executors/search-unified.test.ts +0 -381
- package/tests/executors/session-history.test.ts +0 -340
- package/tests/executors/session-transcript.test.ts +0 -561
- package/tests/executors/shell-abort.test.ts +0 -416
- package/tests/executors/shell-env-blocklist.test.ts +0 -648
- package/tests/executors/shell-env-process.test.ts +0 -245
- package/tests/executors/shell-process-registry.test.ts +0 -334
- package/tests/executors/shell-tools.test.ts +0 -393
- package/tests/executors/shell.test.ts +0 -690
- package/tests/executors/web-abort-vs-timeout.test.ts +0 -213
- package/tests/executors/web-integration.test.ts +0 -633
- package/tests/executors/web-symlink.test.ts +0 -18
- package/tests/executors/web.test.ts +0 -1400
- package/tests/executors/write-stdin.test.ts +0 -145
- package/tests/extraction/content-extraction.test.ts +0 -153
- package/tests/guards/tools-default-test-lane.integration.test.ts +0 -21
- package/tests/guards/tools-package-test-commands.e2e.test.ts +0 -43
- package/tests/guards/tools-test-lane-manifest.contract.test.ts +0 -76
- package/tests/guards/tools-vitest-workspace-alias.contract.test.ts +0 -63
- package/tests/helpers/async-waits.ts +0 -53
- package/tests/integration/headless-control-contract.integration.test.ts +0 -153
- package/tests/integration/memory-tool-schema-parity.integration.test.ts +0 -67
- package/tests/integration/meta-tools-round-trip.integration.test.ts +0 -506
- package/tests/integration/quest-round-trip.test.ts +0 -303
- package/tests/integration/registry-executor-flow.test.ts +0 -85
- package/tests/integration.test.ts +0 -177
- package/tests/loading-tier.test.ts +0 -126
- package/tests/mcp/client-reconnect.test.ts +0 -267
- package/tests/mcp/connection.test.ts +0 -846
- package/tests/mcp/injectable-logger.test.ts +0 -83
- package/tests/mcp/jsonrpc.test.ts +0 -109
- package/tests/mcp/lifecycle.test.ts +0 -879
- package/tests/network-runtime/address-types.contract.test.ts +0 -143
- package/tests/network-runtime/continuity-bind-schema.contract.test.ts +0 -203
- package/tests/network-runtime/local-control-contract.test.ts +0 -869
- package/tests/network-runtime/local-control-invite-token.contract.test.ts +0 -146
- package/tests/network-runtime/node-store-contract.test.ts +0 -11
- package/tests/network-runtime/pair-protocol-nodeid.contract.test.ts +0 -15
- package/tests/network-runtime/peer-state-machine.contract.test.ts +0 -148
- package/tests/network-runtime/protocol-schemas.contract.test.ts +0 -512
- package/tests/network-runtime/relay-pending-nodeid.contract.test.ts +0 -62
- package/tests/network-runtime/runtime-bootstrap-contract.test.ts +0 -227
- package/tests/network-runtime/runtime-socket-local-control-client.test.ts +0 -621
- package/tests/network-runtime/wait-for-message-script.test.ts +0 -288
- package/tests/parallel.test.ts +0 -71
- package/tests/policy.test.ts +0 -184
- package/tests/print-default-test-lane.ts +0 -14
- package/tests/print-test-lane-manifest.ts +0 -22
- package/tests/providers/brave.test.ts +0 -159
- package/tests/providers/duckduckgo.test.ts +0 -207
- package/tests/providers/exa.test.ts +0 -175
- package/tests/providers/firecrawl.test.ts +0 -168
- package/tests/providers/jina.test.ts +0 -144
- package/tests/providers/router.test.ts +0 -328
- package/tests/providers/tavily.test.ts +0 -165
- package/tests/registry/discovery.test.ts +0 -154
- package/tests/registry/injectable-logger.test.ts +0 -230
- package/tests/registry/input-validation.test.ts +0 -361
- package/tests/registry/interface-completeness.test.ts +0 -85
- package/tests/registry/mcp-integration.test.ts +0 -103
- package/tests/registry/mcp-read-only-hint.test.ts +0 -60
- package/tests/registry/memoria-discovery.test.ts +0 -390
- package/tests/registry/nested-validation.test.ts +0 -283
- package/tests/registry/pseudo-tool-filtering.test.ts +0 -258
- package/tests/registry/registration-lifecycle.test.ts +0 -133
- package/tests/registry-validation.test.ts +0 -424
- package/tests/registry.test.ts +0 -460
- package/tests/security/dns-pinning.test.ts +0 -162
- package/tests/security/external-content.test.ts +0 -144
- package/tests/security/ssrf.test.ts +0 -118
- package/tests/shell-safety-integration.test.ts +0 -32
- package/tests/shell-safety.test.ts +0 -365
- package/tests/slack/desktop-session.test.ts +0 -50
- package/tests/test-lane-manifest.ts +0 -440
- package/tests/test-utils.ts +0 -27
- package/tests/tool-factory.test.ts +0 -188
- package/tests/utils/retry.test.ts +0 -231
- package/tests/utils/url.test.ts +0 -63
- package/tsconfig.cjs.json +0 -24
- package/tsconfig.json +0 -12
- package/vitest.config.ts +0 -55
- package/vitest.e2e.config.ts +0 -24
- package/vitest.integration.config.ts +0 -24
- package/vitest.native.config.ts +0 -24
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @aria/tools - process.env blocklist filtering tests (aria-zoa)
|
|
3
|
-
*
|
|
4
|
-
* Verifies that dangerous environment variables inherited from the host
|
|
5
|
-
* process.env are stripped before being passed to child processes.
|
|
6
|
-
*
|
|
7
|
-
* Key distinction: BLOCKED_ENV_VARS (injection vars like LD_PRELOAD) are
|
|
8
|
-
* stripped from ALL sources. BLOCKED_INPUT_ENV_VARS (PATH, HOME, etc.)
|
|
9
|
-
* are only stripped from tool-injected input, NOT from process.env.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { describe, it, expect, afterEach } from "vitest";
|
|
13
|
-
import type { ToolContext } from "../../src/types.js";
|
|
14
|
-
import { mergeEnv, BLOCKED_ENV_VARS, BLOCKED_INPUT_ENV_VARS } from "../../src/executors/shell.js";
|
|
15
|
-
|
|
16
|
-
const createContext = (workingDir: string, env: Record<string, string> = {}): ToolContext => ({
|
|
17
|
-
workingDir,
|
|
18
|
-
env,
|
|
19
|
-
confirm: async () => true,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe("process.env blocklist filtering (aria-zoa)", () => {
|
|
23
|
-
const savedEnvVars: Record<string, string | undefined> = {};
|
|
24
|
-
|
|
25
|
-
const setProcessEnv = (key: string, value: string) => {
|
|
26
|
-
if (!(key in savedEnvVars)) {
|
|
27
|
-
savedEnvVars[key] = process.env[key];
|
|
28
|
-
}
|
|
29
|
-
process.env[key] = value;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const deleteProcessEnv = (key: string) => {
|
|
33
|
-
if (!(key in savedEnvVars)) {
|
|
34
|
-
savedEnvVars[key] = process.env[key];
|
|
35
|
-
}
|
|
36
|
-
delete process.env[key];
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
afterEach(() => {
|
|
40
|
-
for (const [key, original] of Object.entries(savedEnvVars)) {
|
|
41
|
-
if (original === undefined) {
|
|
42
|
-
delete process.env[key];
|
|
43
|
-
} else {
|
|
44
|
-
process.env[key] = original;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
for (const key of Object.keys(savedEnvVars)) {
|
|
48
|
-
delete savedEnvVars[key];
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe("strips BLOCKED_ENV_VARS (injection vars) from process.env", () => {
|
|
53
|
-
it("should strip LD_PRELOAD from process.env", () => {
|
|
54
|
-
setProcessEnv("LD_PRELOAD", "/tmp/evil.so");
|
|
55
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
56
|
-
expect(result).not.toHaveProperty("LD_PRELOAD");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("should strip BASH_ENV from process.env", () => {
|
|
60
|
-
setProcessEnv("BASH_ENV", "/tmp/evil-bashrc.sh");
|
|
61
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
62
|
-
expect(result).not.toHaveProperty("BASH_ENV");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("should strip LD_LIBRARY_PATH from process.env", () => {
|
|
66
|
-
setProcessEnv("LD_LIBRARY_PATH", "/tmp/evil-libs");
|
|
67
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
68
|
-
expect(result).not.toHaveProperty("LD_LIBRARY_PATH");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("should strip DYLD_INSERT_LIBRARIES from process.env", () => {
|
|
72
|
-
setProcessEnv("DYLD_INSERT_LIBRARIES", "/tmp/evil.dylib");
|
|
73
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
74
|
-
expect(result).not.toHaveProperty("DYLD_INSERT_LIBRARIES");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("should strip PROMPT_COMMAND from process.env", () => {
|
|
78
|
-
setProcessEnv("PROMPT_COMMAND", "curl http://evil.com");
|
|
79
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
80
|
-
expect(result).not.toHaveProperty("PROMPT_COMMAND");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("should strip GIT_DIR from process.env", () => {
|
|
84
|
-
setProcessEnv("GIT_DIR", "/tmp/host-repo/.git");
|
|
85
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
86
|
-
expect(result).not.toHaveProperty("GIT_DIR");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("should strip indexed git config vars from process.env", () => {
|
|
90
|
-
setProcessEnv("GIT_CONFIG_KEY_0", "core.bare");
|
|
91
|
-
setProcessEnv("GIT_CONFIG_VALUE_0", "true");
|
|
92
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
93
|
-
expect(result).not.toHaveProperty("GIT_CONFIG_KEY_0");
|
|
94
|
-
expect(result).not.toHaveProperty("GIT_CONFIG_VALUE_0");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("should strip all BLOCKED_ENV_VARS from process.env", () => {
|
|
98
|
-
for (const varName of BLOCKED_ENV_VARS) {
|
|
99
|
-
setProcessEnv(varName, `evil_${varName.toLowerCase()}`);
|
|
100
|
-
}
|
|
101
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
102
|
-
for (const varName of BLOCKED_ENV_VARS) {
|
|
103
|
-
expect(result).not.toHaveProperty(varName);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("should strip git repo-context vars from process.env", () => {
|
|
108
|
-
setProcessEnv("GIT_DIR", "/tmp/not-repo");
|
|
109
|
-
setProcessEnv("GIT_WORK_TREE", "/tmp/not-repo");
|
|
110
|
-
setProcessEnv("GIT_CONFIG_GLOBAL", "/tmp/not-config");
|
|
111
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
112
|
-
expect(result).not.toHaveProperty("GIT_DIR");
|
|
113
|
-
expect(result).not.toHaveProperty("GIT_WORK_TREE");
|
|
114
|
-
expect(result).not.toHaveProperty("GIT_CONFIG_GLOBAL");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("should strip git config injection prefix vars from process.env", () => {
|
|
118
|
-
setProcessEnv("GIT_CONFIG_COUNT", "1");
|
|
119
|
-
setProcessEnv("GIT_CONFIG_KEY_0", "user.name");
|
|
120
|
-
setProcessEnv("GIT_CONFIG_VALUE_0", "poisoned");
|
|
121
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
122
|
-
expect(result).not.toHaveProperty("GIT_CONFIG_COUNT");
|
|
123
|
-
expect(result).not.toHaveProperty("GIT_CONFIG_KEY_0");
|
|
124
|
-
expect(result).not.toHaveProperty("GIT_CONFIG_VALUE_0");
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe("preserves host PATH/HOME/NODE_OPTIONS from process.env", () => {
|
|
129
|
-
it("should preserve PATH from process.env (host PATH is legitimate)", () => {
|
|
130
|
-
setProcessEnv("PATH", "/usr/local/bin:/usr/bin:/bin");
|
|
131
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
132
|
-
expect(result.PATH).toBe("/usr/local/bin:/usr/bin:/bin");
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("should preserve HOME from process.env (needed by git, ssh, etc.)", () => {
|
|
136
|
-
setProcessEnv("HOME", "/Users/testuser");
|
|
137
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
138
|
-
expect(result.HOME).toBe("/Users/testuser");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("should preserve NODE_OPTIONS from process.env (user config)", () => {
|
|
142
|
-
setProcessEnv("NODE_OPTIONS", "--max-old-space-size=4096");
|
|
143
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
144
|
-
expect(result.NODE_OPTIONS).toBe("--max-old-space-size=4096");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it("should preserve SHELL from process.env", () => {
|
|
148
|
-
setProcessEnv("SHELL", "/bin/zsh");
|
|
149
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
150
|
-
expect(result.SHELL).toBe("/bin/zsh");
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe("blocks BLOCKED_INPUT_ENV_VARS from tool-injected input", () => {
|
|
155
|
-
it("should block PATH from inputEnv", () => {
|
|
156
|
-
const result = mergeEnv(createContext("/tmp"), { PATH: "/tmp/evil-path" });
|
|
157
|
-
expect(result.PATH).not.toBe("/tmp/evil-path");
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("should block HOME from inputEnv", () => {
|
|
161
|
-
const result = mergeEnv(createContext("/tmp"), { HOME: "/tmp/evil-home" });
|
|
162
|
-
expect(result.HOME).not.toBe("/tmp/evil-home");
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("should block NODE_OPTIONS from inputEnv", () => {
|
|
166
|
-
const result = mergeEnv(createContext("/tmp"), {
|
|
167
|
-
NODE_OPTIONS: "--require /tmp/evil.js",
|
|
168
|
-
});
|
|
169
|
-
expect(result.NODE_OPTIONS).not.toBe("--require /tmp/evil.js");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("should block PATH from ctx.env", () => {
|
|
173
|
-
const ctx = createContext("/tmp", { PATH: "/tmp/evil-path" });
|
|
174
|
-
const result = mergeEnv(ctx);
|
|
175
|
-
expect(result.PATH).not.toBe("/tmp/evil-path");
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("should block all BLOCKED_INPUT_ENV_VARS from inputEnv", () => {
|
|
179
|
-
const evilInput: Record<string, string> = {};
|
|
180
|
-
for (const varName of BLOCKED_INPUT_ENV_VARS) {
|
|
181
|
-
evilInput[varName] = `evil_${varName.toLowerCase()}`;
|
|
182
|
-
}
|
|
183
|
-
const result = mergeEnv(createContext("/tmp"), evilInput);
|
|
184
|
-
for (const varName of BLOCKED_INPUT_ENV_VARS) {
|
|
185
|
-
expect(result[varName]).not.toBe(`evil_${varName.toLowerCase()}`);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("should block git context and identity vars from inputEnv", () => {
|
|
190
|
-
const result = mergeEnv(createContext("/tmp"), {
|
|
191
|
-
GIT_DIR: "/tmp/not-repo",
|
|
192
|
-
GIT_AUTHOR_NAME: "Injected",
|
|
193
|
-
GIT_COMMITTER_EMAIL: "evil@example.com",
|
|
194
|
-
});
|
|
195
|
-
expect(result).not.toHaveProperty("GIT_DIR");
|
|
196
|
-
expect(result).not.toHaveProperty("GIT_AUTHOR_NAME");
|
|
197
|
-
expect(result).not.toHaveProperty("GIT_COMMITTER_EMAIL");
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it("should block git context vars from ctx.env", () => {
|
|
201
|
-
const ctx = createContext("/tmp", {
|
|
202
|
-
GIT_WORK_TREE: "/tmp/not-repo",
|
|
203
|
-
GIT_CONFIG_GLOBAL: "/tmp/not-config",
|
|
204
|
-
});
|
|
205
|
-
const result = mergeEnv(ctx);
|
|
206
|
-
expect(result).not.toHaveProperty("GIT_WORK_TREE");
|
|
207
|
-
expect(result).not.toHaveProperty("GIT_CONFIG_GLOBAL");
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
describe("passthrough of safe vars", () => {
|
|
212
|
-
it("should preserve TERM from process.env", () => {
|
|
213
|
-
setProcessEnv("TERM", "xterm-256color");
|
|
214
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
215
|
-
expect(result.TERM).toBe("xterm-256color");
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it("should preserve LANG from process.env", () => {
|
|
219
|
-
setProcessEnv("LANG", "en_US.UTF-8");
|
|
220
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
221
|
-
expect(result.LANG).toBe("en_US.UTF-8");
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it("should preserve custom non-blocked variable", () => {
|
|
225
|
-
setProcessEnv("ARIA_TEST_CANARY", "canary_value_12345");
|
|
226
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
227
|
-
expect(result.ARIA_TEST_CANARY).toBe("canary_value_12345");
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
describe("edge cases", () => {
|
|
232
|
-
it("should handle absent process.env vars without crash", () => {
|
|
233
|
-
deleteProcessEnv("LD_PRELOAD");
|
|
234
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
235
|
-
expect(result).not.toHaveProperty("LD_PRELOAD");
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("should return a plain object (not process.env reference)", () => {
|
|
239
|
-
const result = mergeEnv(createContext("/tmp"));
|
|
240
|
-
result["__ARIA_MUTATION_TEST__"] = "test";
|
|
241
|
-
expect(process.env.__ARIA_MUTATION_TEST__).toBeUndefined();
|
|
242
|
-
delete result["__ARIA_MUTATION_TEST__"];
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
});
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @aria/tools - Shell executor + SpawnedProcessRegistry integration tests
|
|
3
|
-
*
|
|
4
|
-
* Verifies that shell executors properly register/deregister PIDs
|
|
5
|
-
* in the SpawnedProcessRegistry via ToolContext.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
9
|
-
import * as fs from "node:fs/promises";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import * as os from "node:os";
|
|
12
|
-
import type { ToolContext } from "../../src/types.js";
|
|
13
|
-
import { SpawnedProcessRegistry } from "../../src/executors/process-registry.js";
|
|
14
|
-
import { executeBash, executeExec, executeSpawn, executeKill } from "../../src/executors/shell.js";
|
|
15
|
-
|
|
16
|
-
const sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
-
const waitForFile = async (filePath: string, timeoutMs = 5_000): Promise<void> => {
|
|
18
|
-
const deadline = Date.now() + timeoutMs;
|
|
19
|
-
while (Date.now() < deadline) {
|
|
20
|
-
try {
|
|
21
|
-
await fs.access(filePath);
|
|
22
|
-
return;
|
|
23
|
-
} catch {
|
|
24
|
-
await sleep(25);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
throw new Error(`Timed out waiting for ${filePath}`);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function isProcessAlive(pid: number): boolean {
|
|
31
|
-
try {
|
|
32
|
-
process.kill(pid, 0);
|
|
33
|
-
return true;
|
|
34
|
-
} catch {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const createTempDir = async (): Promise<string> => {
|
|
40
|
-
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "aria-shell-reg-test-"));
|
|
41
|
-
return fs.realpath(tempDir);
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const SIGTERM_IGNORING_NODE_SCRIPT =
|
|
45
|
-
"const fs = require('node:fs'); process.on('SIGTERM', () => {}); fs.writeFileSync(process.env.READY_FILE, 'ready'); setInterval(() => {}, 1000);";
|
|
46
|
-
|
|
47
|
-
describe("Shell executors + SpawnedProcessRegistry", () => {
|
|
48
|
-
let tempDir: string;
|
|
49
|
-
let registry: SpawnedProcessRegistry;
|
|
50
|
-
let ctx: ToolContext;
|
|
51
|
-
const pidsToCleanup: number[] = [];
|
|
52
|
-
|
|
53
|
-
beforeEach(async () => {
|
|
54
|
-
tempDir = await createTempDir();
|
|
55
|
-
registry = new SpawnedProcessRegistry();
|
|
56
|
-
ctx = {
|
|
57
|
-
workingDir: tempDir,
|
|
58
|
-
env: {},
|
|
59
|
-
confirm: async () => true,
|
|
60
|
-
processRegistry: registry,
|
|
61
|
-
};
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
afterEach(async () => {
|
|
65
|
-
for (const pid of pidsToCleanup) {
|
|
66
|
-
try {
|
|
67
|
-
process.kill(pid, "SIGKILL");
|
|
68
|
-
} catch {
|
|
69
|
-
// Process may already be dead
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
pidsToCleanup.length = 0;
|
|
73
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe("executeExec", () => {
|
|
77
|
-
it("registers PID on spawn and deregisters on normal exit", async () => {
|
|
78
|
-
// Run a quick command
|
|
79
|
-
const result = await executeExec({ program: "echo", args: ["hello"] }, ctx);
|
|
80
|
-
|
|
81
|
-
expect(result.success).toBe(true);
|
|
82
|
-
// After the command exits normally, PID should be deregistered
|
|
83
|
-
expect(registry.size).toBe(0);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("registers PID on spawn and deregisters on non-zero exit", async () => {
|
|
87
|
-
const result = await executeExec({ program: "false" }, ctx);
|
|
88
|
-
|
|
89
|
-
expect(result.success).toBe(false);
|
|
90
|
-
// Even on failure, PID should be deregistered
|
|
91
|
-
expect(registry.size).toBe(0);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("registers PID on spawn and deregisters on timeout", async () => {
|
|
95
|
-
const result = await executeExec({ program: "sleep", args: ["30"], timeout: 100 }, ctx);
|
|
96
|
-
|
|
97
|
-
expect(result.success).toBe(false);
|
|
98
|
-
// After timeout, PID should be deregistered
|
|
99
|
-
expect(registry.size).toBe(0);
|
|
100
|
-
}, 10000);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe("executeBash", () => {
|
|
104
|
-
it("reports teardown-killed bash processes as failures instead of silent success", async () => {
|
|
105
|
-
const resultPromise = executeBash(
|
|
106
|
-
{
|
|
107
|
-
command: "sleep 60",
|
|
108
|
-
},
|
|
109
|
-
ctx,
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
await sleep(100);
|
|
113
|
-
|
|
114
|
-
expect(registry.size).toBe(1);
|
|
115
|
-
const [pid] = registry.getAll();
|
|
116
|
-
expect(typeof pid).toBe("number");
|
|
117
|
-
pidsToCleanup.push(pid!);
|
|
118
|
-
expect(isProcessAlive(pid!)).toBe(true);
|
|
119
|
-
|
|
120
|
-
await registry.killAll();
|
|
121
|
-
const result = await resultPromise;
|
|
122
|
-
|
|
123
|
-
expect(isProcessAlive(pid!)).toBe(false);
|
|
124
|
-
expect(registry.size).toBe(0);
|
|
125
|
-
expect(result.success).toBe(false);
|
|
126
|
-
expect(result.message).toContain("signal");
|
|
127
|
-
}, 15000);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe("executeSpawn", () => {
|
|
131
|
-
it("registers PID of detached background process", async () => {
|
|
132
|
-
const result = await executeSpawn({ program: "sleep", args: ["60"] }, ctx);
|
|
133
|
-
|
|
134
|
-
expect(result.success).toBe(true);
|
|
135
|
-
const data = result.data as { pid: number };
|
|
136
|
-
pidsToCleanup.push(data.pid);
|
|
137
|
-
|
|
138
|
-
// PID should be tracked in registry (spawned process is still alive)
|
|
139
|
-
expect(registry.has(data.pid)).toBe(true);
|
|
140
|
-
expect(registry.size).toBe(1);
|
|
141
|
-
|
|
142
|
-
// Process should be alive
|
|
143
|
-
expect(isProcessAlive(data.pid)).toBe(true);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("killAll cleans up spawned background processes", async () => {
|
|
147
|
-
const result = await executeSpawn({ program: "sleep", args: ["60"] }, ctx);
|
|
148
|
-
expect(result.success).toBe(true);
|
|
149
|
-
const data = result.data as { pid: number };
|
|
150
|
-
pidsToCleanup.push(data.pid);
|
|
151
|
-
|
|
152
|
-
expect(isProcessAlive(data.pid)).toBe(true);
|
|
153
|
-
|
|
154
|
-
// killAll should terminate the process
|
|
155
|
-
await registry.killAll();
|
|
156
|
-
await sleep(200);
|
|
157
|
-
|
|
158
|
-
expect(isProcessAlive(data.pid)).toBe(false);
|
|
159
|
-
expect(registry.size).toBe(0);
|
|
160
|
-
}, 10000);
|
|
161
|
-
|
|
162
|
-
it("killAll waits for SIGTERM-resistant spawned processes to exit", async () => {
|
|
163
|
-
const readyFile = path.join(tempDir, "sigterm-resistant-ready");
|
|
164
|
-
const result = await executeSpawn(
|
|
165
|
-
{
|
|
166
|
-
program: process.execPath,
|
|
167
|
-
args: ["-e", SIGTERM_IGNORING_NODE_SCRIPT],
|
|
168
|
-
env: {
|
|
169
|
-
READY_FILE: readyFile,
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
ctx,
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
expect(result.success).toBe(true);
|
|
176
|
-
const { pid } = result.data as { pid: number };
|
|
177
|
-
pidsToCleanup.push(pid);
|
|
178
|
-
await waitForFile(readyFile);
|
|
179
|
-
|
|
180
|
-
const startedAt = Date.now();
|
|
181
|
-
await registry.killAll();
|
|
182
|
-
const elapsedMs = Date.now() - startedAt;
|
|
183
|
-
|
|
184
|
-
expect(elapsedMs).toBeGreaterThanOrEqual(1900);
|
|
185
|
-
expect(isProcessAlive(pid)).toBe(false);
|
|
186
|
-
expect(registry.getProcess(pid)).toMatchObject({
|
|
187
|
-
pid,
|
|
188
|
-
status: "exited",
|
|
189
|
-
signal: "SIGKILL",
|
|
190
|
-
});
|
|
191
|
-
}, 15000);
|
|
192
|
-
|
|
193
|
-
it("killAll terminates detached process groups, not just the tracked leader pid", async () => {
|
|
194
|
-
const childPidFile = path.join(tempDir, "child-pid");
|
|
195
|
-
const result = await executeSpawn(
|
|
196
|
-
{
|
|
197
|
-
program: "sh",
|
|
198
|
-
args: ["-c", 'sleep 60 & child=$!; echo "$child" > "$CHILD_PID_FILE"; wait "$child"'],
|
|
199
|
-
env: {
|
|
200
|
-
CHILD_PID_FILE: childPidFile,
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
ctx,
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
expect(result.success).toBe(true);
|
|
207
|
-
const { pid } = result.data as { pid: number };
|
|
208
|
-
pidsToCleanup.push(pid);
|
|
209
|
-
await waitForFile(childPidFile);
|
|
210
|
-
const childPid = Number.parseInt((await fs.readFile(childPidFile, "utf8")).trim(), 10);
|
|
211
|
-
|
|
212
|
-
expect(isProcessAlive(pid)).toBe(true);
|
|
213
|
-
expect(isProcessAlive(childPid)).toBe(true);
|
|
214
|
-
|
|
215
|
-
await registry.killAll();
|
|
216
|
-
await sleep(200);
|
|
217
|
-
|
|
218
|
-
expect(isProcessAlive(pid)).toBe(false);
|
|
219
|
-
expect(isProcessAlive(childPid)).toBe(false);
|
|
220
|
-
}, 15000);
|
|
221
|
-
|
|
222
|
-
it("reaps detached process groups after the leader exits but the last child drains naturally", async () => {
|
|
223
|
-
const result = await executeSpawn(
|
|
224
|
-
{
|
|
225
|
-
program: process.execPath,
|
|
226
|
-
args: [
|
|
227
|
-
"-e",
|
|
228
|
-
[
|
|
229
|
-
"const { spawn } = require('node:child_process');",
|
|
230
|
-
"const child = spawn('sleep', ['1'], { stdio: 'ignore' });",
|
|
231
|
-
"child.unref();",
|
|
232
|
-
"setTimeout(() => process.exit(0), 20);",
|
|
233
|
-
].join(" "),
|
|
234
|
-
],
|
|
235
|
-
},
|
|
236
|
-
ctx,
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
expect(result.success).toBe(true);
|
|
240
|
-
const { pid } = result.data as { pid: number };
|
|
241
|
-
pidsToCleanup.push(pid);
|
|
242
|
-
|
|
243
|
-
expect(registry.has(pid)).toBe(true);
|
|
244
|
-
await sleep(1_500);
|
|
245
|
-
|
|
246
|
-
expect(registry.has(pid)).toBe(false);
|
|
247
|
-
expect(registry.getProcess(pid)).toMatchObject({
|
|
248
|
-
pid,
|
|
249
|
-
status: "exited",
|
|
250
|
-
});
|
|
251
|
-
}, 15_000);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
describe("executeKill", () => {
|
|
255
|
-
it("keeps a terminating pid tracked until exit is observed", async () => {
|
|
256
|
-
// Spawn a background process
|
|
257
|
-
const spawnResult = await executeSpawn({ program: "sleep", args: ["60"] }, ctx);
|
|
258
|
-
expect(spawnResult.success).toBe(true);
|
|
259
|
-
const { pid } = spawnResult.data as { pid: number };
|
|
260
|
-
pidsToCleanup.push(pid);
|
|
261
|
-
|
|
262
|
-
expect(registry.has(pid)).toBe(true);
|
|
263
|
-
|
|
264
|
-
// Kill it via executeKill
|
|
265
|
-
const killResult = await executeKill({ pid }, ctx);
|
|
266
|
-
expect(killResult.success).toBe(true);
|
|
267
|
-
|
|
268
|
-
await sleep(50);
|
|
269
|
-
expect(registry.has(pid)).toBe(false);
|
|
270
|
-
expect(registry.size).toBe(0);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it("keeps SIGTERM-resistant processes tracked so killAll can escalate later", async () => {
|
|
274
|
-
const readyFile = path.join(tempDir, "kill-resistant-ready");
|
|
275
|
-
const spawnResult = await executeSpawn(
|
|
276
|
-
{
|
|
277
|
-
program: process.execPath,
|
|
278
|
-
args: ["-e", SIGTERM_IGNORING_NODE_SCRIPT],
|
|
279
|
-
env: {
|
|
280
|
-
READY_FILE: readyFile,
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
ctx,
|
|
284
|
-
);
|
|
285
|
-
expect(spawnResult.success).toBe(true);
|
|
286
|
-
const { pid } = spawnResult.data as { pid: number };
|
|
287
|
-
pidsToCleanup.push(pid);
|
|
288
|
-
await waitForFile(readyFile);
|
|
289
|
-
|
|
290
|
-
const killResult = await executeKill({ pid }, ctx);
|
|
291
|
-
expect(killResult.success).toBe(true);
|
|
292
|
-
expect(registry.has(pid)).toBe(true);
|
|
293
|
-
expect(isProcessAlive(pid)).toBe(true);
|
|
294
|
-
|
|
295
|
-
await registry.killAll();
|
|
296
|
-
expect(isProcessAlive(pid)).toBe(false);
|
|
297
|
-
expect(registry.getProcess(pid)).toMatchObject({
|
|
298
|
-
pid,
|
|
299
|
-
status: "exited",
|
|
300
|
-
signal: "SIGKILL",
|
|
301
|
-
});
|
|
302
|
-
}, 15000);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
describe("without registry (backward compat)", () => {
|
|
306
|
-
it("executeExec works without processRegistry in context", async () => {
|
|
307
|
-
const ctxNoRegistry: ToolContext = {
|
|
308
|
-
workingDir: tempDir,
|
|
309
|
-
env: {},
|
|
310
|
-
confirm: async () => true,
|
|
311
|
-
// No processRegistry
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const result = await executeExec({ program: "echo", args: ["test"] }, ctxNoRegistry);
|
|
315
|
-
expect(result.success).toBe(true);
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
it("executeSpawn works without processRegistry in context", async () => {
|
|
319
|
-
const ctxNoRegistry: ToolContext = {
|
|
320
|
-
workingDir: tempDir,
|
|
321
|
-
env: {},
|
|
322
|
-
confirm: async () => true,
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
const result = await executeSpawn({ program: "sleep", args: ["60"] }, ctxNoRegistry);
|
|
326
|
-
expect(result.success).toBe(true);
|
|
327
|
-
const { pid } = result.data as { pid: number };
|
|
328
|
-
pidsToCleanup.push(pid);
|
|
329
|
-
|
|
330
|
-
// Should still work, just no tracking
|
|
331
|
-
expect(isProcessAlive(pid)).toBe(true);
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
});
|