Skip to content

Commit d9b7aef

Browse files
committed
unset PKG_EXECPATH before invoking safe-chain binary
1 parent 0c8de1e commit d9b7aef

5 files changed

Lines changed: 78 additions & 5 deletions

File tree

packages/safe-chain/bin/safe-chain.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
#!/usr/bin/env node
22

3+
// Strip PKG_EXECPATH from the environment so any child process safe-chain
4+
// spawns (npm, uv, pip, …) doesn't inherit it. If it leaks into a subsequent
5+
// safe-chain invocation (e.g. via a shim) the yao-pkg bootstrap would treat
6+
// argv[1] as a script path and fail with MODULE_NOT_FOUND.
7+
delete process.env.PKG_EXECPATH;
8+
39
import chalk from "chalk";
410
import { ui } from "../src/environment/userInteraction.js";
511
import { setup } from "../src/shell-integration/setup.js";

packages/safe-chain/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ remove_shim_from_path() {
2020
}
2121

2222
if command -v safe-chain >/dev/null 2>&1; then
23-
# Remove shim directory from PATH when calling {{AIKIDO_COMMAND}} to prevent infinite loops
23+
# Remove shim directory from PATH when calling {{AIKIDO_COMMAND}} to prevent infinite loops.
24+
# Unset PKG_EXECPATH so the yao-pkg bootstrap inside the safe-chain binary doesn't
25+
# mistake argv[1] for a script path and try to resolve "{{PACKAGE_MANAGER}}" against cwd.
26+
unset PKG_EXECPATH
2427
PATH=$(remove_shim_from_path) exec safe-chain {{PACKAGE_MANAGER}} "$@"
2528
else
2629
# safe-chain is not reachable — warn the user so they know protection is inactive
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { describe, it } from "node:test";
2+
import assert from "node:assert";
3+
import fs from "node:fs";
4+
import path from "node:path";
5+
import { fileURLToPath } from "node:url";
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
8+
const repoRoot = path.resolve(__dirname, "..", "..");
9+
10+
describe("PKG_EXECPATH cleanup", () => {
11+
it("unix shim template unsets PKG_EXECPATH before invoking safe-chain", () => {
12+
const file = path.join(
13+
repoRoot,
14+
"src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh",
15+
);
16+
const content = fs.readFileSync(file, "utf-8");
17+
assert.match(
18+
content,
19+
/unset PKG_EXECPATH[\s\S]*exec safe-chain/,
20+
"unix-wrapper.template.sh must `unset PKG_EXECPATH` before `exec safe-chain`",
21+
);
22+
});
23+
24+
it("posix shell function unsets PKG_EXECPATH before invoking safe-chain", () => {
25+
const file = path.join(
26+
repoRoot,
27+
"src/shell-integration/startup-scripts/init-posix.sh",
28+
);
29+
const content = fs.readFileSync(file, "utf-8");
30+
// Scoped subshell so we don't mutate the user's interactive env.
31+
assert.match(
32+
content,
33+
/\(unset PKG_EXECPATH;\s*safe-chain "\$@"\)/,
34+
"init-posix.sh must invoke safe-chain in a subshell that unsets PKG_EXECPATH",
35+
);
36+
});
37+
38+
it("fish shell function unsets PKG_EXECPATH before invoking safe-chain", () => {
39+
const file = path.join(
40+
repoRoot,
41+
"src/shell-integration/startup-scripts/init-fish.fish",
42+
);
43+
const content = fs.readFileSync(file, "utf-8");
44+
assert.match(
45+
content,
46+
/env -u PKG_EXECPATH safe-chain/,
47+
"init-fish.fish must invoke safe-chain via `env -u PKG_EXECPATH`",
48+
);
49+
});
50+
51+
it("safe-chain entry point deletes PKG_EXECPATH from process.env", () => {
52+
const file = path.join(repoRoot, "bin/safe-chain.js");
53+
const content = fs.readFileSync(file, "utf-8");
54+
assert.match(
55+
content,
56+
/delete process\.env\.PKG_EXECPATH/,
57+
"bin/safe-chain.js must delete process.env.PKG_EXECPATH so spawned children don't inherit it",
58+
);
59+
});
60+
});

packages/safe-chain/src/shell-integration/startup-scripts/init-fish.fish

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,10 @@ function wrapSafeChainCommand
120120
end
121121

122122
if type -q safe-chain
123-
# If the safe-chain command is available, just run it with the provided arguments
124-
safe-chain $original_cmd $cmd_args
123+
# If the safe-chain command is available, just run it with the provided arguments.
124+
# Unset PKG_EXECPATH for this invocation so the yao-pkg bootstrap inside the
125+
# safe-chain binary doesn't mistake argv[1] for a script path to resolve against cwd.
126+
env -u PKG_EXECPATH safe-chain $original_cmd $cmd_args
125127
else
126128
# If the safe-chain command is not available, print a warning and run the original command
127129
printSafeChainWarning $original_cmd

packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,10 @@ function wrapSafeChainCommand() {
109109
fi
110110

111111
if command -v safe-chain > /dev/null 2>&1; then
112-
# If the aikido command is available, just run it with the provided arguments
113-
safe-chain "$@"
112+
# If the aikido command is available, just run it with the provided arguments.
113+
# Unset PKG_EXECPATH so the yao-pkg bootstrap inside the safe-chain binary doesn't
114+
# mistake argv[1] for a script path and try to resolve it against cwd.
115+
(unset PKG_EXECPATH; safe-chain "$@")
114116
else
115117
# If the aikido command is not available, print a warning and run the original command
116118
printSafeChainWarning "$original_cmd"

0 commit comments

Comments
 (0)