@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 +9 -1
- package/npm-shrinkwrap.json +1 -1
- package/package.json +1 -1
- package/src/scanning/malwareDatabase.js +41 -38
- package/src/scanning/newPackagesListCache.js +16 -19
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
|
|
package/npm-shrinkwrap.json
CHANGED
package/package.json
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
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
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
|
26
|
-
if (
|
|
27
|
-
|
|
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
|
/**
|