@lateos/npm-scan 0.18.2 → 0.18.3
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 +233 -233
- 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 +81 -81
- 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
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
import { transitiveDependencyFinding } from './findings.js';
|
|
2
|
-
import { parseRequirementsTxt, parsePyprojectToml, parsePoetryLock, parsePipfile, parseSetupPy, parseSetupCfg } from './manifest.js';
|
|
3
|
-
|
|
4
|
-
const TIER_1_PACKAGES = [
|
|
5
|
-
'fastapi',
|
|
6
|
-
'vllm',
|
|
7
|
-
'litellm',
|
|
8
|
-
'bentoml',
|
|
9
|
-
'text-generation-inference',
|
|
10
|
-
'ray-serve',
|
|
11
|
-
'ray[serve]',
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
const TIER_2_PACKAGES = [
|
|
15
|
-
'langserve',
|
|
16
|
-
'fastapi-mcp',
|
|
17
|
-
'mcp',
|
|
18
|
-
'starlette-admin',
|
|
19
|
-
'piccolo-api',
|
|
20
|
-
'fastapi-users',
|
|
21
|
-
'broadcaster',
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
function normalizePkgName(name) {
|
|
25
|
-
return name.replace(/["'\[\]]/g, '').trim().toLowerCase();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function findPackagesInManifests(allFiles) {
|
|
29
|
-
const packages = new Set();
|
|
30
|
-
|
|
31
|
-
for (const file of (allFiles || [])) {
|
|
32
|
-
const content = typeof file.content === 'string' ? file.content : '';
|
|
33
|
-
if (!content) continue;
|
|
34
|
-
const path = file.path || '';
|
|
35
|
-
|
|
36
|
-
let deps = [];
|
|
37
|
-
|
|
38
|
-
if (path === 'requirements.txt' || path.match(/^requirements\/.*\.txt$/)) {
|
|
39
|
-
const lines = content.split('\n');
|
|
40
|
-
for (const line of lines) {
|
|
41
|
-
const trimmed = line.trim();
|
|
42
|
-
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) continue;
|
|
43
|
-
const idx = trimmed.indexOf('#');
|
|
44
|
-
const spec = idx >= 0 ? trimmed.slice(0, idx).trim() : trimmed;
|
|
45
|
-
const eqIdx = spec.indexOf('==');
|
|
46
|
-
const geIdx = spec.indexOf('>=');
|
|
47
|
-
const name = eqIdx >= 0 ? spec.slice(0, eqIdx).trim() : (geIdx >= 0 ? spec.slice(0, geIdx).trim() : spec);
|
|
48
|
-
if (name && !name.includes('=') && !name.includes('<') && !name.includes('>')) {
|
|
49
|
-
deps.push(normalizePkgName(name));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
} else if (path === 'pyproject.toml') {
|
|
53
|
-
try {
|
|
54
|
-
const obj = JSON.parse(content);
|
|
55
|
-
const allDeps = { ...(obj?.tool?.poetry?.dependencies || {}), ...(obj?.dependencies || {}), ...(obj?.['dev-dependencies'] || {}) };
|
|
56
|
-
for (const key of Object.keys(allDeps)) {
|
|
57
|
-
deps.push(normalizePkgName(key));
|
|
58
|
-
}
|
|
59
|
-
} catch {}
|
|
60
|
-
} else if (path === 'poetry.lock') {
|
|
61
|
-
const pattern = /name\s*=\s*["']([^"']+)["']/g;
|
|
62
|
-
let m;
|
|
63
|
-
while ((m = pattern.exec(content)) !== null) {
|
|
64
|
-
deps.push(normalizePkgName(m[1]));
|
|
65
|
-
}
|
|
66
|
-
} else if (path === 'Pipfile' || path === 'Pipfile.lock') {
|
|
67
|
-
try {
|
|
68
|
-
const obj = JSON.parse(content);
|
|
69
|
-
for (const key of Object.keys(obj?.packages || {})) {
|
|
70
|
-
deps.push(normalizePkgName(key));
|
|
71
|
-
}
|
|
72
|
-
} catch {}
|
|
73
|
-
}
|
|
74
|
-
for (const dep of deps) packages.add(dep);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return packages;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function hasStarlettePin(allFiles) {
|
|
81
|
-
for (const file of (allFiles || [])) {
|
|
82
|
-
const content = typeof file.content === 'string' ? file.content : '';
|
|
83
|
-
if (!content) continue;
|
|
84
|
-
const path = file.path || '';
|
|
85
|
-
|
|
86
|
-
let result = null;
|
|
87
|
-
if (path === 'requirements.txt' || path.match(/^requirements\/.*\.txt$/)) {
|
|
88
|
-
result = parseRequirementsTxt(content);
|
|
89
|
-
} else if (path === 'pyproject.toml') {
|
|
90
|
-
result = parsePyprojectToml(content);
|
|
91
|
-
} else if (path === 'poetry.lock') {
|
|
92
|
-
result = parsePoetryLock(content);
|
|
93
|
-
} else if (path === 'Pipfile' || path === 'Pipfile.lock') {
|
|
94
|
-
result = parsePipfile(content);
|
|
95
|
-
} else if (path === 'setup.py') {
|
|
96
|
-
result = parseSetupPy(content);
|
|
97
|
-
} else if (path === 'setup.cfg') {
|
|
98
|
-
result = parseSetupCfg(content);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (result) {
|
|
102
|
-
if (result.version === null && result.specifier === null) return false;
|
|
103
|
-
const parsed = parsePEP440(result.version);
|
|
104
|
-
const safe = parsePEP440('1.0.1');
|
|
105
|
-
if (parsed && compareVersions(parsed, safe) >= 0) return true;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function parsePEP440(versionStr) {
|
|
113
|
-
if (!versionStr) return null;
|
|
114
|
-
const clean = versionStr.trim().replace(/^v/, '');
|
|
115
|
-
const parts = clean.split('.');
|
|
116
|
-
return {
|
|
117
|
-
major: parseInt(parts[0], 10) || 0,
|
|
118
|
-
minor: parseInt(parts[1], 10) || 0,
|
|
119
|
-
patch: parseInt(parts[2], 10) || 0,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function compareVersions(a, b) {
|
|
124
|
-
if (!a) return 1;
|
|
125
|
-
if (!b) return -1;
|
|
126
|
-
if (a.major !== b.major) return a.major - b.major;
|
|
127
|
-
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
128
|
-
return a.patch - b.patch;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function scanTransitive(allFiles) {
|
|
132
|
-
const findings = [];
|
|
133
|
-
|
|
134
|
-
if (!allFiles || allFiles.length === 0) return findings;
|
|
135
|
-
|
|
136
|
-
const packages = findPackagesInManifests(allFiles);
|
|
137
|
-
|
|
138
|
-
if (hasStarlettePin(allFiles)) return findings;
|
|
139
|
-
|
|
140
|
-
const handled = new Set();
|
|
141
|
-
|
|
142
|
-
for (const pkg of packages) {
|
|
143
|
-
if (TIER_1_PACKAGES.includes(pkg)) {
|
|
144
|
-
handled.add(pkg);
|
|
145
|
-
if (pkg === 'fastapi') {
|
|
146
|
-
const version = findFastapiVersion(allFiles);
|
|
147
|
-
if (version) {
|
|
148
|
-
const parsed = parsePEP440(version);
|
|
149
|
-
const safeFastapi = parsePEP440('0.116.0');
|
|
150
|
-
if (parsed && compareVersions(parsed, safeFastapi) >= 0) continue;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
findings.push(transitiveDependencyFinding(pkg, 1));
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (findings.length === 0) {
|
|
159
|
-
for (const pkg of packages) {
|
|
160
|
-
if (handled.has(pkg)) continue;
|
|
161
|
-
if (TIER_2_PACKAGES.includes(pkg)) {
|
|
162
|
-
findings.push(transitiveDependencyFinding(pkg, 2));
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return findings;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function findFastapiVersion(allFiles) {
|
|
172
|
-
for (const file of (allFiles || [])) {
|
|
173
|
-
const content = typeof file.content === 'string' ? file.content : '';
|
|
174
|
-
if (!content) continue;
|
|
175
|
-
const path = file.path || '';
|
|
176
|
-
if (path === 'requirements.txt' || path.match(/^requirements\/.*\.txt$/)) {
|
|
177
|
-
const lines = content.split('\n');
|
|
178
|
-
for (const line of lines) {
|
|
179
|
-
const trimmed = line.trim();
|
|
180
|
-
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) continue;
|
|
181
|
-
if (trimmed.startsWith('fastapi')) {
|
|
182
|
-
const eqIdx = trimmed.indexOf('==');
|
|
183
|
-
if (eqIdx >= 0) return trimmed.slice(eqIdx + 2).trim();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
1
|
+
import { transitiveDependencyFinding } from './findings.js';
|
|
2
|
+
import { parseRequirementsTxt, parsePyprojectToml, parsePoetryLock, parsePipfile, parseSetupPy, parseSetupCfg } from './manifest.js';
|
|
3
|
+
|
|
4
|
+
const TIER_1_PACKAGES = [
|
|
5
|
+
'fastapi',
|
|
6
|
+
'vllm',
|
|
7
|
+
'litellm',
|
|
8
|
+
'bentoml',
|
|
9
|
+
'text-generation-inference',
|
|
10
|
+
'ray-serve',
|
|
11
|
+
'ray[serve]',
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const TIER_2_PACKAGES = [
|
|
15
|
+
'langserve',
|
|
16
|
+
'fastapi-mcp',
|
|
17
|
+
'mcp',
|
|
18
|
+
'starlette-admin',
|
|
19
|
+
'piccolo-api',
|
|
20
|
+
'fastapi-users',
|
|
21
|
+
'broadcaster',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function normalizePkgName(name) {
|
|
25
|
+
return name.replace(/["'\[\]]/g, '').trim().toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function findPackagesInManifests(allFiles) {
|
|
29
|
+
const packages = new Set();
|
|
30
|
+
|
|
31
|
+
for (const file of (allFiles || [])) {
|
|
32
|
+
const content = typeof file.content === 'string' ? file.content : '';
|
|
33
|
+
if (!content) continue;
|
|
34
|
+
const path = file.path || '';
|
|
35
|
+
|
|
36
|
+
let deps = [];
|
|
37
|
+
|
|
38
|
+
if (path === 'requirements.txt' || path.match(/^requirements\/.*\.txt$/)) {
|
|
39
|
+
const lines = content.split('\n');
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) continue;
|
|
43
|
+
const idx = trimmed.indexOf('#');
|
|
44
|
+
const spec = idx >= 0 ? trimmed.slice(0, idx).trim() : trimmed;
|
|
45
|
+
const eqIdx = spec.indexOf('==');
|
|
46
|
+
const geIdx = spec.indexOf('>=');
|
|
47
|
+
const name = eqIdx >= 0 ? spec.slice(0, eqIdx).trim() : (geIdx >= 0 ? spec.slice(0, geIdx).trim() : spec);
|
|
48
|
+
if (name && !name.includes('=') && !name.includes('<') && !name.includes('>')) {
|
|
49
|
+
deps.push(normalizePkgName(name));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} else if (path === 'pyproject.toml') {
|
|
53
|
+
try {
|
|
54
|
+
const obj = JSON.parse(content);
|
|
55
|
+
const allDeps = { ...(obj?.tool?.poetry?.dependencies || {}), ...(obj?.dependencies || {}), ...(obj?.['dev-dependencies'] || {}) };
|
|
56
|
+
for (const key of Object.keys(allDeps)) {
|
|
57
|
+
deps.push(normalizePkgName(key));
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
60
|
+
} else if (path === 'poetry.lock') {
|
|
61
|
+
const pattern = /name\s*=\s*["']([^"']+)["']/g;
|
|
62
|
+
let m;
|
|
63
|
+
while ((m = pattern.exec(content)) !== null) {
|
|
64
|
+
deps.push(normalizePkgName(m[1]));
|
|
65
|
+
}
|
|
66
|
+
} else if (path === 'Pipfile' || path === 'Pipfile.lock') {
|
|
67
|
+
try {
|
|
68
|
+
const obj = JSON.parse(content);
|
|
69
|
+
for (const key of Object.keys(obj?.packages || {})) {
|
|
70
|
+
deps.push(normalizePkgName(key));
|
|
71
|
+
}
|
|
72
|
+
} catch {}
|
|
73
|
+
}
|
|
74
|
+
for (const dep of deps) packages.add(dep);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return packages;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function hasStarlettePin(allFiles) {
|
|
81
|
+
for (const file of (allFiles || [])) {
|
|
82
|
+
const content = typeof file.content === 'string' ? file.content : '';
|
|
83
|
+
if (!content) continue;
|
|
84
|
+
const path = file.path || '';
|
|
85
|
+
|
|
86
|
+
let result = null;
|
|
87
|
+
if (path === 'requirements.txt' || path.match(/^requirements\/.*\.txt$/)) {
|
|
88
|
+
result = parseRequirementsTxt(content);
|
|
89
|
+
} else if (path === 'pyproject.toml') {
|
|
90
|
+
result = parsePyprojectToml(content);
|
|
91
|
+
} else if (path === 'poetry.lock') {
|
|
92
|
+
result = parsePoetryLock(content);
|
|
93
|
+
} else if (path === 'Pipfile' || path === 'Pipfile.lock') {
|
|
94
|
+
result = parsePipfile(content);
|
|
95
|
+
} else if (path === 'setup.py') {
|
|
96
|
+
result = parseSetupPy(content);
|
|
97
|
+
} else if (path === 'setup.cfg') {
|
|
98
|
+
result = parseSetupCfg(content);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (result) {
|
|
102
|
+
if (result.version === null && result.specifier === null) return false;
|
|
103
|
+
const parsed = parsePEP440(result.version);
|
|
104
|
+
const safe = parsePEP440('1.0.1');
|
|
105
|
+
if (parsed && compareVersions(parsed, safe) >= 0) return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parsePEP440(versionStr) {
|
|
113
|
+
if (!versionStr) return null;
|
|
114
|
+
const clean = versionStr.trim().replace(/^v/, '');
|
|
115
|
+
const parts = clean.split('.');
|
|
116
|
+
return {
|
|
117
|
+
major: parseInt(parts[0], 10) || 0,
|
|
118
|
+
minor: parseInt(parts[1], 10) || 0,
|
|
119
|
+
patch: parseInt(parts[2], 10) || 0,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function compareVersions(a, b) {
|
|
124
|
+
if (!a) return 1;
|
|
125
|
+
if (!b) return -1;
|
|
126
|
+
if (a.major !== b.major) return a.major - b.major;
|
|
127
|
+
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
128
|
+
return a.patch - b.patch;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function scanTransitive(allFiles) {
|
|
132
|
+
const findings = [];
|
|
133
|
+
|
|
134
|
+
if (!allFiles || allFiles.length === 0) return findings;
|
|
135
|
+
|
|
136
|
+
const packages = findPackagesInManifests(allFiles);
|
|
137
|
+
|
|
138
|
+
if (hasStarlettePin(allFiles)) return findings;
|
|
139
|
+
|
|
140
|
+
const handled = new Set();
|
|
141
|
+
|
|
142
|
+
for (const pkg of packages) {
|
|
143
|
+
if (TIER_1_PACKAGES.includes(pkg)) {
|
|
144
|
+
handled.add(pkg);
|
|
145
|
+
if (pkg === 'fastapi') {
|
|
146
|
+
const version = findFastapiVersion(allFiles);
|
|
147
|
+
if (version) {
|
|
148
|
+
const parsed = parsePEP440(version);
|
|
149
|
+
const safeFastapi = parsePEP440('0.116.0');
|
|
150
|
+
if (parsed && compareVersions(parsed, safeFastapi) >= 0) continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
findings.push(transitiveDependencyFinding(pkg, 1));
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (findings.length === 0) {
|
|
159
|
+
for (const pkg of packages) {
|
|
160
|
+
if (handled.has(pkg)) continue;
|
|
161
|
+
if (TIER_2_PACKAGES.includes(pkg)) {
|
|
162
|
+
findings.push(transitiveDependencyFinding(pkg, 2));
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return findings;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function findFastapiVersion(allFiles) {
|
|
172
|
+
for (const file of (allFiles || [])) {
|
|
173
|
+
const content = typeof file.content === 'string' ? file.content : '';
|
|
174
|
+
if (!content) continue;
|
|
175
|
+
const path = file.path || '';
|
|
176
|
+
if (path === 'requirements.txt' || path.match(/^requirements\/.*\.txt$/)) {
|
|
177
|
+
const lines = content.split('\n');
|
|
178
|
+
for (const line of lines) {
|
|
179
|
+
const trimmed = line.trim();
|
|
180
|
+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('-')) continue;
|
|
181
|
+
if (trimmed.startsWith('fastapi')) {
|
|
182
|
+
const eqIdx = trimmed.indexOf('==');
|
|
183
|
+
if (eqIdx >= 0) return trimmed.slice(eqIdx + 2).trim();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|