muaddib-scanner 2.4.3 → 2.4.5

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.
@@ -1,139 +1,139 @@
1
- const { detectSuddenLifecycleChange } = require('./temporal-analysis.js');
2
- const { detectSuddenAstChanges } = require('./temporal-ast-diff.js');
3
- const { detectPublishAnomaly } = require('./publish-anomaly.js');
4
- const { detectMaintainerChange } = require('./maintainer-change.js');
5
-
6
- /**
7
- * Run all temporal analyses (lifecycle, AST diff, publish anomaly, maintainer change).
8
- * Each analysis is gated by its own option flag.
9
- * @param {string} targetPath - scan target
10
- * @param {Object} options - scan options (temporal, temporalAst, temporalPublish, temporalMaintainer, _capture, json)
11
- * @param {string[]} pkgNames - installed package names from listInstalledPackages()
12
- * @returns {Promise<Array>} array of threat objects
13
- */
14
- async function runTemporalAnalyses(targetPath, options, pkgNames) {
15
- const threats = [];
16
-
17
- // Temporal analysis (--temporal flag, off by default)
18
- if (options.temporal) {
19
- if (!options._capture && !options.json) {
20
- console.log('[TEMPORAL] Analyzing lifecycle script changes (this makes network requests)...\n');
21
- }
22
- {
23
- const TEMPORAL_CONCURRENCY = 5;
24
- for (let i = 0; i < pkgNames.length; i += TEMPORAL_CONCURRENCY) {
25
- const batch = pkgNames.slice(i, i + TEMPORAL_CONCURRENCY);
26
- const results = await Promise.allSettled(
27
- batch.map(name => detectSuddenLifecycleChange(name))
28
- );
29
- for (const r of results) {
30
- if (r.status !== 'fulfilled' || !r.value.suspicious) continue;
31
- const det = r.value;
32
- for (const f of det.findings) {
33
- const isCriticalScript = ['preinstall', 'install', 'postinstall'].includes(f.script);
34
- const threatType = f.type === 'lifecycle_added'
35
- ? (isCriticalScript ? 'lifecycle_added_critical' : 'lifecycle_added_high')
36
- : 'lifecycle_modified';
37
- threats.push({
38
- type: threatType,
39
- severity: f.severity,
40
- message: `Package "${det.packageName}" v${det.latestVersion} ${f.type === 'lifecycle_added' ? 'added' : 'modified'} ${f.script} script (not in v${det.previousVersion}). Script: "${f.type === 'lifecycle_modified' ? f.newValue : f.value}"`,
41
- file: `node_modules/${det.packageName}/package.json`
42
- });
43
- }
44
- }
45
- }
46
- }
47
- }
48
-
49
- // Temporal AST analysis (--temporal-ast or --temporal-full flag, off by default)
50
- if (options.temporalAst) {
51
- if (!options._capture && !options.json) {
52
- console.log('[TEMPORAL-AST] Analyzing dangerous API changes (this downloads tarballs)...\n');
53
- }
54
- {
55
- const AST_CONCURRENCY = 3;
56
- for (let i = 0; i < pkgNames.length; i += AST_CONCURRENCY) {
57
- const batch = pkgNames.slice(i, i + AST_CONCURRENCY);
58
- const results = await Promise.allSettled(
59
- batch.map(name => detectSuddenAstChanges(name))
60
- );
61
- for (const r of results) {
62
- if (r.status !== 'fulfilled' || !r.value.suspicious) continue;
63
- const det = r.value;
64
- for (const f of det.findings) {
65
- const threatType = f.severity === 'CRITICAL' ? 'dangerous_api_added_critical'
66
- : f.severity === 'HIGH' ? 'dangerous_api_added_high'
67
- : 'dangerous_api_added_medium';
68
- threats.push({
69
- type: threatType,
70
- severity: f.severity,
71
- message: `Package "${det.packageName}" v${det.latestVersion} now uses ${f.pattern} (not in v${det.previousVersion})`,
72
- file: `node_modules/${det.packageName}/package.json`
73
- });
74
- }
75
- }
76
- }
77
- }
78
- }
79
-
80
- // Temporal publish frequency analysis (--temporal-publish or --temporal-full flag, off by default)
81
- if (options.temporalPublish) {
82
- if (!options._capture && !options.json) {
83
- console.log('[TEMPORAL-PUBLISH] Analyzing publish frequency anomalies (this makes network requests)...\n');
84
- }
85
- {
86
- const PUBLISH_CONCURRENCY = 5;
87
- for (let i = 0; i < pkgNames.length; i += PUBLISH_CONCURRENCY) {
88
- const batch = pkgNames.slice(i, i + PUBLISH_CONCURRENCY);
89
- const results = await Promise.allSettled(
90
- batch.map(name => detectPublishAnomaly(name))
91
- );
92
- for (const r of results) {
93
- if (r.status !== 'fulfilled' || !r.value.suspicious) continue;
94
- const det = r.value;
95
- for (const a of det.anomalies) {
96
- threats.push({
97
- type: a.type,
98
- severity: a.severity,
99
- message: a.description,
100
- file: `node_modules/${det.packageName}/package.json`
101
- });
102
- }
103
- }
104
- }
105
- }
106
- }
107
-
108
- // Temporal maintainer change analysis (--temporal-maintainer or --temporal-full flag, off by default)
109
- if (options.temporalMaintainer) {
110
- if (!options._capture && !options.json) {
111
- console.log('[TEMPORAL-MAINTAINER] Analyzing maintainer changes (this makes network requests)...\n');
112
- }
113
- {
114
- const MAINTAINER_CONCURRENCY = 5;
115
- for (let i = 0; i < pkgNames.length; i += MAINTAINER_CONCURRENCY) {
116
- const batch = pkgNames.slice(i, i + MAINTAINER_CONCURRENCY);
117
- const results = await Promise.allSettled(
118
- batch.map(name => detectMaintainerChange(name))
119
- );
120
- for (const r of results) {
121
- if (r.status !== 'fulfilled' || !r.value.suspicious) continue;
122
- const det = r.value;
123
- for (const f of det.findings) {
124
- threats.push({
125
- type: f.type,
126
- severity: f.severity,
127
- message: f.description,
128
- file: `node_modules/${det.packageName}/package.json`
129
- });
130
- }
131
- }
132
- }
133
- }
134
- }
135
-
136
- return threats;
137
- }
138
-
139
- module.exports = { runTemporalAnalyses };
1
+ const { detectSuddenLifecycleChange } = require('./temporal-analysis.js');
2
+ const { detectSuddenAstChanges } = require('./temporal-ast-diff.js');
3
+ const { detectPublishAnomaly } = require('./publish-anomaly.js');
4
+ const { detectMaintainerChange } = require('./maintainer-change.js');
5
+
6
+ /**
7
+ * Run all temporal analyses (lifecycle, AST diff, publish anomaly, maintainer change).
8
+ * Each analysis is gated by its own option flag.
9
+ * @param {string} targetPath - scan target
10
+ * @param {Object} options - scan options (temporal, temporalAst, temporalPublish, temporalMaintainer, _capture, json)
11
+ * @param {string[]} pkgNames - installed package names from listInstalledPackages()
12
+ * @returns {Promise<Array>} array of threat objects
13
+ */
14
+ async function runTemporalAnalyses(targetPath, options, pkgNames) {
15
+ const threats = [];
16
+
17
+ // Temporal analysis (--temporal flag, off by default)
18
+ if (options.temporal) {
19
+ if (!options._capture && !options.json) {
20
+ console.log('[TEMPORAL] Analyzing lifecycle script changes (this makes network requests)...\n');
21
+ }
22
+ {
23
+ const TEMPORAL_CONCURRENCY = 5;
24
+ for (let i = 0; i < pkgNames.length; i += TEMPORAL_CONCURRENCY) {
25
+ const batch = pkgNames.slice(i, i + TEMPORAL_CONCURRENCY);
26
+ const results = await Promise.allSettled(
27
+ batch.map(name => detectSuddenLifecycleChange(name))
28
+ );
29
+ for (const r of results) {
30
+ if (r.status !== 'fulfilled' || !r.value.suspicious) continue;
31
+ const det = r.value;
32
+ for (const f of det.findings) {
33
+ const isCriticalScript = ['preinstall', 'install', 'postinstall'].includes(f.script);
34
+ const threatType = f.type === 'lifecycle_added'
35
+ ? (isCriticalScript ? 'lifecycle_added_critical' : 'lifecycle_added_high')
36
+ : 'lifecycle_modified';
37
+ threats.push({
38
+ type: threatType,
39
+ severity: f.severity,
40
+ message: `Package "${det.packageName}" v${det.latestVersion} ${f.type === 'lifecycle_added' ? 'added' : 'modified'} ${f.script} script (not in v${det.previousVersion}). Script: "${f.type === 'lifecycle_modified' ? f.newValue : f.value}"`,
41
+ file: `node_modules/${det.packageName}/package.json`
42
+ });
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ // Temporal AST analysis (--temporal-ast or --temporal-full flag, off by default)
50
+ if (options.temporalAst) {
51
+ if (!options._capture && !options.json) {
52
+ console.log('[TEMPORAL-AST] Analyzing dangerous API changes (this downloads tarballs)...\n');
53
+ }
54
+ {
55
+ const AST_CONCURRENCY = 3;
56
+ for (let i = 0; i < pkgNames.length; i += AST_CONCURRENCY) {
57
+ const batch = pkgNames.slice(i, i + AST_CONCURRENCY);
58
+ const results = await Promise.allSettled(
59
+ batch.map(name => detectSuddenAstChanges(name))
60
+ );
61
+ for (const r of results) {
62
+ if (r.status !== 'fulfilled' || !r.value.suspicious) continue;
63
+ const det = r.value;
64
+ for (const f of det.findings) {
65
+ const threatType = f.severity === 'CRITICAL' ? 'dangerous_api_added_critical'
66
+ : f.severity === 'HIGH' ? 'dangerous_api_added_high'
67
+ : 'dangerous_api_added_medium';
68
+ threats.push({
69
+ type: threatType,
70
+ severity: f.severity,
71
+ message: `Package "${det.packageName}" v${det.latestVersion} now uses ${f.pattern} (not in v${det.previousVersion})`,
72
+ file: `node_modules/${det.packageName}/package.json`
73
+ });
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ // Temporal publish frequency analysis (--temporal-publish or --temporal-full flag, off by default)
81
+ if (options.temporalPublish) {
82
+ if (!options._capture && !options.json) {
83
+ console.log('[TEMPORAL-PUBLISH] Analyzing publish frequency anomalies (this makes network requests)...\n');
84
+ }
85
+ {
86
+ const PUBLISH_CONCURRENCY = 5;
87
+ for (let i = 0; i < pkgNames.length; i += PUBLISH_CONCURRENCY) {
88
+ const batch = pkgNames.slice(i, i + PUBLISH_CONCURRENCY);
89
+ const results = await Promise.allSettled(
90
+ batch.map(name => detectPublishAnomaly(name))
91
+ );
92
+ for (const r of results) {
93
+ if (r.status !== 'fulfilled' || !r.value.suspicious) continue;
94
+ const det = r.value;
95
+ for (const a of det.anomalies) {
96
+ threats.push({
97
+ type: a.type,
98
+ severity: a.severity,
99
+ message: a.description,
100
+ file: `node_modules/${det.packageName}/package.json`
101
+ });
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ // Temporal maintainer change analysis (--temporal-maintainer or --temporal-full flag, off by default)
109
+ if (options.temporalMaintainer) {
110
+ if (!options._capture && !options.json) {
111
+ console.log('[TEMPORAL-MAINTAINER] Analyzing maintainer changes (this makes network requests)...\n');
112
+ }
113
+ {
114
+ const MAINTAINER_CONCURRENCY = 5;
115
+ for (let i = 0; i < pkgNames.length; i += MAINTAINER_CONCURRENCY) {
116
+ const batch = pkgNames.slice(i, i + MAINTAINER_CONCURRENCY);
117
+ const results = await Promise.allSettled(
118
+ batch.map(name => detectMaintainerChange(name))
119
+ );
120
+ for (const r of results) {
121
+ if (r.status !== 'fulfilled' || !r.value.suspicious) continue;
122
+ const det = r.value;
123
+ for (const f of det.findings) {
124
+ threats.push({
125
+ type: f.type,
126
+ severity: f.severity,
127
+ message: f.description,
128
+ file: `node_modules/${det.packageName}/package.json`
129
+ });
130
+ }
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ return threats;
137
+ }
138
+
139
+ module.exports = { runTemporalAnalyses };