@lateos/npm-scan 0.18.0 → 0.18.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/.dockerignore +20 -20
- package/.husky/pre-commit +1 -1
- package/CHANGELOG.md +199 -199
- package/LICENSING.md +19 -19
- package/README.de.md +708 -708
- package/README.fr.md +707 -707
- package/README.ja.md +704 -704
- package/README.md +826 -826
- package/README.zh.md +708 -708
- package/SECURITY.md +72 -72
- package/backend/cra.js +68 -68
- package/backend/db/schema.sql +32 -32
- package/backend/db.js +88 -88
- package/backend/detectors/atk-001-lifecycle.js +17 -17
- package/backend/detectors/atk-002-obfusc.js +261 -261
- package/backend/detectors/atk-003-creds.js +13 -13
- package/backend/detectors/atk-004-persist.js +13 -13
- package/backend/detectors/atk-005-exfil.js +13 -13
- package/backend/detectors/atk-006-depconf.js +14 -14
- package/backend/detectors/atk-007-typosquat.js +34 -34
- package/backend/detectors/atk-008-tarball-tamper.js +91 -91
- package/backend/detectors/atk-009-dormant-trigger.js +62 -62
- package/backend/detectors/atk-010-sandbox-evasion.js +50 -50
- package/backend/detectors/atk-011-transitive-prop.js +76 -76
- package/backend/detectors/cve-2026-48710-badhost/codePattern.js +99 -99
- package/backend/detectors/cve-2026-48710-badhost/findings.js +105 -105
- package/backend/detectors/cve-2026-48710-badhost/index.js +15 -15
- package/backend/detectors/cve-2026-48710-badhost/manifest.js +305 -305
- package/backend/detectors/cve-2026-48710-badhost/transitive.js +189 -189
- package/backend/detectors/hf-impersonation/index.js +396 -396
- package/backend/detectors/hf-impersonation/jaro-winkler.js +44 -44
- package/backend/detectors/hf-impersonation/known-orgs.js +5 -5
- package/backend/detectors/hf-impersonation/simhash.js +46 -46
- package/backend/detectors/index.js +75 -75
- package/backend/detectors/megalodon/d1-workflow-scan.js +147 -147
- package/backend/detectors/megalodon/d2-credential-harvest.js +61 -61
- package/backend/detectors/megalodon/d3-publish-velocity.js +67 -67
- package/backend/detectors/megalodon/d4-publisher-drift.js +124 -124
- package/backend/detectors/megalodon/d5-bot-commit-identity.js +3 -3
- package/backend/detectors/megalodon/d6-date-anachronism.js +3 -3
- package/backend/detectors/megalodon/index.js +80 -80
- package/backend/detectors/megalodon/types.js +9 -9
- package/backend/detectors/mini-shai-hulud/d1-burst-publish.js +42 -42
- package/backend/detectors/mini-shai-hulud/d2-sibling-compromise.js +116 -116
- package/backend/detectors/mini-shai-hulud/d3-slsa-mismatch.js +72 -72
- package/backend/detectors/mini-shai-hulud/d4-maintainer-anomaly.js +45 -45
- package/backend/detectors/mini-shai-hulud/d5-ioc-check.js +95 -95
- package/backend/detectors/mini-shai-hulud/d6-token-exfil.js +38 -38
- package/backend/detectors/mini-shai-hulud/index.js +118 -118
- package/backend/detectors/mini-shai-hulud/iocs.json +79 -79
- package/backend/fetch.js +175 -175
- package/backend/index.js +4 -4
- package/backend/license.js +89 -89
- package/backend/lockfile.js +379 -379
- package/backend/pdf.js +245 -245
- package/backend/policy.js +193 -193
- package/backend/report.js +254 -254
- package/backend/sbom.js +66 -66
- package/backend/siem/cef.js +32 -32
- package/backend/siem/ecs.js +40 -40
- package/backend/siem/index.js +18 -18
- package/backend/siem/qradar.js +56 -56
- package/backend/siem/sentinel.js +27 -27
- package/backend/vsix-scan/detectors/activation-event-risk.js +116 -116
- package/backend/vsix-scan/detectors/burst-publish.js +52 -52
- package/backend/vsix-scan/detectors/exfil-pattern.js +88 -88
- package/backend/vsix-scan/detectors/known-ioc.js +105 -105
- package/backend/vsix-scan/detectors/orphan-commit-fetch.js +69 -69
- package/backend/vsix-scan/detectors/publisher-anomaly.js +70 -70
- package/backend/vsix-scan/index.js +183 -183
- package/backend/vsix-scan/marketplace-client.js +145 -145
- package/backend/vsix-scan/vsix-iocs.json +31 -31
- package/cli/cli.js +458 -458
- package/deploy/helm/npm-scan/Chart.yaml +21 -21
- package/deploy/helm/npm-scan/templates/_helpers.tpl +8 -8
- package/deploy/helm/npm-scan/templates/api.yaml +93 -93
- package/deploy/helm/npm-scan/templates/ingress.yaml +27 -27
- package/deploy/helm/npm-scan/templates/postgresql.yaml +66 -66
- package/deploy/helm/npm-scan/templates/secrets.yaml +18 -18
- package/deploy/helm/npm-scan/templates/worker.yaml +31 -31
- package/deploy/helm/npm-scan/values.byoc.yaml +74 -74
- package/deploy/helm/npm-scan/values.yaml +102 -102
- package/package.json +57 -57
- package/scripts/download-corpus.js +30 -30
- package/scripts/gen-mal-corpus.js +34 -34
- package/test/fixtures/lockfiles/npm-lock.json +68 -68
- package/test/fixtures/lockfiles/pnpm-lock.yaml +117 -117
- package/test/fixtures/lockfiles/yarn.lock +103 -103
- package/test/fixtures/mock-data.js +69 -69
package/backend/license.js
CHANGED
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
import { createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
-
|
|
3
|
-
const HMAC_KEY = process.env.NPM_SCAN_LICENSE_SECRET || 'npm-scan-default-dev-key';
|
|
4
|
-
|
|
5
|
-
const FEATURE_TIERS = {
|
|
6
|
-
community: [],
|
|
7
|
-
premium: ['sandbox', 'siem', 'cra', 'nist-pdf', 'rest-api', 'webhooks', 'helm'],
|
|
8
|
-
enterprise: ['sandbox', 'siem', 'cra', 'nist-pdf', 'rest-api', 'webhooks', 'helm', 'sso', 'audit-logs', 'pg-backend', 'kubernetes'],
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const ALL_FEATURES = Object.values(FEATURE_TIERS).flat();
|
|
12
|
-
const ALLOWED_UNLOCKED = ['sbom', 'nist-html', 'html-report', 'sqlite'];
|
|
13
|
-
|
|
14
|
-
function generateSignature(payload) {
|
|
15
|
-
return createHmac('sha256', HMAC_KEY).update(JSON.stringify(payload)).digest('hex');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function generateKey(edition, options = {}) {
|
|
19
|
-
const payload = {
|
|
20
|
-
edition,
|
|
21
|
-
issued: new Date().toISOString(),
|
|
22
|
-
exp: options.expiresAt || null,
|
|
23
|
-
seats: options.seats || 1,
|
|
24
|
-
org: options.org || null,
|
|
25
|
-
};
|
|
26
|
-
const sig = generateSignature(payload);
|
|
27
|
-
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
28
|
-
return `npm-scan-${edition}-${encoded}.${sig}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function validateLicense(key, feature = '*') {
|
|
32
|
-
if (!key) {
|
|
33
|
-
throw new Error('No license key provided');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (feature === 'scan' || ALLOWED_UNLOCKED.includes(feature)) {
|
|
37
|
-
return { edition: 'community', features: ALL_FEATURES };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const parts = key.split('-');
|
|
41
|
-
if (parts.length < 4 || !key.includes('.')) {
|
|
42
|
-
throw new Error('Invalid license key format');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const edition = parts[2];
|
|
46
|
-
const encodedPayload = parts.slice(3).join('-').split('.')[0];
|
|
47
|
-
const sig = key.split('.')[1];
|
|
48
|
-
|
|
49
|
-
let payload;
|
|
50
|
-
try {
|
|
51
|
-
payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));
|
|
52
|
-
} catch {
|
|
53
|
-
throw new Error('Invalid license key payload');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const expectedSig = generateSignature(payload);
|
|
57
|
-
const sigBuf = Buffer.from(sig, 'hex');
|
|
58
|
-
const expectedBuf = Buffer.from(expectedSig, 'hex');
|
|
59
|
-
if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
|
|
60
|
-
throw new Error('Invalid license key signature');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (payload.exp && new Date(payload.exp) < new Date()) {
|
|
64
|
-
throw new Error('License key expired');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const allowed = FEATURE_TIERS[edition];
|
|
68
|
-
if (!allowed) {
|
|
69
|
-
throw new Error(`Unknown license edition: ${edition}`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (feature !== '*' && !allowed.includes(feature) && !ALLOWED_UNLOCKED.includes(feature)) {
|
|
73
|
-
throw new Error(`Feature "${feature}" requires ${edition === 'community' ? 'premium' : 'enterprise'} license`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return { edition, features: allowed, ...payload };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function isFeatureEnabled(feature, licenseKey = process.env.NPM_SCAN_LICENSE_KEY) {
|
|
80
|
-
try {
|
|
81
|
-
if (!licenseKey) {
|
|
82
|
-
const unlocked = feature === 'scan' || ALLOWED_UNLOCKED.includes(feature);
|
|
83
|
-
if (unlocked) return true;
|
|
84
|
-
}
|
|
85
|
-
validateLicense(licenseKey, feature);
|
|
86
|
-
return true;
|
|
87
|
-
} catch {
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
+
|
|
3
|
+
const HMAC_KEY = process.env.NPM_SCAN_LICENSE_SECRET || 'npm-scan-default-dev-key';
|
|
4
|
+
|
|
5
|
+
const FEATURE_TIERS = {
|
|
6
|
+
community: [],
|
|
7
|
+
premium: ['sandbox', 'siem', 'cra', 'nist-pdf', 'rest-api', 'webhooks', 'helm'],
|
|
8
|
+
enterprise: ['sandbox', 'siem', 'cra', 'nist-pdf', 'rest-api', 'webhooks', 'helm', 'sso', 'audit-logs', 'pg-backend', 'kubernetes'],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const ALL_FEATURES = Object.values(FEATURE_TIERS).flat();
|
|
12
|
+
const ALLOWED_UNLOCKED = ['sbom', 'nist-html', 'html-report', 'sqlite'];
|
|
13
|
+
|
|
14
|
+
function generateSignature(payload) {
|
|
15
|
+
return createHmac('sha256', HMAC_KEY).update(JSON.stringify(payload)).digest('hex');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function generateKey(edition, options = {}) {
|
|
19
|
+
const payload = {
|
|
20
|
+
edition,
|
|
21
|
+
issued: new Date().toISOString(),
|
|
22
|
+
exp: options.expiresAt || null,
|
|
23
|
+
seats: options.seats || 1,
|
|
24
|
+
org: options.org || null,
|
|
25
|
+
};
|
|
26
|
+
const sig = generateSignature(payload);
|
|
27
|
+
const encoded = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
28
|
+
return `npm-scan-${edition}-${encoded}.${sig}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function validateLicense(key, feature = '*') {
|
|
32
|
+
if (!key) {
|
|
33
|
+
throw new Error('No license key provided');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (feature === 'scan' || ALLOWED_UNLOCKED.includes(feature)) {
|
|
37
|
+
return { edition: 'community', features: ALL_FEATURES };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const parts = key.split('-');
|
|
41
|
+
if (parts.length < 4 || !key.includes('.')) {
|
|
42
|
+
throw new Error('Invalid license key format');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const edition = parts[2];
|
|
46
|
+
const encodedPayload = parts.slice(3).join('-').split('.')[0];
|
|
47
|
+
const sig = key.split('.')[1];
|
|
48
|
+
|
|
49
|
+
let payload;
|
|
50
|
+
try {
|
|
51
|
+
payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error('Invalid license key payload');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const expectedSig = generateSignature(payload);
|
|
57
|
+
const sigBuf = Buffer.from(sig, 'hex');
|
|
58
|
+
const expectedBuf = Buffer.from(expectedSig, 'hex');
|
|
59
|
+
if (sigBuf.length !== expectedBuf.length || !timingSafeEqual(sigBuf, expectedBuf)) {
|
|
60
|
+
throw new Error('Invalid license key signature');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (payload.exp && new Date(payload.exp) < new Date()) {
|
|
64
|
+
throw new Error('License key expired');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const allowed = FEATURE_TIERS[edition];
|
|
68
|
+
if (!allowed) {
|
|
69
|
+
throw new Error(`Unknown license edition: ${edition}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (feature !== '*' && !allowed.includes(feature) && !ALLOWED_UNLOCKED.includes(feature)) {
|
|
73
|
+
throw new Error(`Feature "${feature}" requires ${edition === 'community' ? 'premium' : 'enterprise'} license`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { edition, features: allowed, ...payload };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isFeatureEnabled(feature, licenseKey = process.env.NPM_SCAN_LICENSE_KEY) {
|
|
80
|
+
try {
|
|
81
|
+
if (!licenseKey) {
|
|
82
|
+
const unlocked = feature === 'scan' || ALLOWED_UNLOCKED.includes(feature);
|
|
83
|
+
if (unlocked) return true;
|
|
84
|
+
}
|
|
85
|
+
validateLicense(licenseKey, feature);
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
90
|
}
|