@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 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
- - **npx**
13
- - **yarn**
14
- - **pnpm**
15
- - **pnpx**
16
- - 🚧 **bun** Coming soon
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
- 🚧 Support for CI/CD environments is coming soon...
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.22",
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": "^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"
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(os.EOL), "utf-8");
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 (line.includes("\n") || line.includes("\r") || line.includes("\u2028") || line.includes("\u2029")) {
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
- if (!fs.existsSync(filePath)) {
62
- fs.writeFileSync(filePath, "", "utf-8");
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 + os.EOL + line;
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(startupFile, new RegExp(`^alias\\s+${tool}=`));
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(startupFile, new RegExp(`^alias\\s+${tool}=`));
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;