@aikidosec/safe-chain 0.0.1-immutable-releases-beta
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 +517 -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-yarn.js +14 -0
- package/bin/safe-chain.js +130 -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 +4069 -0
- package/package.json +72 -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/settings.js +247 -0
- package/src/environment/environment.js +14 -0
- package/src/environment/userInteraction.js +122 -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 +79 -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/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 +304 -0
- package/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh +22 -0
- package/src/shell-integration/path-wrappers/templates/windows-wrapper.template.cmd +24 -0
- package/src/shell-integration/setup-ci.js +172 -0
- package/src/shell-integration/setup.js +129 -0
- package/src/shell-integration/shellDetection.js +39 -0
- package/src/shell-integration/startup-scripts/init-fish.fish +115 -0
- package/src/shell-integration/startup-scripts/init-posix.sh +96 -0
- package/src/shell-integration/startup-scripts/init-pwsh.ps1 +171 -0
- package/src/shell-integration/supported-shells/bash.js +152 -0
- package/src/shell-integration/supported-shells/fish.js +95 -0
- package/src/shell-integration/supported-shells/powershell.js +100 -0
- package/src/shell-integration/supported-shells/windowsPowershell.js +100 -0
- package/src/shell-integration/supported-shells/zsh.js +92 -0
- package/src/shell-integration/teardown.js +112 -0
- package/src/ultimate/ultimateTroubleshooting.js +111 -0
- package/src/utils/safeSpawn.js +153 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { ui } from "../environment/userInteraction.js";
|
|
3
|
+
import { detectShells } from "./shellDetection.js";
|
|
4
|
+
import {
|
|
5
|
+
knownAikidoTools,
|
|
6
|
+
getPackageManagerList,
|
|
7
|
+
getScriptsDir,
|
|
8
|
+
} from "./helpers.js";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
|
|
13
|
+
/** @type {string} */
|
|
14
|
+
// This checks the current file's dirname in a way that's compatible with:
|
|
15
|
+
// - Modulejs (import.meta.url)
|
|
16
|
+
// - ES modules (__dirname)
|
|
17
|
+
// This is needed because safe-chain's npm package is built using ES modules,
|
|
18
|
+
// but building the binaries requires commonjs.
|
|
19
|
+
let dirname;
|
|
20
|
+
if (import.meta.url) {
|
|
21
|
+
const filename = fileURLToPath(import.meta.url);
|
|
22
|
+
dirname = path.dirname(filename);
|
|
23
|
+
} else {
|
|
24
|
+
dirname = __dirname;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Loops over the detected shells and calls the setup function for each.
|
|
29
|
+
*/
|
|
30
|
+
export async function setup() {
|
|
31
|
+
ui.writeInformation(
|
|
32
|
+
chalk.bold("Setting up shell aliases.") +
|
|
33
|
+
` This will wrap safe-chain around ${getPackageManagerList()}.`,
|
|
34
|
+
);
|
|
35
|
+
ui.emptyLine();
|
|
36
|
+
|
|
37
|
+
copyStartupFiles();
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const shells = detectShells();
|
|
41
|
+
if (shells.length === 0) {
|
|
42
|
+
ui.writeError("No supported shells detected. Cannot set up aliases.");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
ui.writeInformation(
|
|
47
|
+
`Detected ${shells.length} supported shell(s): ${shells
|
|
48
|
+
.map((shell) => chalk.bold(shell.name))
|
|
49
|
+
.join(", ")}.`,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
let updatedCount = 0;
|
|
53
|
+
for (const shell of shells) {
|
|
54
|
+
if (await setupShell(shell)) {
|
|
55
|
+
updatedCount++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (updatedCount > 0) {
|
|
60
|
+
ui.emptyLine();
|
|
61
|
+
ui.writeInformation(`Please restart your terminal to apply the changes.`);
|
|
62
|
+
}
|
|
63
|
+
} catch (/** @type {any} */ error) {
|
|
64
|
+
ui.writeError(
|
|
65
|
+
`Failed to set up shell aliases: ${error.message}. Please check your shell configuration.`,
|
|
66
|
+
);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Calls the setup function for the given shell and reports the result.
|
|
73
|
+
* @param {import("./shellDetection.js").Shell} shell
|
|
74
|
+
*/
|
|
75
|
+
async function setupShell(shell) {
|
|
76
|
+
let success = false;
|
|
77
|
+
let error;
|
|
78
|
+
try {
|
|
79
|
+
shell.teardown(knownAikidoTools); // First, tear down to prevent duplicate aliases
|
|
80
|
+
success = await shell.setup(knownAikidoTools);
|
|
81
|
+
} catch (/** @type {any} */ err) {
|
|
82
|
+
success = false;
|
|
83
|
+
error = err;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (success) {
|
|
87
|
+
ui.writeInformation(
|
|
88
|
+
`${chalk.bold("- " + shell.name + ":")} ${chalk.green(
|
|
89
|
+
"Setup successful",
|
|
90
|
+
)}`,
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
ui.writeError(
|
|
94
|
+
`${chalk.bold("- " + shell.name + ":")} ${chalk.red("Setup failed")}`,
|
|
95
|
+
);
|
|
96
|
+
if (error) {
|
|
97
|
+
let message = ` Error: ${error.message}`;
|
|
98
|
+
if (error.code) {
|
|
99
|
+
message += ` (code: ${error.code})`;
|
|
100
|
+
}
|
|
101
|
+
ui.writeError(message);
|
|
102
|
+
}
|
|
103
|
+
ui.emptyLine();
|
|
104
|
+
ui.writeInformation(` ${chalk.bold("To set up manually:")}`);
|
|
105
|
+
for (const instruction of shell.getManualSetupInstructions()) {
|
|
106
|
+
ui.writeInformation(` ${instruction}`);
|
|
107
|
+
}
|
|
108
|
+
ui.emptyLine();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return success;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function copyStartupFiles() {
|
|
115
|
+
const startupFiles = ["init-posix.sh", "init-pwsh.ps1", "init-fish.fish"];
|
|
116
|
+
const targetDir = getScriptsDir();
|
|
117
|
+
|
|
118
|
+
for (const file of startupFiles) {
|
|
119
|
+
const targetPath = path.join(targetDir, file);
|
|
120
|
+
|
|
121
|
+
if (!fs.existsSync(targetDir)) {
|
|
122
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Use absolute path for source
|
|
126
|
+
const sourcePath = path.join(dirname, "startup-scripts", file);
|
|
127
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -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,115 @@
|
|
|
1
|
+
set -gx PATH $PATH $HOME/.safe-chain/bin
|
|
2
|
+
|
|
3
|
+
function npx
|
|
4
|
+
wrapSafeChainCommand "npx" $argv
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
function yarn
|
|
8
|
+
wrapSafeChainCommand "yarn" $argv
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
function pnpm
|
|
12
|
+
wrapSafeChainCommand "pnpm" $argv
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
function pnpx
|
|
16
|
+
wrapSafeChainCommand "pnpx" $argv
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
function bun
|
|
20
|
+
wrapSafeChainCommand "bun" $argv
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
function bunx
|
|
24
|
+
wrapSafeChainCommand "bunx" $argv
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
function npm
|
|
28
|
+
# If args is just -v or --version and nothing else, just run the `npm -v` command
|
|
29
|
+
# This is because nvm uses this to check the version of npm
|
|
30
|
+
set argc (count $argv)
|
|
31
|
+
if test $argc -eq 1
|
|
32
|
+
switch $argv[1]
|
|
33
|
+
case "-v" "--version"
|
|
34
|
+
command npm $argv
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
wrapSafeChainCommand "npm" $argv
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
function pip
|
|
43
|
+
wrapSafeChainCommand "pip" $argv
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
function pip3
|
|
47
|
+
wrapSafeChainCommand "pip3" $argv
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
function uv
|
|
51
|
+
wrapSafeChainCommand "uv" $argv
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
function poetry
|
|
55
|
+
wrapSafeChainCommand "poetry" $argv
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# `python -m pip`, `python -m pip3`.
|
|
59
|
+
function python
|
|
60
|
+
wrapSafeChainCommand "python" $argv
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# `python3 -m pip`, `python3 -m pip3'.
|
|
64
|
+
function python3
|
|
65
|
+
wrapSafeChainCommand "python3" $argv
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
function pipx
|
|
69
|
+
wrapSafeChainCommand "pipx" $argv
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
function printSafeChainWarning
|
|
73
|
+
set original_cmd $argv[1]
|
|
74
|
+
|
|
75
|
+
# Fish equivalent of ANSI color codes: yellow background, black text for "Warning:"
|
|
76
|
+
set_color -b yellow black
|
|
77
|
+
printf "Warning:"
|
|
78
|
+
set_color normal
|
|
79
|
+
printf " safe-chain is not available to protect you from installing malware. %s will run without it.\n" $original_cmd
|
|
80
|
+
|
|
81
|
+
# Cyan text for the install command
|
|
82
|
+
printf "Install safe-chain by using "
|
|
83
|
+
set_color cyan
|
|
84
|
+
printf "npm install -g @aikidosec/safe-chain"
|
|
85
|
+
set_color normal
|
|
86
|
+
printf ".\n"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
function wrapSafeChainCommand
|
|
90
|
+
set original_cmd $argv[1]
|
|
91
|
+
set cmd_args $argv[2..-1]
|
|
92
|
+
|
|
93
|
+
if not type -fq $original_cmd
|
|
94
|
+
# If the original command is not available, don't try to wrap it: invoke
|
|
95
|
+
# it transparently, so the shell can report errors as if this wrapper
|
|
96
|
+
# didn't exist. fish always adds extra debug information when executing
|
|
97
|
+
# missing commands from within a function, so after the "command not
|
|
98
|
+
# found" handler, there will be information about how the
|
|
99
|
+
# wrapSafeChainCommand function errored out. To avoid users assuming this
|
|
100
|
+
# is a safe-chain bug, display an explicit error message afterwards.
|
|
101
|
+
command $original_cmd $cmd_args
|
|
102
|
+
set oldstatus $status
|
|
103
|
+
echo "safe-chain tried to run $original_cmd but it doesn't seem to be installed in your \$PATH." >&2
|
|
104
|
+
return $oldstatus
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if type -q safe-chain
|
|
108
|
+
# If the safe-chain command is available, just run it with the provided arguments
|
|
109
|
+
safe-chain $original_cmd $cmd_args
|
|
110
|
+
else
|
|
111
|
+
# If the safe-chain command is not available, print a warning and run the original command
|
|
112
|
+
printSafeChainWarning $original_cmd
|
|
113
|
+
command $original_cmd $cmd_args
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export PATH="$PATH:$HOME/.safe-chain/bin"
|
|
2
|
+
|
|
3
|
+
function npx() {
|
|
4
|
+
wrapSafeChainCommand "npx" "$@"
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function yarn() {
|
|
8
|
+
wrapSafeChainCommand "yarn" "$@"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function pnpm() {
|
|
12
|
+
wrapSafeChainCommand "pnpm" "$@"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function pnpx() {
|
|
16
|
+
wrapSafeChainCommand "pnpx" "$@"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function bun() {
|
|
20
|
+
wrapSafeChainCommand "bun" "$@"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function bunx() {
|
|
24
|
+
wrapSafeChainCommand "bunx" "$@"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function npm() {
|
|
28
|
+
if [[ "$1" == "-v" || "$1" == "--version" ]] && [[ $# -eq 1 ]]; then
|
|
29
|
+
# If args is just -v or --version and nothing else, just run the npm version command
|
|
30
|
+
# This is because nvm uses this to check the version of npm
|
|
31
|
+
command npm "$@"
|
|
32
|
+
return
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
wrapSafeChainCommand "npm" "$@"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pip() {
|
|
39
|
+
wrapSafeChainCommand "pip" "$@"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pip3() {
|
|
43
|
+
wrapSafeChainCommand "pip3" "$@"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function uv() {
|
|
47
|
+
wrapSafeChainCommand "uv" "$@"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function poetry() {
|
|
51
|
+
wrapSafeChainCommand "poetry" "$@"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# `python -m pip`, `python -m pip3`.
|
|
55
|
+
function python() {
|
|
56
|
+
wrapSafeChainCommand "python" "$@"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# `python3 -m pip`, `python3 -m pip3'.
|
|
60
|
+
function python3() {
|
|
61
|
+
wrapSafeChainCommand "python3" "$@"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function pipx() {
|
|
65
|
+
wrapSafeChainCommand "pipx" "$@"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function printSafeChainWarning() {
|
|
69
|
+
# \033[43;30m is used to set the background color to yellow and text color to black
|
|
70
|
+
# \033[0m is used to reset the text formatting
|
|
71
|
+
printf "\033[43;30mWarning:\033[0m safe-chain is not available to protect you from installing malware. %s will run without it.\n" "$1"
|
|
72
|
+
# \033[36m is used to set the text color to cyan
|
|
73
|
+
printf "Install safe-chain by using \033[36mnpm install -g @aikidosec/safe-chain\033[0m.\n"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function wrapSafeChainCommand() {
|
|
77
|
+
local original_cmd="$1"
|
|
78
|
+
|
|
79
|
+
if ! type -f "${original_cmd}" > /dev/null 2>&1; then
|
|
80
|
+
# If the original command is not available, don't try to wrap it: invoke it
|
|
81
|
+
# transparently, so the shell can report errors as if this wrapper didn't
|
|
82
|
+
# exist.
|
|
83
|
+
command "$@"
|
|
84
|
+
return $?
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
if command -v safe-chain > /dev/null 2>&1; then
|
|
88
|
+
# If the aikido command is available, just run it with the provided arguments
|
|
89
|
+
safe-chain "$@"
|
|
90
|
+
else
|
|
91
|
+
# If the aikido command is not available, print a warning and run the original command
|
|
92
|
+
printSafeChainWarning "$original_cmd"
|
|
93
|
+
|
|
94
|
+
command "$@"
|
|
95
|
+
fi
|
|
96
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
$safeChainBin = Join-Path (Join-Path $HOME '.safe-chain') 'bin'
|
|
6
|
+
$env:PATH = "$env:PATH$pathSeparator$safeChainBin"
|
|
7
|
+
|
|
8
|
+
function npx {
|
|
9
|
+
Invoke-WrappedCommand "npx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function yarn {
|
|
13
|
+
Invoke-WrappedCommand "yarn" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function pnpm {
|
|
17
|
+
Invoke-WrappedCommand "pnpm" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pnpx {
|
|
21
|
+
Invoke-WrappedCommand "pnpx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function bun {
|
|
25
|
+
Invoke-WrappedCommand "bun" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function bunx {
|
|
29
|
+
Invoke-WrappedCommand "bunx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function npm {
|
|
33
|
+
# If args is just -v or --version and nothing else, just run the npm version command
|
|
34
|
+
# This is because nvm uses this to check the version of npm
|
|
35
|
+
if (($args.Length -eq 1) -and (($args[0] -eq "-v") -or ($args[0] -eq "--version"))) {
|
|
36
|
+
Invoke-RealCommand "npm" $args
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Invoke-WrappedCommand "npm" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function pip {
|
|
44
|
+
Invoke-WrappedCommand "pip" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function pip3 {
|
|
48
|
+
Invoke-WrappedCommand "pip3" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function uv {
|
|
52
|
+
Invoke-WrappedCommand "uv" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function poetry {
|
|
56
|
+
Invoke-WrappedCommand "poetry" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# `python -m pip`, `python -m pip3`.
|
|
60
|
+
function python {
|
|
61
|
+
Invoke-WrappedCommand 'python' $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# `python3 -m pip`, `python3 -m pip3'.
|
|
65
|
+
function python3 {
|
|
66
|
+
Invoke-WrappedCommand 'python3' $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function pipx {
|
|
70
|
+
Invoke-WrappedCommand "pipx" $args $MyInvocation.Line $MyInvocation.OffsetInLine
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function Write-SafeChainWarning {
|
|
74
|
+
param([string]$Command)
|
|
75
|
+
|
|
76
|
+
# PowerShell equivalent of ANSI color codes: yellow background, black text for "Warning:"
|
|
77
|
+
Write-Host "Warning:" -BackgroundColor Yellow -ForegroundColor Black -NoNewline
|
|
78
|
+
Write-Host " safe-chain is not available to protect you from installing malware. $Command will run without it."
|
|
79
|
+
|
|
80
|
+
# Cyan text for the install command
|
|
81
|
+
Write-Host "Install safe-chain by using " -NoNewline
|
|
82
|
+
Write-Host "npm install -g @aikidosec/safe-chain" -ForegroundColor Cyan -NoNewline
|
|
83
|
+
Write-Host "."
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function Test-CommandAvailable {
|
|
87
|
+
param([string]$Command)
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
Get-Command $Command -ErrorAction Stop | Out-Null
|
|
91
|
+
return $true
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return $false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Invoke-RealCommand {
|
|
99
|
+
param(
|
|
100
|
+
[string]$Command,
|
|
101
|
+
[string[]]$Arguments
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Find the real executable to avoid calling our wrapped functions
|
|
105
|
+
$realCommand = Get-Command -Name $Command -CommandType Application | Select-Object -First 1
|
|
106
|
+
if ($realCommand) {
|
|
107
|
+
& $realCommand.Source @Arguments
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function Get-ReconstructedArguments {
|
|
112
|
+
param(
|
|
113
|
+
[string]$RawLine,
|
|
114
|
+
[int]$RawOffset
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if (-not $RawLine) { return $null }
|
|
118
|
+
|
|
119
|
+
$tokens = [System.Management.Automation.PSParser]::Tokenize($RawLine, [ref]$null)
|
|
120
|
+
$newArgs = @()
|
|
121
|
+
$foundCommand = $false
|
|
122
|
+
|
|
123
|
+
foreach ($t in $tokens) {
|
|
124
|
+
if (-not $foundCommand) {
|
|
125
|
+
if ($t.Start -eq ($RawOffset - 1)) { $foundCommand = $true }
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if ($t.Type -eq 'Operator' -and $t.Content -match '[|;&]') { break }
|
|
130
|
+
|
|
131
|
+
# Stop if complex variable expansion is used
|
|
132
|
+
if ($t.Type -eq 'Variable' -or $t.Type -eq 'Group' -or $t.Type -eq 'SubExpression') {
|
|
133
|
+
return $null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
$newArgs += $t.Content
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if ($foundCommand) {
|
|
140
|
+
return ,$newArgs
|
|
141
|
+
}
|
|
142
|
+
return $null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function Invoke-WrappedCommand {
|
|
146
|
+
param(
|
|
147
|
+
[string]$OriginalCmd,
|
|
148
|
+
[string[]]$Arguments,
|
|
149
|
+
[string]$RawLine = $null,
|
|
150
|
+
[int]$RawOffset = 0
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Use raw line parsing to recover arguments like '--' that PowerShell consumes
|
|
154
|
+
if ($RawLine) {
|
|
155
|
+
$reconstructedArgs = Get-ReconstructedArguments $RawLine $RawOffset
|
|
156
|
+
if ($null -ne $reconstructedArgs) {
|
|
157
|
+
$Arguments = $reconstructedArgs
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if ($isWindowsPlatform -and (Test-CommandAvailable "safe-chain.cmd")) {
|
|
162
|
+
& safe-chain.cmd $OriginalCmd @Arguments
|
|
163
|
+
}
|
|
164
|
+
elseif (Test-CommandAvailable "safe-chain") {
|
|
165
|
+
& safe-chain $OriginalCmd @Arguments
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
Write-SafeChainWarning $OriginalCmd
|
|
169
|
+
Invoke-RealCommand $OriginalCmd $Arguments
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addLineToFile,
|
|
3
|
+
doesExecutableExistOnSystem,
|
|
4
|
+
removeLinesMatchingPattern,
|
|
5
|
+
} from "../helpers.js";
|
|
6
|
+
import { execSync, spawnSync } from "child_process";
|
|
7
|
+
import * as os from "os";
|
|
8
|
+
|
|
9
|
+
const shellName = "Bash";
|
|
10
|
+
const executableName = "bash";
|
|
11
|
+
const startupFileCommand = "echo ~/.bashrc";
|
|
12
|
+
const eol = "\n"; // When bash runs on Windows (e.g., Git Bash or WSL), it expects LF line endings.
|
|
13
|
+
|
|
14
|
+
function isInstalled() {
|
|
15
|
+
return doesExecutableExistOnSystem(executableName);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {import("../helpers.js").AikidoTool[]} tools
|
|
20
|
+
*
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
23
|
+
function teardown(tools) {
|
|
24
|
+
const startupFile = getStartupFile();
|
|
25
|
+
|
|
26
|
+
for (const { tool } of tools) {
|
|
27
|
+
// Remove any existing alias for the tool
|
|
28
|
+
removeLinesMatchingPattern(
|
|
29
|
+
startupFile,
|
|
30
|
+
new RegExp(`^alias\\s+${tool}=`),
|
|
31
|
+
eol
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Removes the line that sources the safe-chain bash initialization script (~/.safe-chain/scripts/init-posix.sh)
|
|
36
|
+
removeLinesMatchingPattern(
|
|
37
|
+
startupFile,
|
|
38
|
+
/^source\s+~\/\.safe-chain\/scripts\/init-posix\.sh/,
|
|
39
|
+
eol
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setup() {
|
|
46
|
+
const startupFile = getStartupFile();
|
|
47
|
+
|
|
48
|
+
addLineToFile(
|
|
49
|
+
startupFile,
|
|
50
|
+
`source ~/.safe-chain/scripts/init-posix.sh # Safe-chain bash initialization script`,
|
|
51
|
+
eol
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getStartupFile() {
|
|
58
|
+
try {
|
|
59
|
+
var path = execSync(startupFileCommand, {
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
shell: executableName,
|
|
62
|
+
}).trim();
|
|
63
|
+
|
|
64
|
+
return windowsFixPath(path);
|
|
65
|
+
} catch (/** @type {any} */ error) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Command failed: ${startupFileCommand}. Error: ${error.message}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} path
|
|
74
|
+
*
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function windowsFixPath(path) {
|
|
78
|
+
try {
|
|
79
|
+
if (os.platform() !== "win32") {
|
|
80
|
+
return path;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// On windows cygwin bash, paths are returned in format /c/user/..., but we need it in format C:\user\...
|
|
84
|
+
// To convert, the cygpath -w command can be used to convert to the desired format.
|
|
85
|
+
// Cygpath only exists on Cygwin, so we first check if the command is available.
|
|
86
|
+
// If it is, we use it to convert the path.
|
|
87
|
+
if (hasCygpath()) {
|
|
88
|
+
return cygpathw(path);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return path;
|
|
92
|
+
} catch {
|
|
93
|
+
return path;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function hasCygpath() {
|
|
98
|
+
try {
|
|
99
|
+
var result = spawnSync("where", ["cygpath"], { shell: executableName });
|
|
100
|
+
return result.status === 0;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {string} path
|
|
108
|
+
*
|
|
109
|
+
* @returns {string}
|
|
110
|
+
*/
|
|
111
|
+
function cygpathw(path) {
|
|
112
|
+
try {
|
|
113
|
+
var result = spawnSync("cygpath", ["-w", path], {
|
|
114
|
+
encoding: "utf8",
|
|
115
|
+
shell: executableName,
|
|
116
|
+
});
|
|
117
|
+
if (result.status === 0) {
|
|
118
|
+
return result.stdout.trim();
|
|
119
|
+
}
|
|
120
|
+
return path;
|
|
121
|
+
} catch {
|
|
122
|
+
return path;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getManualTeardownInstructions() {
|
|
127
|
+
return [
|
|
128
|
+
`Remove the following line from your ~/.bashrc file:`,
|
|
129
|
+
` source ~/.safe-chain/scripts/init-posix.sh`,
|
|
130
|
+
`Then restart your terminal or run: source ~/.bashrc`,
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getManualSetupInstructions() {
|
|
135
|
+
return [
|
|
136
|
+
`Add the following line to your ~/.bashrc file:`,
|
|
137
|
+
` source ~/.safe-chain/scripts/init-posix.sh`,
|
|
138
|
+
`Then restart your terminal or run: source ~/.bashrc`,
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @type {import("../shellDetection.js").Shell}
|
|
144
|
+
*/
|
|
145
|
+
export default {
|
|
146
|
+
name: shellName,
|
|
147
|
+
isInstalled,
|
|
148
|
+
setup,
|
|
149
|
+
teardown,
|
|
150
|
+
getManualSetupInstructions,
|
|
151
|
+
getManualTeardownInstructions,
|
|
152
|
+
};
|