From 761e38e1f449d04dcc52ff0b6c588f7289a13aa7 Mon Sep 17 00:00:00 2001 From: ndpvt-web Date: Tue, 10 Mar 2026 05:16:20 +0000 Subject: [PATCH 1/4] feat: flag git: and https: dependencies as security concern Adds detection and flagging for dependencies that use git: or https: URLs, which can be manipulated as noted in the issue discussion. These URL-based dependencies bypass npm registry integrity checks. Closes #1084 Co-Authored-By: AI Assistant (Claude) Signed-off-by: ndpvt-web --- app/components/Package/Dependencies.vue | 22 ++++++++++ server/utils/dependency-analysis.ts | 53 ++++++++++++++++++++++++- server/utils/dependency-resolver.ts | 2 + shared/types/dependency-analysis.ts | 15 +++++++ 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/app/components/Package/Dependencies.vue b/app/components/Package/Dependencies.vue index 817aa2458e..1f68b82304 100644 --- a/app/components/Package/Dependencies.vue +++ b/app/components/Package/Dependencies.vue @@ -37,6 +37,12 @@ function getDeprecatedDepInfo(depName: string) { return vulnTree.value.deprecatedPackages.find(p => p.name === depName && p.depth === 'direct') } +// Check if a dependency uses git: or https: URL +function getUrlDepInfo(depName: string) { + if (!vulnTree.value) return null + return vulnTree.value.urlDependencies.find(p => p.name === depName) +} + // Expanded state for each section const depsExpanded = shallowRef(false) const peerDepsExpanded = shallowRef(false) @@ -73,6 +79,8 @@ const sortedOptionalDependencies = computed(() => { // Get version tooltip function getDepVersionTooltip(dep: string, version: string) { + const urlDep = getUrlDepInfo(dep) + if (urlDep) return urlDep.url const outdated = outdatedDeps.value[dep] if (outdated) return getOutdatedTooltip(outdated, t) if (getVulnerableDepInfo(dep) || getDeprecatedDepInfo(dep)) return version @@ -82,6 +90,7 @@ function getDepVersionTooltip(dep: string, version: string) { // Get version class function getDepVersionClass(dep: string) { + if (getUrlDepInfo(dep)) return 'text-orange-700 dark:text-orange-500' const outdated = outdatedDeps.value[dep] if (outdated) return getVersionClass(outdated) if (getVulnerableDepInfo(dep) || getDeprecatedDepInfo(dep)) return getVersionClass(undefined) @@ -164,6 +173,19 @@ const numberFormatter = useNumberFormatter() > {{ $t('package.deprecated.label') }} + + + { + try { + const packument = await fetchNpmPackage(name) + const versionData = packument.versions[version] + if (!versionData) return [] + + const urlDeps: UrlDependencyInfo[] = [] + const allDeps = { + ...versionData.dependencies, + ...versionData.optionalDependencies, + ...versionData.devDependencies, + } + + for (const [depName, depUrl] of Object.entries(allDeps || {})) { + if (isUrlDependency(depUrl)) { + urlDeps.push({ + name: depName, + url: depUrl, + depth, + path: [...path, `${depName}@${depUrl}`], + }) + } + } + + return urlDeps + } catch { + return [] + } +} + /** * Analyze entire dependency tree for vulnerabilities and deprecated packages. * Uses OSV batch API for efficient vulnerability discovery, then fetches @@ -289,6 +336,9 @@ export const analyzeDependencyTree = defineCachedFunction( return depthOrder[a.depth] - depthOrder[b.depth] }) + // Scan for git: and https: URL dependencies in the root package + const urlDependencies = await scanUrlDependencies(name, version, 'root', []) + // Step 1: Use batch API to find which packages have vulnerabilities // This is much faster than individual queries - one request for all packages const { vulnerableIndices, failed: batchFailed } = await queryOsvBatch(packages) @@ -347,6 +397,7 @@ export const analyzeDependencyTree = defineCachedFunction( version, vulnerablePackages, deprecatedPackages, + urlDependencies, totalPackages: packages.length, failedQueries, totalCounts, @@ -356,6 +407,6 @@ export const analyzeDependencyTree = defineCachedFunction( maxAge: 60 * 60, swr: true, name: 'dependency-analysis', - getKey: (name: string, version: string) => `v2:${name}@${version}`, + getKey: (name: string, version: string) => `v3:${name}@${version}`, }, ) diff --git a/server/utils/dependency-resolver.ts b/server/utils/dependency-resolver.ts index 327299a538..044c7faba7 100644 --- a/server/utils/dependency-resolver.ts +++ b/server/utils/dependency-resolver.ts @@ -106,6 +106,8 @@ export interface ResolvedPackage { path?: string[] /** Deprecation message if the version is deprecated */ deprecated?: string + /** Original URL if this was a git: or https: dependency */ + url?: string } /** diff --git a/shared/types/dependency-analysis.ts b/shared/types/dependency-analysis.ts index a733c302e5..7c51b63cfb 100644 --- a/shared/types/dependency-analysis.ts +++ b/shared/types/dependency-analysis.ts @@ -180,6 +180,19 @@ export interface DeprecatedPackageInfo { message: string } +/** + * URL-based dependency info (git:, https:) + */ +export interface UrlDependencyInfo { + name: string + /** The git: or https: URL */ + url: string + /** Depth in dependency tree: root (0), direct (1), transitive (2+) */ + depth: DependencyDepth + /** Dependency path from root package */ + path: string[] +} + /** * Result of dependency tree analysis */ @@ -192,6 +205,8 @@ export interface VulnerabilityTreeResult { vulnerablePackages: PackageVulnerabilityInfo[] /** All deprecated packages in the tree */ deprecatedPackages: DeprecatedPackageInfo[] + /** All dependencies using git: or https: URLs */ + urlDependencies: UrlDependencyInfo[] /** Total packages analyzed */ totalPackages: number /** Number of packages that could not be checked (OSV query failed) */ From 28403132b8df49728a1104ad7ff3aaee0c2fa570 Mon Sep 17 00:00:00 2001 From: ndpvt-web Date: Tue, 10 Mar 2026 08:48:24 +0000 Subject: [PATCH 2/4] fix: address review feedback for URL dependency detection - Scan all packages for URL deps, not just root (fixes transitive detection) - Align isUrlDependency with resolveVersion URL patterns - Use computed map for URL dep lookups in Dependencies.vue - Remove orphaned url field from ResolvedPackage interface Co-Authored-By: Claude Opus 4.6 --- app/components/Package/Dependencies.vue | 9 +++++++-- server/utils/dependency-analysis.ts | 16 +++++++++++++--- server/utils/dependency-resolver.ts | 2 -- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/app/components/Package/Dependencies.vue b/app/components/Package/Dependencies.vue index 1f68b82304..6d8778371d 100644 --- a/app/components/Package/Dependencies.vue +++ b/app/components/Package/Dependencies.vue @@ -37,10 +37,15 @@ function getDeprecatedDepInfo(depName: string) { return vulnTree.value.deprecatedPackages.find(p => p.name === depName && p.depth === 'direct') } +// Cache URL dependency lookups with computed map +const urlDepMap = computed(() => { + if (!vulnTree.value) return new Map() + return new Map(vulnTree.value.urlDependencies.map(dep => [dep.name, dep])) +}) + // Check if a dependency uses git: or https: URL function getUrlDepInfo(depName: string) { - if (!vulnTree.value) return null - return vulnTree.value.urlDependencies.find(p => p.name === depName) + return urlDepMap.value.get(depName) ?? null } // Expanded state for each section diff --git a/server/utils/dependency-analysis.ts b/server/utils/dependency-analysis.ts index 6a16bbccba..bf63ec34e6 100644 --- a/server/utils/dependency-analysis.ts +++ b/server/utils/dependency-analysis.ts @@ -260,7 +260,13 @@ function getSeverityLevel(vuln: OsvVulnerability): OsvSeverityLevel { * Check if a dependency URL is a git: or https: URL that should be flagged. */ function isUrlDependency(url: string): boolean { - return url.startsWith('git:') || url.startsWith('https:') || url.startsWith('git+https:') + return ( + url.startsWith('git:') || + url.startsWith('git+') || + url.startsWith('http:') || + url.startsWith('https:') || + url.startsWith('file:') + ) } /** @@ -336,8 +342,12 @@ export const analyzeDependencyTree = defineCachedFunction( return depthOrder[a.depth] - depthOrder[b.depth] }) - // Scan for git: and https: URL dependencies in the root package - const urlDependencies = await scanUrlDependencies(name, version, 'root', []) + // Scan for git: and https: URL dependencies in all packages + const urlDependencies: UrlDependencyInfo[] = [] + for (const pkg of packages) { + const pkgUrlDeps = await scanUrlDependencies(pkg.name, pkg.version, pkg.depth, pkg.path) + urlDependencies.push(...pkgUrlDeps) + } // Step 1: Use batch API to find which packages have vulnerabilities // This is much faster than individual queries - one request for all packages diff --git a/server/utils/dependency-resolver.ts b/server/utils/dependency-resolver.ts index 044c7faba7..327299a538 100644 --- a/server/utils/dependency-resolver.ts +++ b/server/utils/dependency-resolver.ts @@ -106,8 +106,6 @@ export interface ResolvedPackage { path?: string[] /** Deprecation message if the version is deprecated */ deprecated?: string - /** Original URL if this was a git: or https: dependency */ - url?: string } /** From b10c55fc31e6e4263577958f38398d1b09301962 Mon Sep 17 00:00:00 2001 From: ndpvt-web Date: Tue, 10 Mar 2026 08:59:38 +0000 Subject: [PATCH 3/4] refactor: exclude transitive devDeps and parallelize URL dep scanning - Only include devDependencies when scanning the root package - Use mapWithConcurrency for consistent parallel processing Co-Authored-By: Claude Opus 4.6 --- server/utils/dependency-analysis.ts | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/server/utils/dependency-analysis.ts b/server/utils/dependency-analysis.ts index bf63ec34e6..3f64f9c3f6 100644 --- a/server/utils/dependency-analysis.ts +++ b/server/utils/dependency-analysis.ts @@ -285,11 +285,17 @@ async function scanUrlDependencies( if (!versionData) return [] const urlDeps: UrlDependencyInfo[] = [] - const allDeps = { - ...versionData.dependencies, - ...versionData.optionalDependencies, - ...versionData.devDependencies, - } + // Include devDependencies only for the root package + const allDeps = depth === 'root' + ? { + ...versionData.dependencies, + ...versionData.optionalDependencies, + ...versionData.devDependencies, + } + : { + ...versionData.dependencies, + ...versionData.optionalDependencies, + } for (const [depName, depUrl] of Object.entries(allDeps || {})) { if (isUrlDependency(depUrl)) { @@ -343,11 +349,12 @@ export const analyzeDependencyTree = defineCachedFunction( }) // Scan for git: and https: URL dependencies in all packages - const urlDependencies: UrlDependencyInfo[] = [] - for (const pkg of packages) { - const pkgUrlDeps = await scanUrlDependencies(pkg.name, pkg.version, pkg.depth, pkg.path) - urlDependencies.push(...pkgUrlDeps) - } + const urlDepResults = await mapWithConcurrency( + packages, + pkg => scanUrlDependencies(pkg.name, pkg.version, pkg.depth, pkg.path), + OSV_DETAIL_CONCURRENCY, + ) + const urlDependencies = urlDepResults.flat() // Step 1: Use batch API to find which packages have vulnerabilities // This is much faster than individual queries - one request for all packages From 5d5cecc176a452a5c335db2f72e5987387a8e063 Mon Sep 17 00:00:00 2001 From: ndpvt-web Date: Tue, 10 Mar 2026 09:08:28 +0000 Subject: [PATCH 4/4] fix: correct URL dep depth and add error handling - Record child dependency depth instead of parent depth - Surface scan failures instead of silently returning empty array Co-Authored-By: Claude Opus 4.6 --- server/utils/dependency-analysis.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/utils/dependency-analysis.ts b/server/utils/dependency-analysis.ts index 3f64f9c3f6..fc3ea4daed 100644 --- a/server/utils/dependency-analysis.ts +++ b/server/utils/dependency-analysis.ts @@ -297,19 +297,24 @@ async function scanUrlDependencies( ...versionData.optionalDependencies, } + // URL dependencies are children of the current package, so their depth is one level deeper + const dependencyDepth: DependencyDepth = depth === 'root' ? 'direct' : 'transitive' + for (const [depName, depUrl] of Object.entries(allDeps || {})) { if (isUrlDependency(depUrl)) { urlDeps.push({ name: depName, url: depUrl, - depth, + depth: dependencyDepth, path: [...path, `${depName}@${depUrl}`], }) } } return urlDeps - } catch { + } catch (error) { + // oxlint-disable-next-line no-console -- log URL dependency scan failures for debugging + console.warn(`[dep-analysis] URL dependency scan failed for ${name}@${version}:`, error) return [] } }