@aikidosec/safe-chain 1.0.22 → 1.0.23
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/README.md +11 -7
- package/package.json +7 -7
- package/src/shell-integration/helpers.js +31 -10
- package/src/shell-integration/setup.js +10 -1
- package/src/shell-integration/supported-shells/bash.js +10 -3
- package/src/shell-integration/supported-shells/fish.js +7 -3
- package/src/shell-integration/supported-shells/zsh.js +10 -3
package/README.md
CHANGED
|
@@ -8,12 +8,16 @@ The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [n
|
|
|
8
8
|
|
|
9
9
|
Aikido Safe Chain works on Node.js version 18 and above and supports the following package managers:
|
|
10
10
|
|
|
11
|
-
- ✅ **npm
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
11
|
+
- ✅ full coverage: **npm >= 10.4.0**:
|
|
12
|
+
- ⚠️ limited to scanning the install command arguments (broader scanning coming soon):
|
|
13
|
+
- **npm < 10.4.0**
|
|
14
|
+
- **npx**
|
|
15
|
+
- **yarn**
|
|
16
|
+
- **pnpm**
|
|
17
|
+
- **pnpx**
|
|
18
|
+
- 🚧 **bun**: coming soon
|
|
19
|
+
|
|
20
|
+
Note on the limited support for npm < 10.4.0, npx, yarn, pnpm and pnpx: adding **full support for these package managers is a high priority**. In the meantime, we offer limited support already, which means that the Aikido Safe Chain will scan the package names passed as arguments to the install commands. However, it will not scan the full dependency tree of these packages.
|
|
17
21
|
|
|
18
22
|
# Usage
|
|
19
23
|
|
|
@@ -84,4 +88,4 @@ npm install suspicious-package --safe-chain-malware-action=prompt
|
|
|
84
88
|
|
|
85
89
|
# Usage in CI/CD
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
[Learn more about Safe Chain CI/CD integration in the Aikido docs.](https://help.aikido.dev/code-scanning/aikido-malware-scanning/malware-scanning-with-safe-chain-in-ci-cd-environments)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikidosec/safe-chain",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"test": "node --test --experimental-test-module-mocks 'src/**/*.spec.js'",
|
|
6
6
|
"test:watch": "node --test --watch --experimental-test-module-mocks 'src/**/*.spec.js'",
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"license": "AGPL-3.0-or-later",
|
|
29
29
|
"description": "The Aikido Safe Chain wraps around the [npm cli](https://github.com/npm/cli), [npx](https://github.com/npm/cli/blob/latest/docs/content/commands/npx.md), [yarn](https://yarnpkg.com/), [pnpm](https://pnpm.io/), and [pnpx](https://pnpm.io/cli/dlx) to provide extra checks before installing new packages. This tool will detect when a package contains malware and prompt you to exit, preventing npm, npx, yarn, pnpm, or pnpx from downloading or running the malware.",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"abbrev": "
|
|
32
|
-
"chalk": "
|
|
33
|
-
"make-fetch-happen": "
|
|
34
|
-
"npm-registry-fetch": "
|
|
35
|
-
"ora": "
|
|
36
|
-
"semver": "
|
|
31
|
+
"abbrev": "3.0.1",
|
|
32
|
+
"chalk": "5.4.1",
|
|
33
|
+
"make-fetch-happen": "14.0.3",
|
|
34
|
+
"npm-registry-fetch": "18.0.2",
|
|
35
|
+
"ora": "8.2.0",
|
|
36
|
+
"semver": "7.7.2"
|
|
37
37
|
},
|
|
38
38
|
"main": "src/main.js",
|
|
39
39
|
"bugs": {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "child_process";
|
|
2
2
|
import * as os from "os";
|
|
3
3
|
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
4
5
|
|
|
5
6
|
export const knownAikidoTools = [
|
|
6
7
|
{ tool: "npm", aikidoCommand: "aikido-npm" },
|
|
@@ -22,15 +23,17 @@ export function doesExecutableExistOnSystem(executableName) {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
export function removeLinesMatchingPattern(filePath, pattern) {
|
|
26
|
+
export function removeLinesMatchingPattern(filePath, pattern, eol) {
|
|
26
27
|
if (!fs.existsSync(filePath)) {
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
eol = eol || os.EOL;
|
|
32
|
+
|
|
30
33
|
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
31
|
-
const lines = fileContent.split(/[\r\n\u2028\u2029]
|
|
34
|
+
const lines = fileContent.split(/[\r\n\u2028\u2029]/);
|
|
32
35
|
const updatedLines = lines.filter((line) => !shouldRemoveLine(line, pattern));
|
|
33
|
-
fs.writeFileSync(filePath, updatedLines.join(
|
|
36
|
+
fs.writeFileSync(filePath, updatedLines.join(eol), "utf-8");
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
const maxLineLength = 100;
|
|
@@ -43,12 +46,17 @@ function shouldRemoveLine(line, pattern) {
|
|
|
43
46
|
|
|
44
47
|
if (line.length > maxLineLength) {
|
|
45
48
|
// safe-chain only adds lines shorter than maxLineLength
|
|
46
|
-
// so if the line is longer, it must be from a different
|
|
49
|
+
// so if the line is longer, it must be from a different
|
|
47
50
|
// source and could be dangerous to remove
|
|
48
51
|
return false;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
if (
|
|
54
|
+
if (
|
|
55
|
+
line.includes("\n") ||
|
|
56
|
+
line.includes("\r") ||
|
|
57
|
+
line.includes("\u2028") ||
|
|
58
|
+
line.includes("\u2029")
|
|
59
|
+
) {
|
|
52
60
|
// If the line contains newlines, something has gone wrong in splitting
|
|
53
61
|
// \u2028 and \u2029 are Unicode line separator characters (line and paragraph separators)
|
|
54
62
|
return false;
|
|
@@ -57,12 +65,25 @@ function shouldRemoveLine(line, pattern) {
|
|
|
57
65
|
return true;
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
export function addLineToFile(filePath, line) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
export function addLineToFile(filePath, line, eol) {
|
|
69
|
+
createFileIfNotExists(filePath);
|
|
70
|
+
|
|
71
|
+
eol = eol || os.EOL;
|
|
64
72
|
|
|
65
73
|
const fileContent = fs.readFileSync(filePath, "utf-8");
|
|
66
|
-
const updatedContent = fileContent +
|
|
74
|
+
const updatedContent = fileContent + eol + line + eol;
|
|
67
75
|
fs.writeFileSync(filePath, updatedContent, "utf-8");
|
|
68
76
|
}
|
|
77
|
+
|
|
78
|
+
function createFileIfNotExists(filePath) {
|
|
79
|
+
if (fs.existsSync(filePath)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const dir = path.dirname(filePath);
|
|
84
|
+
if (!fs.existsSync(dir)) {
|
|
85
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fs.writeFileSync(filePath, "", "utf-8");
|
|
89
|
+
}
|
|
@@ -56,11 +56,13 @@ export async function setup() {
|
|
|
56
56
|
*/
|
|
57
57
|
function setupShell(shell) {
|
|
58
58
|
let success = false;
|
|
59
|
+
let error;
|
|
59
60
|
try {
|
|
60
61
|
shell.teardown(knownAikidoTools); // First, tear down to prevent duplicate aliases
|
|
61
62
|
success = shell.setup(knownAikidoTools);
|
|
62
|
-
} catch {
|
|
63
|
+
} catch (err) {
|
|
63
64
|
success = false;
|
|
65
|
+
error = err;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
if (success) {
|
|
@@ -75,6 +77,13 @@ function setupShell(shell) {
|
|
|
75
77
|
"Setup failed"
|
|
76
78
|
)}. Please check your ${shell.name} configuration.`
|
|
77
79
|
);
|
|
80
|
+
if (error) {
|
|
81
|
+
let message = ` Error: ${error.message}`;
|
|
82
|
+
if (error.code) {
|
|
83
|
+
message += ` (code: ${error.code})`;
|
|
84
|
+
}
|
|
85
|
+
ui.writeError(message);
|
|
86
|
+
}
|
|
78
87
|
}
|
|
79
88
|
|
|
80
89
|
return success;
|
|
@@ -9,6 +9,7 @@ import * as os from "os";
|
|
|
9
9
|
const shellName = "Bash";
|
|
10
10
|
const executableName = "bash";
|
|
11
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.
|
|
12
13
|
|
|
13
14
|
function isInstalled() {
|
|
14
15
|
return doesExecutableExistOnSystem(executableName);
|
|
@@ -19,13 +20,18 @@ function teardown(tools) {
|
|
|
19
20
|
|
|
20
21
|
for (const { tool } of tools) {
|
|
21
22
|
// Remove any existing alias for the tool
|
|
22
|
-
removeLinesMatchingPattern(
|
|
23
|
+
removeLinesMatchingPattern(
|
|
24
|
+
startupFile,
|
|
25
|
+
new RegExp(`^alias\\s+${tool}=`),
|
|
26
|
+
eol
|
|
27
|
+
);
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
// Removes the line that sources the safe-chain bash initialization script (~/.aikido/scripts/init-posix.sh)
|
|
26
31
|
removeLinesMatchingPattern(
|
|
27
32
|
startupFile,
|
|
28
|
-
/^source\s+~\/\.safe-chain\/scripts\/init-posix\.sh
|
|
33
|
+
/^source\s+~\/\.safe-chain\/scripts\/init-posix\.sh/,
|
|
34
|
+
eol
|
|
29
35
|
);
|
|
30
36
|
|
|
31
37
|
return true;
|
|
@@ -36,7 +42,8 @@ function setup() {
|
|
|
36
42
|
|
|
37
43
|
addLineToFile(
|
|
38
44
|
startupFile,
|
|
39
|
-
`source ~/.safe-chain/scripts/init-posix.sh # Safe-chain bash initialization script
|
|
45
|
+
`source ~/.safe-chain/scripts/init-posix.sh # Safe-chain bash initialization script`,
|
|
46
|
+
eol
|
|
40
47
|
);
|
|
41
48
|
|
|
42
49
|
return true;
|
|
@@ -8,6 +8,7 @@ import { execSync } from "child_process";
|
|
|
8
8
|
const shellName = "Fish";
|
|
9
9
|
const executableName = "fish";
|
|
10
10
|
const startupFileCommand = "echo ~/.config/fish/config.fish";
|
|
11
|
+
const eol = "\n"; // When fish runs on Windows (e.g., Git Bash or WSL), it expects LF line endings.
|
|
11
12
|
|
|
12
13
|
function isInstalled() {
|
|
13
14
|
return doesExecutableExistOnSystem(executableName);
|
|
@@ -20,14 +21,16 @@ function teardown(tools) {
|
|
|
20
21
|
// Remove any existing alias for the tool
|
|
21
22
|
removeLinesMatchingPattern(
|
|
22
23
|
startupFile,
|
|
23
|
-
new RegExp(`^alias\\s+${tool}\\s+`)
|
|
24
|
+
new RegExp(`^alias\\s+${tool}\\s+`),
|
|
25
|
+
eol
|
|
24
26
|
);
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
// Removes the line that sources the safe-chain fish initialization script (~/.safe-chain/scripts/init-fish.fish)
|
|
28
30
|
removeLinesMatchingPattern(
|
|
29
31
|
startupFile,
|
|
30
|
-
/^source\s+~\/\.safe-chain\/scripts\/init-fish\.fish
|
|
32
|
+
/^source\s+~\/\.safe-chain\/scripts\/init-fish\.fish/,
|
|
33
|
+
eol
|
|
31
34
|
);
|
|
32
35
|
|
|
33
36
|
return true;
|
|
@@ -38,7 +41,8 @@ function setup() {
|
|
|
38
41
|
|
|
39
42
|
addLineToFile(
|
|
40
43
|
startupFile,
|
|
41
|
-
`source ~/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script
|
|
44
|
+
`source ~/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script`,
|
|
45
|
+
eol
|
|
42
46
|
);
|
|
43
47
|
|
|
44
48
|
return true;
|
|
@@ -8,6 +8,7 @@ import { execSync } from "child_process";
|
|
|
8
8
|
const shellName = "Zsh";
|
|
9
9
|
const executableName = "zsh";
|
|
10
10
|
const startupFileCommand = "echo ${ZDOTDIR:-$HOME}/.zshrc";
|
|
11
|
+
const eol = "\n"; // When zsh runs on Windows (e.g., Git Bash or WSL), it expects LF line endings.
|
|
11
12
|
|
|
12
13
|
function isInstalled() {
|
|
13
14
|
return doesExecutableExistOnSystem(executableName);
|
|
@@ -18,13 +19,18 @@ function teardown(tools) {
|
|
|
18
19
|
|
|
19
20
|
for (const { tool } of tools) {
|
|
20
21
|
// Remove any existing alias for the tool
|
|
21
|
-
removeLinesMatchingPattern(
|
|
22
|
+
removeLinesMatchingPattern(
|
|
23
|
+
startupFile,
|
|
24
|
+
new RegExp(`^alias\\s+${tool}=`),
|
|
25
|
+
eol
|
|
26
|
+
);
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
// Removes the line that sources the safe-chain zsh initialization script (~/.aikido/scripts/init-posix.sh)
|
|
25
30
|
removeLinesMatchingPattern(
|
|
26
31
|
startupFile,
|
|
27
|
-
/^source\s+~\/\.safe-chain\/scripts\/init-posix\.sh
|
|
32
|
+
/^source\s+~\/\.safe-chain\/scripts\/init-posix\.sh/,
|
|
33
|
+
eol
|
|
28
34
|
);
|
|
29
35
|
|
|
30
36
|
return true;
|
|
@@ -35,7 +41,8 @@ function setup() {
|
|
|
35
41
|
|
|
36
42
|
addLineToFile(
|
|
37
43
|
startupFile,
|
|
38
|
-
`source ~/.safe-chain/scripts/init-posix.sh # Safe-chain Zsh initialization script
|
|
44
|
+
`source ~/.safe-chain/scripts/init-posix.sh # Safe-chain Zsh initialization script`,
|
|
45
|
+
eol
|
|
39
46
|
);
|
|
40
47
|
|
|
41
48
|
return true;
|