@aikidosec/safe-chain 0.0.1-custom-install-dir
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/LICENSE +674 -0
- package/README.md +537 -0
- package/bin/aikido-bun.js +14 -0
- package/bin/aikido-bunx.js +14 -0
- package/bin/aikido-npm.js +14 -0
- package/bin/aikido-npx.js +14 -0
- package/bin/aikido-pip.js +17 -0
- package/bin/aikido-pip3.js +17 -0
- package/bin/aikido-pipx.js +16 -0
- package/bin/aikido-pnpm.js +14 -0
- package/bin/aikido-pnpx.js +14 -0
- package/bin/aikido-poetry.js +13 -0
- package/bin/aikido-python.js +19 -0
- package/bin/aikido-python3.js +19 -0
- package/bin/aikido-uv.js +16 -0
- package/bin/aikido-uvx.js +16 -0
- package/bin/aikido-yarn.js +14 -0
- package/bin/safe-chain.js +147 -0
- package/docs/Release.md +25 -0
- package/docs/banner.svg +151 -0
- package/docs/safe-package-manager-demo.gif +0 -0
- package/docs/safe-package-manager-demo.png +0 -0
- package/docs/shell-integration.md +149 -0
- package/docs/troubleshooting.md +321 -0
- package/npm-shrinkwrap.json +3180 -0
- package/package.json +71 -0
- package/src/api/aikido.js +187 -0
- package/src/api/npmApi.js +71 -0
- package/src/config/cliArguments.js +161 -0
- package/src/config/configFile.js +327 -0
- package/src/config/environmentVariables.js +57 -0
- package/src/config/safeChainDir.js +71 -0
- package/src/config/settings.js +247 -0
- package/src/environment/environment.js +14 -0
- package/src/environment/userInteraction.js +122 -0
- package/src/installLocation.js +42 -0
- package/src/main.js +123 -0
- package/src/packagemanager/_shared/commandErrors.js +17 -0
- package/src/packagemanager/_shared/matchesCommand.js +18 -0
- package/src/packagemanager/bun/createBunPackageManager.js +48 -0
- package/src/packagemanager/currentPackageManager.js +82 -0
- package/src/packagemanager/npm/createPackageManager.js +72 -0
- package/src/packagemanager/npm/dependencyScanner/commandArgumentScanner.js +74 -0
- package/src/packagemanager/npm/dependencyScanner/nullScanner.js +9 -0
- package/src/packagemanager/npm/parsing/parsePackagesFromInstallArgs.js +144 -0
- package/src/packagemanager/npm/runNpmCommand.js +20 -0
- package/src/packagemanager/npm/utils/abbrevs-generated.js +359 -0
- package/src/packagemanager/npm/utils/cmd-list.js +174 -0
- package/src/packagemanager/npm/utils/npmCommands.js +34 -0
- package/src/packagemanager/npx/createPackageManager.js +15 -0
- package/src/packagemanager/npx/dependencyScanner/commandArgumentScanner.js +43 -0
- package/src/packagemanager/npx/parsing/parsePackagesFromArguments.js +130 -0
- package/src/packagemanager/npx/runNpxCommand.js +20 -0
- package/src/packagemanager/pip/createPackageManager.js +25 -0
- package/src/packagemanager/pip/pipSettings.js +6 -0
- package/src/packagemanager/pip/runPipCommand.js +209 -0
- package/src/packagemanager/pipx/createPipXPackageManager.js +18 -0
- package/src/packagemanager/pipx/runPipXCommand.js +60 -0
- package/src/packagemanager/pnpm/createPackageManager.js +57 -0
- package/src/packagemanager/pnpm/dependencyScanner/commandArgumentScanner.js +35 -0
- package/src/packagemanager/pnpm/parsing/parsePackagesFromArguments.js +109 -0
- package/src/packagemanager/pnpm/runPnpmCommand.js +32 -0
- package/src/packagemanager/poetry/createPoetryPackageManager.js +72 -0
- package/src/packagemanager/uv/createUvPackageManager.js +18 -0
- package/src/packagemanager/uv/runUvCommand.js +66 -0
- package/src/packagemanager/uvx/createUvxPackageManager.js +18 -0
- package/src/packagemanager/yarn/createPackageManager.js +41 -0
- package/src/packagemanager/yarn/dependencyScanner/commandArgumentScanner.js +35 -0
- package/src/packagemanager/yarn/parsing/parsePackagesFromArguments.js +128 -0
- package/src/packagemanager/yarn/runYarnCommand.js +36 -0
- package/src/registryProxy/certBundle.js +203 -0
- package/src/registryProxy/certUtils.js +178 -0
- package/src/registryProxy/getConnectTimeout.js +13 -0
- package/src/registryProxy/http-utils.js +80 -0
- package/src/registryProxy/interceptors/createInterceptorForEcoSystem.js +25 -0
- package/src/registryProxy/interceptors/interceptorBuilder.js +179 -0
- package/src/registryProxy/interceptors/minimumPackageAgeExclusions.js +33 -0
- package/src/registryProxy/interceptors/npm/modifyNpmInfo.js +180 -0
- package/src/registryProxy/interceptors/npm/npmInterceptor.js +101 -0
- package/src/registryProxy/interceptors/npm/parseNpmPackageUrl.js +60 -0
- package/src/registryProxy/interceptors/pip/modifyPipInfo.js +167 -0
- package/src/registryProxy/interceptors/pip/modifyPipJsonResponse.js +176 -0
- package/src/registryProxy/interceptors/pip/parsePipPackageUrl.js +162 -0
- package/src/registryProxy/interceptors/pip/pipInterceptor.js +122 -0
- package/src/registryProxy/interceptors/pip/pipMetadataResponseUtils.js +27 -0
- package/src/registryProxy/interceptors/pip/pipMetadataVersionUtils.js +131 -0
- package/src/registryProxy/interceptors/suppressedVersionsState.js +21 -0
- package/src/registryProxy/isImdsEndpoint.js +13 -0
- package/src/registryProxy/mitmRequestHandler.js +240 -0
- package/src/registryProxy/plainHttpProxy.js +95 -0
- package/src/registryProxy/registryProxy.js +255 -0
- package/src/registryProxy/tunnelRequestHandler.js +213 -0
- package/src/scanning/audit/index.js +129 -0
- package/src/scanning/index.js +82 -0
- package/src/scanning/malwareDatabase.js +131 -0
- package/src/scanning/newPackagesDatabaseBuilder.js +71 -0
- package/src/scanning/newPackagesDatabaseWarnings.js +17 -0
- package/src/scanning/newPackagesListCache.js +126 -0
- package/src/scanning/packageNameVariants.js +29 -0
- package/src/shell-integration/helpers.js +296 -0
- package/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh +37 -0
- package/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd +25 -0
- package/src/shell-integration/setup-ci.js +152 -0
- package/src/shell-integration/setup.js +110 -0
- package/src/shell-integration/shellDetection.js +39 -0
- package/src/shell-integration/startup-scripts/init-fish.fish +122 -0
- package/src/shell-integration/startup-scripts/init-posix.sh +112 -0
- package/src/shell-integration/startup-scripts/init-pwsh.ps1 +176 -0
- package/src/shell-integration/supported-shells/bash.js +222 -0
- package/src/shell-integration/supported-shells/fish.js +97 -0
- package/src/shell-integration/supported-shells/powershell.js +102 -0
- package/src/shell-integration/supported-shells/windowsPowershell.js +102 -0
- package/src/shell-integration/supported-shells/zsh.js +94 -0
- package/src/shell-integration/teardown.js +114 -0
- package/src/utils/safeSpawn.js +153 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { ui } from "../environment/userInteraction.js";
|
|
3
|
+
import { detectShells } from "./shellDetection.js";
|
|
4
|
+
import { knownAikidoTools, getPackageManagerList } from "./helpers.js";
|
|
5
|
+
import { getScriptsDir, getStartupScriptSourcePath } from "../config/safeChainDir.js";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Loops over the detected shells and calls the setup function for each.
|
|
11
|
+
*/
|
|
12
|
+
export async function setup() {
|
|
13
|
+
ui.writeInformation(
|
|
14
|
+
chalk.bold("Setting up shell aliases.") +
|
|
15
|
+
` This will wrap safe-chain around ${getPackageManagerList()}.`,
|
|
16
|
+
);
|
|
17
|
+
ui.emptyLine();
|
|
18
|
+
|
|
19
|
+
copyStartupFiles();
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const shells = detectShells();
|
|
23
|
+
if (shells.length === 0) {
|
|
24
|
+
ui.writeError("No supported shells detected. Cannot set up aliases.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ui.writeInformation(
|
|
29
|
+
`Detected ${shells.length} supported shell(s): ${shells
|
|
30
|
+
.map((shell) => chalk.bold(shell.name))
|
|
31
|
+
.join(", ")}.`,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
let updatedCount = 0;
|
|
35
|
+
for (const shell of shells) {
|
|
36
|
+
if (await setupShell(shell)) {
|
|
37
|
+
updatedCount++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (updatedCount > 0) {
|
|
42
|
+
ui.emptyLine();
|
|
43
|
+
ui.writeInformation(`Please restart your terminal to apply the changes.`);
|
|
44
|
+
}
|
|
45
|
+
} catch (/** @type {any} */ error) {
|
|
46
|
+
ui.writeError(
|
|
47
|
+
`Failed to set up shell aliases: ${error.message}. Please check your shell configuration.`,
|
|
48
|
+
);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Calls the setup function for the given shell and reports the result.
|
|
55
|
+
* @param {import("./shellDetection.js").Shell} shell
|
|
56
|
+
*/
|
|
57
|
+
async function setupShell(shell) {
|
|
58
|
+
let success = false;
|
|
59
|
+
let error;
|
|
60
|
+
try {
|
|
61
|
+
shell.teardown(knownAikidoTools); // First, tear down to prevent duplicate aliases
|
|
62
|
+
success = await shell.setup(knownAikidoTools);
|
|
63
|
+
} catch (/** @type {any} */ err) {
|
|
64
|
+
success = false;
|
|
65
|
+
error = err;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (success) {
|
|
69
|
+
ui.writeInformation(
|
|
70
|
+
`${chalk.bold("- " + shell.name + ":")} ${chalk.green(
|
|
71
|
+
"Setup successful",
|
|
72
|
+
)}`,
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
ui.writeError(
|
|
76
|
+
`${chalk.bold("- " + shell.name + ":")} ${chalk.red("Setup failed")}`,
|
|
77
|
+
);
|
|
78
|
+
if (error) {
|
|
79
|
+
let message = ` Error: ${error.message}`;
|
|
80
|
+
if (error.code) {
|
|
81
|
+
message += ` (code: ${error.code})`;
|
|
82
|
+
}
|
|
83
|
+
ui.writeError(message);
|
|
84
|
+
}
|
|
85
|
+
ui.emptyLine();
|
|
86
|
+
ui.writeInformation(` ${chalk.bold("To set up manually:")}`);
|
|
87
|
+
for (const instruction of shell.getManualSetupInstructions()) {
|
|
88
|
+
ui.writeInformation(` ${instruction}`);
|
|
89
|
+
}
|
|
90
|
+
ui.emptyLine();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return success;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function copyStartupFiles() {
|
|
97
|
+
const startupFiles = ["init-posix.sh", "init-pwsh.ps1", "init-fish.fish"];
|
|
98
|
+
const targetDir = getScriptsDir();
|
|
99
|
+
|
|
100
|
+
for (const file of startupFiles) {
|
|
101
|
+
const targetPath = path.join(targetDir, file);
|
|
102
|
+
|
|
103
|
+
if (!fs.existsSync(targetDir)) {
|
|
104
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const sourcePath = getStartupScriptSourcePath(import.meta.url, file);
|
|
108
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import zsh from "./supported-shells/zsh.js";
|
|
2
|
+
import bash from "./supported-shells/bash.js";
|
|
3
|
+
import powershell from "./supported-shells/powershell.js";
|
|
4
|
+
import windowsPowershell from "./supported-shells/windowsPowershell.js";
|
|
5
|
+
import fish from "./supported-shells/fish.js";
|
|
6
|
+
import { ui } from "../environment/userInteraction.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} Shell
|
|
10
|
+
* @property {string} name
|
|
11
|
+
* @property {() => boolean} isInstalled
|
|
12
|
+
* @property {(tools: import("./helpers.js").AikidoTool[]) => boolean|Promise<boolean>} setup
|
|
13
|
+
* @property {(tools: import("./helpers.js").AikidoTool[]) => boolean} teardown
|
|
14
|
+
* @property {() => string[]} getManualSetupInstructions
|
|
15
|
+
* @property {() => string[]} getManualTeardownInstructions
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @returns {Shell[]}
|
|
20
|
+
*/
|
|
21
|
+
export function detectShells() {
|
|
22
|
+
let possibleShells = [zsh, bash, powershell, windowsPowershell, fish];
|
|
23
|
+
let availableShells = [];
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
for (const shell of possibleShells) {
|
|
27
|
+
if (shell.isInstalled()) {
|
|
28
|
+
availableShells.push(shell);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch (/** @type {any} */ error) {
|
|
32
|
+
ui.writeError(
|
|
33
|
+
`We were not able to detect which shells are installed on your system. Please check your shell configuration. Error: ${error.message}`,
|
|
34
|
+
);
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return availableShells;
|
|
39
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
set -l safe_chain_script (status filename)
|
|
2
|
+
set -l safe_chain_scripts_dir (dirname $safe_chain_script)
|
|
3
|
+
set -l safe_chain_base (dirname $safe_chain_scripts_dir)
|
|
4
|
+
set -gx PATH $PATH $safe_chain_base/bin
|
|
5
|
+
|
|
6
|
+
function npx
|
|
7
|
+
wrapSafeChainCommand "npx" $argv
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
function yarn
|
|
11
|
+
wrapSafeChainCommand "yarn" $argv
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
function pnpm
|
|
15
|
+
wrapSafeChainCommand "pnpm" $argv
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
function pnpx
|
|
19
|
+
wrapSafeChainCommand "pnpx" $argv
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
function bun
|
|
23
|
+
wrapSafeChainCommand "bun" $argv
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
function bunx
|
|
27
|
+
wrapSafeChainCommand "bunx" $argv
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
function npm
|
|
31
|
+
# If args is just -v or --version and nothing else, just run the `npm -v` command
|
|
32
|
+
# This is because nvm uses this to check the version of npm
|
|
33
|
+
set argc (count $argv)
|
|
34
|
+
if test $argc -eq 1
|
|
35
|
+
switch $argv[1]
|
|
36
|
+
case "-v" "--version"
|
|
37
|
+
command npm $argv
|
|
38
|
+
return
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
wrapSafeChainCommand "npm" $argv
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
function pip
|
|
46
|
+
wrapSafeChainCommand "pip" $argv
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
function pip3
|
|
50
|
+
wrapSafeChainCommand "pip3" $argv
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
function uv
|
|
54
|
+
wrapSafeChainCommand "uv" $argv
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
function uvx
|
|
58
|
+
wrapSafeChainCommand "uvx" $argv
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
function poetry
|
|
62
|
+
wrapSafeChainCommand "poetry" $argv
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# `python -m pip`, `python -m pip3`.
|
|
66
|
+
function python
|
|
67
|
+
wrapSafeChainCommand "python" $argv
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# `python3 -m pip`, `python3 -m pip3'.
|
|
71
|
+
function python3
|
|
72
|
+
wrapSafeChainCommand "python3" $argv
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
function pipx
|
|
76
|
+
wrapSafeChainCommand "pipx" $argv
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
function printSafeChainWarning
|
|
80
|
+
set original_cmd $argv[1]
|
|
81
|
+
|
|
82
|
+
# Fish equivalent of ANSI color codes: yellow background, black text for "Warning:"
|
|
83
|
+
set_color -b yellow black
|
|
84
|
+
printf "Warning:"
|
|
85
|
+
set_color normal
|
|
86
|
+
printf " safe-chain is not available to protect you from installing malware. %s will run without it.\n" $original_cmd
|
|
87
|
+
|
|
88
|
+
# Cyan text for the install command
|
|
89
|
+
printf "Install safe-chain by using "
|
|
90
|
+
set_color cyan
|
|
91
|
+
printf "npm install -g @aikidosec/safe-chain"
|
|
92
|
+
set_color normal
|
|
93
|
+
printf ".\n"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
function wrapSafeChainCommand
|
|
97
|
+
set original_cmd $argv[1]
|
|
98
|
+
set cmd_args $argv[2..-1]
|
|
99
|
+
|
|
100
|
+
if not type -fq $original_cmd
|
|
101
|
+
# If the original command is not available, don't try to wrap it: invoke
|
|
102
|
+
# it transparently, so the shell can report errors as if this wrapper
|
|
103
|
+
# didn't exist. fish always adds extra debug information when executing
|
|
104
|
+
# missing commands from within a function, so after the "command not
|
|
105
|
+
# found" handler, there will be information about how the
|
|
106
|
+
# wrapSafeChainCommand function errored out. To avoid users assuming this
|
|
107
|
+
# is a safe-chain bug, display an explicit error message afterwards.
|
|
108
|
+
command $original_cmd $cmd_args
|
|
109
|
+
set oldstatus $status
|
|
110
|
+
echo "safe-chain tried to run $original_cmd but it doesn't seem to be installed in your \$PATH." >&2
|
|
111
|
+
return $oldstatus
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if type -q safe-chain
|
|
115
|
+
# If the safe-chain command is available, just run it with the provided arguments
|
|
116
|
+
safe-chain $original_cmd $cmd_args
|
|
117
|
+
else
|
|
118
|
+
# If the safe-chain command is not available, print a warning and run the original command
|
|
119
|
+
printSafeChainWarning $original_cmd
|
|
120
|
+
command $original_cmd $cmd_args
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
if [ -n "${BASH_SOURCE[0]:-}" ]; then
|
|
2
|
+
_sc_script_path="${BASH_SOURCE[0]}"
|
|
3
|
+
elif [ -n "${ZSH_VERSION:-}" ]; then
|
|
4
|
+
# ${(%):-%x} uses Zsh prompt expansion to get the sourced file's path.
|
|
5
|
+
# eval is required so other shells don't try to parse the Zsh-specific syntax.
|
|
6
|
+
eval '_sc_script_path="${(%):-%x}"'
|
|
7
|
+
else
|
|
8
|
+
_sc_script_path="$0"
|
|
9
|
+
fi
|
|
10
|
+
_sc_scripts_dir=$(CDPATH= cd -- "$(dirname -- "$_sc_script_path")" 2>/dev/null && pwd -P)
|
|
11
|
+
_sc_base=$(dirname -- "$_sc_scripts_dir")
|
|
12
|
+
export PATH="$PATH:${_sc_base}/bin"
|
|
13
|
+
unset _sc_base _sc_script_path _sc_scripts_dir
|
|
14
|
+
|
|
15
|
+
function npx() {
|
|
16
|
+
wrapSafeChainCommand "npx" "$@"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function yarn() {
|
|
20
|
+
wrapSafeChainCommand "yarn" "$@"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function pnpm() {
|
|
24
|
+
wrapSafeChainCommand "pnpm" "$@"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pnpx() {
|
|
28
|
+
wrapSafeChainCommand "pnpx" "$@"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function bun() {
|
|
32
|
+
wrapSafeChainCommand "bun" "$@"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function bunx() {
|
|
36
|
+
wrapSafeChainCommand "bunx" "$@"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function npm() {
|
|
40
|
+
if [[ "$1" == "-v" || "$1" == "--version" ]] && [[ $# -eq 1 ]]; then
|
|
41
|
+
# If args is just -v or --version and nothing else, just run the npm version command
|
|
42
|
+
# This is because nvm uses this to check the version of npm
|
|
43
|
+
command npm "$@"
|
|
44
|
+
return
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
wrapSafeChainCommand "npm" "$@"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function pip() {
|
|
51
|
+
wrapSafeChainCommand "pip" "$@"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function pip3() {
|
|
55
|
+
wrapSafeChainCommand "pip3" "$@"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function uv() {
|
|
59
|
+
wrapSafeChainCommand "uv" "$@"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function uvx() {
|
|
63
|
+
wrapSafeChainCommand "uvx" "$@"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function poetry() {
|
|
67
|
+
wrapSafeChainCommand "poetry" "$@"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# `python -m pip`, `python -m pip3`.
|
|
71
|
+
function python() {
|
|
72
|
+
wrapSafeChainCommand "python" "$@"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# `python3 -m pip`, `python3 -m pip3'.
|
|
76
|
+
function python3() {
|
|
77
|
+
wrapSafeChainCommand "python3" "$@"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function pipx() {
|
|
81
|
+
wrapSafeChainCommand "pipx" "$@"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printSafeChainWarning() {
|
|
85
|
+
# \033[43;30m is used to set the background color to yellow and text color to black
|
|
86
|
+
# \033[0m is used to reset the text formatting
|
|
87
|
+
printf "\033[43;30mWarning:\033[0m safe-chain is not available to protect you from installing malware. %s will run without it.\n" "$1"
|
|
88
|
+
# \033[36m is used to set the text color to cyan
|
|
89
|
+
printf "Install safe-chain by using \033[36mnpm install -g @aikidosec/safe-chain\033[0m.\n"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function wrapSafeChainCommand() {
|
|
93
|
+
local original_cmd="$1"
|
|
94
|
+
|
|
95
|
+
if ! type -f "${original_cmd}" > /dev/null 2>&1; then
|
|
96
|
+
# If the original command is not available, don't try to wrap it: invoke it
|
|
97
|
+
# transparently, so the shell can report errors as if this wrapper didn't
|
|
98
|
+
# exist.
|
|
99
|
+
command "$@"
|
|
100
|
+
return $?
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if command -v safe-chain > /dev/null 2>&1; then
|
|
104
|
+
# If the aikido command is available, just run it with the provided arguments
|
|
105
|
+
safe-chain "$@"
|
|
106
|
+
else
|
|
107
|
+
# If the aikido command is not available, print a warning and run the original command
|
|
108
|
+
printSafeChainWarning "$original_cmd"
|
|
109
|
+
|
|
110
|
+
command "$@"
|
|
111
|
+
fi
|
|
112
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Use cross-platform path separator (: on Unix, ; on Windows)
|
|
2
|
+
# $IsWindows is only available in PowerShell Core 6.0+. If it doesn't exist, assume Windows PowerShell
|
|
3
|
+
$isWindowsPlatform = if (Test-Path variable:IsWindows) { $IsWindows } else { $true }
|
|
4
|
+
$pathSeparator = if ($isWindowsPlatform) { ';' } else { ':' }
|
|
5
|
+
$safeChainBase = Split-Path -Parent $PSScriptRoot
|
|
6
|
+
$safeChainBin = Join-Path $safeChainBase 'bin'
|
|
7
|
+
$env:PATH = "$env:PATH$pathSeparator$safeChainBin"
|
|
8
|
+
|
|
9
|
+
function npx {
|
|
10
|
+
Invoke-WrappedCommand "npx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function yarn {
|
|
14
|
+
Invoke-WrappedCommand "yarn" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function pnpm {
|
|
18
|
+
Invoke-WrappedCommand "pnpm" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function pnpx {
|
|
22
|
+
Invoke-WrappedCommand "pnpx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function bun {
|
|
26
|
+
Invoke-WrappedCommand "bun" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function bunx {
|
|
30
|
+
Invoke-WrappedCommand "bunx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function npm {
|
|
34
|
+
# If args is just -v or --version and nothing else, just run the npm version command
|
|
35
|
+
# This is because nvm uses this to check the version of npm
|
|
36
|
+
if (($args.Length -eq 1) -and (($args[0] -eq "-v") -or ($args[0] -eq "--version"))) {
|
|
37
|
+
Invoke-RealCommand "npm" $args
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Invoke-WrappedCommand "npm" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function pip {
|
|
45
|
+
Invoke-WrappedCommand "pip" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function pip3 {
|
|
49
|
+
Invoke-WrappedCommand "pip3" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function uv {
|
|
53
|
+
Invoke-WrappedCommand "uv" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function uvx {
|
|
57
|
+
Invoke-WrappedCommand "uvx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function poetry {
|
|
61
|
+
Invoke-WrappedCommand "poetry" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# `python -m pip`, `python -m pip3`.
|
|
65
|
+
function python {
|
|
66
|
+
Invoke-WrappedCommand 'python' $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# `python3 -m pip`, `python3 -m pip3'.
|
|
70
|
+
function python3 {
|
|
71
|
+
Invoke-WrappedCommand 'python3' $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function pipx {
|
|
75
|
+
Invoke-WrappedCommand "pipx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Write-SafeChainWarning {
|
|
79
|
+
param([string]$Command)
|
|
80
|
+
|
|
81
|
+
# PowerShell equivalent of ANSI color codes: yellow background, black text for "Warning:"
|
|
82
|
+
Write-Host "Warning:" -BackgroundColor Yellow -ForegroundColor Black -NoNewline
|
|
83
|
+
Write-Host " safe-chain is not available to protect you from installing malware. $Command will run without it."
|
|
84
|
+
|
|
85
|
+
# Cyan text for the install command
|
|
86
|
+
Write-Host "Install safe-chain by using " -NoNewline
|
|
87
|
+
Write-Host "npm install -g @aikidosec/safe-chain" -ForegroundColor Cyan -NoNewline
|
|
88
|
+
Write-Host "."
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function Test-CommandAvailable {
|
|
92
|
+
param([string]$Command)
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
Get-Command $Command -ErrorAction Stop | Out-Null
|
|
96
|
+
return $true
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return $false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function Invoke-RealCommand {
|
|
104
|
+
param(
|
|
105
|
+
[string]$Command,
|
|
106
|
+
[string[]]$Arguments
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Find the real executable to avoid calling our wrapped functions
|
|
110
|
+
$realCommand = Get-Command -Name $Command -CommandType Application | Select-Object -First 1
|
|
111
|
+
if ($realCommand) {
|
|
112
|
+
& $realCommand.Source @Arguments
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function Get-ReconstructedArguments {
|
|
117
|
+
param(
|
|
118
|
+
[string]$RawLine,
|
|
119
|
+
[int]$RawOffset
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if (-not $RawLine) { return $null }
|
|
123
|
+
|
|
124
|
+
$tokens = [System.Management.Automation.PSParser]::Tokenize($RawLine, [ref]$null)
|
|
125
|
+
$newArgs = @()
|
|
126
|
+
$foundCommand = $false
|
|
127
|
+
|
|
128
|
+
foreach ($t in $tokens) {
|
|
129
|
+
if (-not $foundCommand) {
|
|
130
|
+
if ($t.Start -eq ($RawOffset - 1)) { $foundCommand = $true }
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if ($t.Type -eq 'Operator' -and $t.Content -match '[|;&]') { break }
|
|
135
|
+
|
|
136
|
+
# Stop if complex variable expansion is used
|
|
137
|
+
if ($t.Type -eq 'Variable' -or $t.Type -eq 'Group' -or $t.Type -eq 'SubExpression') {
|
|
138
|
+
return $null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
$newArgs += $t.Content
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if ($foundCommand) {
|
|
145
|
+
return ,$newArgs
|
|
146
|
+
}
|
|
147
|
+
return $null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function Invoke-WrappedCommand {
|
|
151
|
+
param(
|
|
152
|
+
[string]$OriginalCmd,
|
|
153
|
+
[string[]]$Arguments,
|
|
154
|
+
[string]$RawLine = $null,
|
|
155
|
+
[int]$RawOffset = 0
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Use raw line parsing to recover arguments like '--' that PowerShell consumes
|
|
159
|
+
if ($RawLine) {
|
|
160
|
+
$reconstructedArgs = Get-ReconstructedArguments $RawLine $RawOffset
|
|
161
|
+
if ($null -ne $reconstructedArgs) {
|
|
162
|
+
$Arguments = $reconstructedArgs
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if ($isWindowsPlatform -and (Test-CommandAvailable "safe-chain.cmd")) {
|
|
167
|
+
& safe-chain.cmd $OriginalCmd @Arguments
|
|
168
|
+
}
|
|
169
|
+
elseif (Test-CommandAvailable "safe-chain") {
|
|
170
|
+
& safe-chain $OriginalCmd @Arguments
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
Write-SafeChainWarning $OriginalCmd
|
|
174
|
+
Invoke-RealCommand $OriginalCmd $Arguments
|
|
175
|
+
}
|
|
176
|
+
}
|