@aikidosec/safe-chain 1.5.0 → 1.5.1

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
@@ -10,6 +10,14 @@
10
10
  - ✅ **Blocks packages newer than 48 hours** without breaking your build
11
11
  - ✅ **Tokenless, free, no build data shared**
12
12
 
13
+ ## Need protection beyond npm & PyPI?
14
+
15
+ [Aikido Endpoint](https://www.aikido.dev/protect/endpoint-protection?utm_source=github.com&utm_medium=referral&utm_campaign=safechain) builds on Safe Chain, extending package and extension security across more ecosystems: **npm**, **PyPI**, **Maven**, **NuGet**, **VS Code**, **Open VSX** - (Cursor, Windsurf, Kiro, Vs Codium, ...), **Chrome extensions**, **Skills.sh AI skills** and more.
16
+
17
+ Get centralized policy management, request-and-approval workflows, and visibility across every developer workstation in your org. Powered by the same Aikido Intel feed. Deploy it manually or manage it through your MDM tool (Jamf, Fleet, or Iru).
18
+
19
+ ---
20
+
13
21
  Aikido Safe Chain supports the following package managers:
14
22
 
15
23
  - 📦 **npm**
@@ -463,7 +471,7 @@ steps:
463
471
  name: Install
464
472
  script:
465
473
  - curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
466
- - export PATH=~/.safe-chain/shims:$PATH
474
+ - export PATH=~/.safe-chain/shims:~/.safe-chain/bin:$PATH
467
475
  - npm ci
468
476
  ```
469
477
 
@@ -3112,7 +3112,7 @@
3112
3112
  },
3113
3113
  "packages/safe-chain": {
3114
3114
  "name": "@aikidosec/safe-chain",
3115
- "version": "1.5.0",
3115
+ "version": "1.5.1",
3116
3116
  "license": "AGPL-3.0-or-later",
3117
3117
  "dependencies": {
3118
3118
  "certifi": "14.5.15",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikidosec/safe-chain",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
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'",
@@ -15,8 +15,12 @@ import { getEcoSystem, ECOSYSTEM_PY } from "../config/settings.js";
15
15
  * @property {function(string, string): boolean} isMalware
16
16
  */
17
17
 
18
- /** @type {MalwareDatabase | null} */
19
- let cachedMalwareDatabase = null;
18
+ // Caching the Promise (rather than the resolved database) prevents duplicate fetches. If we cached the resolved
19
+ // value, multiple callers could pass the null-check before the first fetch completes (because each `await` yields
20
+ // control back to the event loop, allowing other callers to run). Since the Promise assignment is synchronous, all
21
+ // concurrent callers see it immediately and share a single fetch.
22
+ /** @type {Promise<MalwareDatabase> | null} */
23
+ let cachedMalwareDatabasePromise = null;
20
24
 
21
25
  /**
22
26
  * Normalize package name for comparison.
@@ -34,45 +38,44 @@ function normalizePackageName(name) {
34
38
  return name;
35
39
  }
36
40
 
37
- export async function openMalwareDatabase() {
38
- if (cachedMalwareDatabase) {
39
- return cachedMalwareDatabase;
40
- }
41
-
42
- const malwareDatabase = await getMalwareDatabase();
43
-
44
- /**
45
- * @param {string} name
46
- * @param {string} version
47
- * @returns {string}
48
- */
49
- function getPackageStatus(name, version) {
50
- const normalizedName = normalizePackageName(name);
51
- const packageData = malwareDatabase.find(
52
- (pkg) => {
53
- const normalizedPkgName = normalizePackageName(pkg.package_name);
54
- return normalizedPkgName === normalizedName &&
55
- (pkg.version === version || pkg.version === "*");
41
+ export function openMalwareDatabase() {
42
+ if (!cachedMalwareDatabasePromise) {
43
+ cachedMalwareDatabasePromise = getMalwareDatabase().then((malwareDatabase) => {
44
+ /**
45
+ * @param {string} name
46
+ * @param {string} version
47
+ * @returns {string}
48
+ */
49
+ function getPackageStatus(name, version) {
50
+ const normalizedName = normalizePackageName(name);
51
+ const packageData = malwareDatabase.find(
52
+ (pkg) => {
53
+ const normalizedPkgName = normalizePackageName(pkg.package_name);
54
+ return normalizedPkgName === normalizedName &&
55
+ (pkg.version === version || pkg.version === "*");
56
+ }
57
+ );
58
+
59
+ if (!packageData) {
60
+ return MALWARE_STATUS_OK;
61
+ }
62
+
63
+ return packageData.reason;
56
64
  }
57
- );
58
65
 
59
- if (!packageData) {
60
- return MALWARE_STATUS_OK;
61
- }
62
-
63
- return packageData.reason;
66
+ return {
67
+ getPackageStatus,
68
+ isMalware: (/** @type {string} */ name, /** @type {string} */ version) => {
69
+ const status = getPackageStatus(name, version);
70
+ return isMalwareStatus(status);
71
+ },
72
+ };
73
+ }).catch((error) => {
74
+ cachedMalwareDatabasePromise = null;
75
+ throw error;
76
+ });
64
77
  }
65
-
66
- // This implicitly caches the malware database
67
- // that's closed over by the getPackageStatus function
68
- cachedMalwareDatabase = {
69
- getPackageStatus,
70
- isMalware: (name, version) => {
71
- const status = getPackageStatus(name, version);
72
- return isMalwareStatus(status);
73
- },
74
- };
75
- return cachedMalwareDatabase;
78
+ return cachedMalwareDatabasePromise;
76
79
  }
77
80
 
78
81
  /**
@@ -16,30 +16,27 @@ import { warnOnceAboutUnavailableDatabase } from "./newPackagesDatabaseWarnings.
16
16
  */
17
17
 
18
18
  // Shared per-process cache to avoid rebuilding the same feed-backed database on each request.
19
- /** @type {NewPackagesDatabase | null} */
20
- let cachedNewPackagesDatabase = null;
19
+ // Caching the Promise (rather than the resolved database) prevents duplicate fetches. If we cached the resolved
20
+ // value, multiple callers could pass the null-check before the first fetch completes (because each `await` yields
21
+ // control back to the event loop, allowing other callers to run). Since the Promise assignment is synchronous, all
22
+ // concurrent callers see it immediately and share a single fetch.
23
+ /** @type {Promise<NewPackagesDatabase> | null} */
24
+ let cachedNewPackagesDatabasePromise = null;
21
25
 
22
26
  /**
23
27
  * @returns {Promise<NewPackagesDatabase>}
24
28
  */
25
- export async function openNewPackagesDatabase() {
26
- if (cachedNewPackagesDatabase) {
27
- return cachedNewPackagesDatabase;
29
+ export function openNewPackagesDatabase() {
30
+ if (!cachedNewPackagesDatabasePromise) {
31
+ cachedNewPackagesDatabasePromise = getNewPackagesList()
32
+ .then((newPackagesList) => buildNewPackagesDatabase(newPackagesList))
33
+ .catch((/** @type {any} */ error) => {
34
+ warnOnceAboutUnavailableDatabase(error);
35
+ cachedNewPackagesDatabasePromise = null;
36
+ return { isNewlyReleasedPackage: () => false };
37
+ });
28
38
  }
29
-
30
- /** @type {import("../api/aikido.js").NewPackageEntry[]} */
31
- let newPackagesList;
32
-
33
- try {
34
- newPackagesList = await getNewPackagesList();
35
- } catch (/** @type {any} */ error) {
36
- warnOnceAboutUnavailableDatabase(error);
37
- cachedNewPackagesDatabase = { isNewlyReleasedPackage: () => false };
38
- return cachedNewPackagesDatabase;
39
- }
40
-
41
- cachedNewPackagesDatabase = buildNewPackagesDatabase(newPackagesList);
42
- return cachedNewPackagesDatabase;
39
+ return cachedNewPackagesDatabasePromise;
43
40
  }
44
41
 
45
42
  /**