unset PKG_EXECPATH before invoking safe-chain binary

This commit is contained in:
Reinier Criel 2026-05-13 14:33:58 -07:00
parent 0c8de1e606
commit d9b7aefd34
5 changed files with 78 additions and 5 deletions

View file

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

View file

@ -20,7 +20,10 @@ remove_shim_from_path() {
} }
if command -v safe-chain >/dev/null 2>&1; then if command -v safe-chain >/dev/null 2>&1; then
# Remove shim directory from PATH when calling {{AIKIDO_COMMAND}} to prevent infinite loops # Remove shim directory from PATH when calling {{AIKIDO_COMMAND}} to prevent infinite loops.
# Unset PKG_EXECPATH so the yao-pkg bootstrap inside the safe-chain binary doesn't
# mistake argv[1] for a script path and try to resolve "{{PACKAGE_MANAGER}}" against cwd.
unset PKG_EXECPATH
PATH=$(remove_shim_from_path) exec safe-chain {{PACKAGE_MANAGER}} "$@" PATH=$(remove_shim_from_path) exec safe-chain {{PACKAGE_MANAGER}} "$@"
else else
# safe-chain is not reachable — warn the user so they know protection is inactive # safe-chain is not reachable — warn the user so they know protection is inactive

View file

@ -0,0 +1,60 @@
import { describe, it } from "node:test";
import assert from "node:assert";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, "..", "..");
describe("PKG_EXECPATH cleanup", () => {
it("unix shim template unsets PKG_EXECPATH before invoking safe-chain", () => {
const file = path.join(
repoRoot,
"src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh",
);
const content = fs.readFileSync(file, "utf-8");
assert.match(
content,
/unset PKG_EXECPATH[\s\S]*exec safe-chain/,
"unix-wrapper.template.sh must `unset PKG_EXECPATH` before `exec safe-chain`",
);
});
it("posix shell function unsets PKG_EXECPATH before invoking safe-chain", () => {
const file = path.join(
repoRoot,
"src/shell-integration/startup-scripts/init-posix.sh",
);
const content = fs.readFileSync(file, "utf-8");
// Scoped subshell so we don't mutate the user's interactive env.
assert.match(
content,
/\(unset PKG_EXECPATH;\s*safe-chain "\$@"\)/,
"init-posix.sh must invoke safe-chain in a subshell that unsets PKG_EXECPATH",
);
});
it("fish shell function unsets PKG_EXECPATH before invoking safe-chain", () => {
const file = path.join(
repoRoot,
"src/shell-integration/startup-scripts/init-fish.fish",
);
const content = fs.readFileSync(file, "utf-8");
assert.match(
content,
/env -u PKG_EXECPATH safe-chain/,
"init-fish.fish must invoke safe-chain via `env -u PKG_EXECPATH`",
);
});
it("safe-chain entry point deletes PKG_EXECPATH from process.env", () => {
const file = path.join(repoRoot, "bin/safe-chain.js");
const content = fs.readFileSync(file, "utf-8");
assert.match(
content,
/delete process\.env\.PKG_EXECPATH/,
"bin/safe-chain.js must delete process.env.PKG_EXECPATH so spawned children don't inherit it",
);
});
});

View file

@ -120,8 +120,10 @@ function wrapSafeChainCommand
end end
if type -q safe-chain if type -q safe-chain
# If the safe-chain command is available, just run it with the provided arguments # If the safe-chain command is available, just run it with the provided arguments.
safe-chain $original_cmd $cmd_args # Unset PKG_EXECPATH for this invocation so the yao-pkg bootstrap inside the
# safe-chain binary doesn't mistake argv[1] for a script path to resolve against cwd.
env -u PKG_EXECPATH safe-chain $original_cmd $cmd_args
else else
# If the safe-chain command is not available, print a warning and run the original command # If the safe-chain command is not available, print a warning and run the original command
printSafeChainWarning $original_cmd printSafeChainWarning $original_cmd

View file

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