mirror of
https://github.com/lukaszraczylo/gohoarder.git
synced 2026-07-22 06:20:09 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1baf0993de | ||
|
|
434a15076e | ||
|
|
4e7350363d | ||
|
|
38edd735b6 | ||
|
|
c207aa72e9 | ||
|
|
bf0925a4fc | ||
|
|
bc854aa183 | ||
|
|
c4bb2f6e3a | ||
|
|
8848656193 | ||
|
|
3ecff61114 | ||
|
|
f86943b884 | ||
|
|
448bb70ac8 | ||
|
|
d63ae21133 |
+5
-13
@@ -35,18 +35,15 @@ EOF
|
||||
|
||||
RUN chmod +x /docker-entrypoint.d/40-inject-config.sh
|
||||
|
||||
# Create nginx configuration template with backend proxy support
|
||||
RUN cat > /etc/nginx/templates/default.conf.template <<'EOF'
|
||||
# Create nginx templates directory and configuration template with backend proxy support
|
||||
RUN mkdir -p /etc/nginx/templates && \
|
||||
cat > /etc/nginx/templates/default.conf.template <<'EOF'
|
||||
# Upstream backend server
|
||||
upstream backend {
|
||||
server ${BACKEND_HOST}:${BACKEND_PORT};
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
# Rate limiting zones
|
||||
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=download_limit:10m rate=5r/s;
|
||||
|
||||
# Cache configuration
|
||||
proxy_cache_path /var/cache/nginx/static levels=1:2 keys_zone=static_cache:10m max_size=100m inactive=60m use_temp_path=off;
|
||||
|
||||
@@ -76,11 +73,8 @@ server {
|
||||
|
||||
# API endpoints - proxy to backend
|
||||
location /api/ {
|
||||
# Rate limiting
|
||||
limit_req zone=api_limit burst=20 nodelay;
|
||||
|
||||
# Proxy settings
|
||||
proxy_pass http://backend/;
|
||||
proxy_pass http://backend;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# Headers
|
||||
@@ -119,10 +113,8 @@ server {
|
||||
proxy_set_header Connection "";
|
||||
}
|
||||
|
||||
# Package download endpoints with rate limiting
|
||||
# Package download endpoints
|
||||
location ~ ^/(npm|pypi|go)/ {
|
||||
limit_req zone=download_limit burst=10 nodelay;
|
||||
|
||||
proxy_pass http://backend;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
+5
-2
@@ -2,7 +2,9 @@
|
||||
# This Dockerfile expects a PRE-BUILT binary from GoReleaser (no compilation)
|
||||
# GoReleaser injects the platform-specific binary automatically
|
||||
|
||||
FROM alpine:latest
|
||||
FROM --platform=$TARGETPLATFORM alpine:latest
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
# Install runtime dependencies (including CGO/SQLite dependencies)
|
||||
RUN apk add --no-cache \
|
||||
@@ -20,7 +22,8 @@ RUN addgroup -g 1000 gohoarder && \
|
||||
|
||||
# Copy pre-built binary from GoReleaser
|
||||
# GoReleaser will automatically inject the correct binary for the target platform
|
||||
COPY migrate /usr/local/bin/migrate
|
||||
# In split/merge mode, binaries are in linux/${TARGETARCH}/ subdirectories
|
||||
COPY linux/${TARGETARCH}/migrate /usr/local/bin/migrate
|
||||
RUN chmod +x /usr/local/bin/migrate
|
||||
|
||||
# Copy migration SQL files
|
||||
|
||||
+16
-2
@@ -2,7 +2,9 @@
|
||||
# This Dockerfile expects a PRE-BUILT binary from GoReleaser (no compilation)
|
||||
# GoReleaser injects the platform-specific binary automatically
|
||||
|
||||
FROM alpine:latest
|
||||
FROM --platform=$TARGETPLATFORM alpine:latest
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
# Install scanning tools and runtime dependencies (including CGO/SQLite dependencies)
|
||||
RUN apk add --no-cache \
|
||||
@@ -14,6 +16,10 @@ RUN apk add --no-cache \
|
||||
bash \
|
||||
sqlite-libs \
|
||||
musl \
|
||||
python3 \
|
||||
py3-pip \
|
||||
npm \
|
||||
go \
|
||||
&& update-ca-certificates
|
||||
|
||||
# Install Trivy for container scanning
|
||||
@@ -22,6 +28,13 @@ RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/
|
||||
# Install Grype for vulnerability scanning
|
||||
RUN curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
|
||||
|
||||
# Install govulncheck for Go vulnerability scanning
|
||||
RUN go install golang.org/x/vuln/cmd/govulncheck@latest && \
|
||||
mv /root/go/bin/govulncheck /usr/local/bin/
|
||||
|
||||
# Install pip-audit for Python package vulnerability scanning
|
||||
RUN pip3 install --no-cache-dir pip-audit --break-system-packages
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1000 scanner && \
|
||||
adduser -D -u 1000 -G scanner scanner
|
||||
@@ -41,7 +54,8 @@ RUN mkdir -p /var/cache/gohoarder \
|
||||
|
||||
# Copy pre-built binary from GoReleaser
|
||||
# GoReleaser will automatically inject the correct binary for the target platform
|
||||
COPY gohoarder /usr/local/bin/gohoarder
|
||||
# In split/merge mode, binaries are in linux/${TARGETARCH}/ subdirectories
|
||||
COPY linux/${TARGETARCH}/gohoarder /usr/local/bin/gohoarder
|
||||
RUN chmod +x /usr/local/bin/gohoarder
|
||||
|
||||
# Copy example config
|
||||
|
||||
+5
-2
@@ -2,7 +2,9 @@
|
||||
# This Dockerfile expects a PRE-BUILT binary from GoReleaser (no compilation)
|
||||
# GoReleaser injects the platform-specific binary automatically
|
||||
|
||||
FROM alpine:latest
|
||||
FROM --platform=$TARGETPLATFORM alpine:latest
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
# Install runtime dependencies (CGO/SQLite requires these)
|
||||
RUN apk add --no-cache \
|
||||
@@ -28,7 +30,8 @@ RUN mkdir -p /var/cache/gohoarder \
|
||||
|
||||
# Copy pre-built binary from GoReleaser
|
||||
# GoReleaser will automatically inject the correct binary for the target platform
|
||||
COPY gohoarder /usr/local/bin/gohoarder
|
||||
# In split/merge mode, binaries are in linux/${TARGETARCH}/ subdirectories
|
||||
COPY linux/${TARGETARCH}/gohoarder /usr/local/bin/gohoarder
|
||||
RUN chmod +x /usr/local/bin/gohoarder
|
||||
|
||||
# Copy example config
|
||||
|
||||
@@ -19,4 +19,4 @@ sources:
|
||||
maintainers:
|
||||
- name: Lukasz Raczylo
|
||||
email: [email protected]
|
||||
icon: https://raw.githubusercontent.com/lukaszraczylo/gohoarder/main/docs/logo.png
|
||||
# icon: https://raw.githubusercontent.com/lukaszraczylo/gohoarder/main/docs/logo.png # TODO: Add logo
|
||||
|
||||
@@ -93,24 +93,30 @@ data:
|
||||
low: {{ .Values.security.blockThresholds.low }}
|
||||
scanners:
|
||||
trivy:
|
||||
enabled: {{ .Values.security.scanners.trivy.enabled }}
|
||||
# Disabled in server config (no trivy binary), enabled via env var in scanner pod
|
||||
enabled: false
|
||||
timeout: {{ .Values.security.scanners.trivy.timeout | quote }}
|
||||
cache_db: {{ .Values.security.scanners.trivy.cacheDb | quote }}
|
||||
osv:
|
||||
# API-based scanner - works in both server and scanner pods
|
||||
enabled: {{ .Values.security.scanners.osv.enabled }}
|
||||
api_url: {{ .Values.security.scanners.osv.apiUrl | quote }}
|
||||
timeout: {{ .Values.security.scanners.osv.timeout | quote }}
|
||||
grype:
|
||||
enabled: {{ .Values.security.scanners.grype.enabled }}
|
||||
# Disabled in server config (no grype binary), enabled via env var in scanner pod
|
||||
enabled: false
|
||||
timeout: {{ .Values.security.scanners.grype.timeout | quote }}
|
||||
govulncheck:
|
||||
enabled: {{ .Values.security.scanners.govulncheck.enabled }}
|
||||
# Disabled in server config (no go/govulncheck binary), enabled via env var in scanner pod
|
||||
enabled: false
|
||||
timeout: {{ .Values.security.scanners.govulncheck.timeout | quote }}
|
||||
npm_audit:
|
||||
enabled: {{ .Values.security.scanners.npmAudit.enabled }}
|
||||
# Disabled in server config (no npm binary), enabled via env var in scanner pod
|
||||
enabled: false
|
||||
timeout: {{ .Values.security.scanners.npmAudit.timeout | quote }}
|
||||
pip_audit:
|
||||
enabled: {{ .Values.security.scanners.pipAudit.enabled }}
|
||||
# Disabled in server config (no pip binary), enabled via env var in scanner pod
|
||||
enabled: false
|
||||
timeout: {{ .Values.security.scanners.pipAudit.timeout | quote }}
|
||||
ghsa:
|
||||
enabled: {{ .Values.security.scanners.ghsa.enabled }}
|
||||
|
||||
@@ -109,6 +109,17 @@ spec:
|
||||
env:
|
||||
- name: CONFIG_FILE
|
||||
value: /etc/gohoarder/config.yaml
|
||||
# Enable tool-based scanners only in scanner pod (server doesn't have the tools)
|
||||
- name: GOHOARDER_SECURITY_SCANNERS_TRIVY_ENABLED
|
||||
value: "{{ .Values.security.scanners.trivy.enabled }}"
|
||||
- name: GOHOARDER_SECURITY_SCANNERS_GRYPE_ENABLED
|
||||
value: "{{ .Values.security.scanners.grype.enabled }}"
|
||||
- name: GOHOARDER_SECURITY_SCANNERS_GOVULNCHECK_ENABLED
|
||||
value: "{{ .Values.security.scanners.govulncheck.enabled }}"
|
||||
- name: GOHOARDER_SECURITY_SCANNERS_NPM_AUDIT_ENABLED
|
||||
value: "{{ .Values.security.scanners.npmAudit.enabled }}"
|
||||
- name: GOHOARDER_SECURITY_SCANNERS_PIP_AUDIT_ENABLED
|
||||
value: "{{ .Values.security.scanners.pipAudit.enabled }}"
|
||||
{{- if and (eq .Values.metadata.backend "postgresql") .Values.metadata.postgresql.existingSecret }}
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
|
||||
+3
-1
@@ -290,7 +290,9 @@ func (a *App) initializeComponents() error {
|
||||
a.healthChecker.AddCheck("scanner", func(ctx context.Context) (health.Status, string) {
|
||||
if a.config.Security.Enabled {
|
||||
if err := a.scanManager.Health(ctx); err != nil {
|
||||
return health.StatusUnhealthy, err.Error()
|
||||
// Scanner failures (e.g., API rate limits) shouldn't mark server as unhealthy
|
||||
// Server can still serve cached packages, just can't scan new ones
|
||||
return health.StatusDegraded, err.Error()
|
||||
}
|
||||
}
|
||||
return health.StatusHealthy, ""
|
||||
|
||||
Vendored
+47
-1
@@ -145,7 +145,37 @@ func (m *Manager) getOrFetch(ctx context.Context, registry, name, version string
|
||||
if err == nil {
|
||||
// Cache hit!
|
||||
metrics.RecordCacheHit(registry)
|
||||
_ = m.metadata.UpdateDownloadCount(ctx, registry, name, version) // #nosec G104 -- Async update, error logged
|
||||
|
||||
// Update download count (log errors for debugging)
|
||||
if err := m.metadata.UpdateDownloadCount(ctx, registry, name, version); err != nil {
|
||||
log.Warn().
|
||||
Err(err).
|
||||
Str("registry", registry).
|
||||
Str("package", name).
|
||||
Str("version", version).
|
||||
Msg("Failed to update download count - package may not exist in database")
|
||||
|
||||
// Try to save package to database if it doesn't exist
|
||||
// This handles the case where storage has files but database was migrated/reset
|
||||
if saveErr := m.metadata.SavePackage(ctx, pkg); saveErr != nil {
|
||||
log.Error().
|
||||
Err(saveErr).
|
||||
Str("registry", registry).
|
||||
Str("package", name).
|
||||
Str("version", version).
|
||||
Msg("Failed to save package to database")
|
||||
} else {
|
||||
// Retry download count update after saving package
|
||||
if retryErr := m.metadata.UpdateDownloadCount(ctx, registry, name, version); retryErr != nil {
|
||||
log.Error().
|
||||
Err(retryErr).
|
||||
Str("registry", registry).
|
||||
Str("package", name).
|
||||
Str("version", version).
|
||||
Msg("Failed to update download count even after saving package")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track download in analytics if enabled
|
||||
if m.analytics != nil {
|
||||
@@ -290,6 +320,22 @@ servePkg:
|
||||
return nil, errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to retrieve just-stored package")
|
||||
}
|
||||
|
||||
// Track download count for first-time download (cache miss)
|
||||
// This ensures download count increments regardless of cache hit/miss
|
||||
if err := m.metadata.UpdateDownloadCount(ctx, registry, name, version); err != nil {
|
||||
log.Warn().
|
||||
Err(err).
|
||||
Str("registry", registry).
|
||||
Str("package", name).
|
||||
Str("version", version).
|
||||
Msg("Failed to update download count for newly cached package")
|
||||
}
|
||||
|
||||
// Track download in analytics if enabled
|
||||
if m.analytics != nil {
|
||||
m.trackDownload(registry, name, version, storedPkg.Size)
|
||||
}
|
||||
|
||||
return &CacheEntry{
|
||||
Package: storedPkg,
|
||||
Data: storedData,
|
||||
|
||||
Vendored
+3
@@ -343,6 +343,7 @@ func TestGet(t *testing.T) {
|
||||
s.On("Put", mock.Anything, "npm/lodash/4.17.21", mock.Anything, mock.Anything).Return(nil)
|
||||
m.On("SavePackage", mock.Anything, mock.Anything).Return(nil)
|
||||
s.On("Get", mock.Anything, "npm/lodash/4.17.21").Return(io.NopCloser(strings.NewReader("upstream data")), nil)
|
||||
m.On("UpdateDownloadCount", mock.Anything, "npm", "lodash", "4.17.21").Return(nil)
|
||||
},
|
||||
fetchFunc: func(ctx context.Context) (io.ReadCloser, string, error) {
|
||||
return io.NopCloser(strings.NewReader("upstream data")), "https://registry.npmjs.org/lodash", nil
|
||||
@@ -374,6 +375,7 @@ func TestGet(t *testing.T) {
|
||||
s.On("Put", mock.Anything, "npm/expired-pkg/1.0.0", mock.Anything, mock.Anything).Return(nil)
|
||||
m.On("SavePackage", mock.Anything, mock.Anything).Return(nil)
|
||||
s.On("Get", mock.Anything, "npm/expired-pkg/1.0.0").Return(io.NopCloser(strings.NewReader("refreshed data")), nil)
|
||||
m.On("UpdateDownloadCount", mock.Anything, "npm", "expired-pkg", "1.0.0").Return(nil)
|
||||
},
|
||||
fetchFunc: func(ctx context.Context) (io.ReadCloser, string, error) {
|
||||
return io.NopCloser(strings.NewReader("refreshed data")), "https://registry.npmjs.org/expired-pkg", nil
|
||||
@@ -435,6 +437,7 @@ func TestGet(t *testing.T) {
|
||||
m.On("SavePackage", mock.Anything, mock.Anything).Return(nil)
|
||||
// Second Get succeeds (after re-storing)
|
||||
s.On("Get", mock.Anything, "npm/inconsistent/1.0.0").Return(io.NopCloser(strings.NewReader("recovered data")), nil).Once()
|
||||
m.On("UpdateDownloadCount", mock.Anything, "npm", "inconsistent", "1.0.0").Return(nil)
|
||||
},
|
||||
fetchFunc: func(ctx context.Context) (io.ReadCloser, string, error) {
|
||||
return io.NopCloser(strings.NewReader("recovered data")), "https://registry.npmjs.org/inconsistent", nil
|
||||
|
||||
@@ -583,29 +583,38 @@ func (s *GORMStoreV2) GetTimeSeriesStats(ctx context.Context, period string, reg
|
||||
// Determine which table to query based on period
|
||||
var tableName string
|
||||
var since time.Time
|
||||
var bucketDuration time.Duration
|
||||
|
||||
switch period {
|
||||
case "1h":
|
||||
tableName = "download_stats_hourly"
|
||||
since = time.Now().Add(-1 * time.Hour)
|
||||
since = time.Now().Add(-1 * time.Hour).Truncate(time.Hour)
|
||||
bucketDuration = time.Hour
|
||||
case "1day":
|
||||
tableName = "download_stats_hourly"
|
||||
since = time.Now().Add(-24 * time.Hour)
|
||||
since = time.Now().Add(-24 * time.Hour).Truncate(time.Hour)
|
||||
bucketDuration = time.Hour
|
||||
case "7day":
|
||||
tableName = "download_stats_daily"
|
||||
since = time.Now().Add(-7 * 24 * time.Hour)
|
||||
since = time.Now().Add(-7 * 24 * time.Hour).Truncate(24 * time.Hour)
|
||||
bucketDuration = 24 * time.Hour
|
||||
case "30day":
|
||||
tableName = "download_stats_daily"
|
||||
since = time.Now().Add(-30 * 24 * time.Hour)
|
||||
since = time.Now().Add(-30 * 24 * time.Hour).Truncate(24 * time.Hour)
|
||||
bucketDuration = 24 * time.Hour
|
||||
default:
|
||||
tableName = "download_stats_hourly"
|
||||
since = time.Now().Add(-24 * time.Hour)
|
||||
since = time.Now().Add(-24 * time.Hour).Truncate(time.Hour)
|
||||
bucketDuration = time.Hour
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// Try to get registry-level aggregate stats first
|
||||
query := s.db.WithContext(ctx).
|
||||
Table(tableName).
|
||||
Select("time_bucket as timestamp, download_count as value").
|
||||
Where("time_bucket >= ?", since)
|
||||
Where("time_bucket >= ? AND time_bucket <= ?", since, now)
|
||||
|
||||
// Filter by registry if specified
|
||||
if registry != "" {
|
||||
@@ -630,10 +639,45 @@ func (s *GORMStoreV2) GetTimeSeriesStats(ctx context.Context, period string, reg
|
||||
return nil, errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to get time series stats")
|
||||
}
|
||||
|
||||
// If no aggregate data found, try summing package-level stats
|
||||
if len(results) == 0 {
|
||||
sumQuery := s.db.WithContext(ctx).
|
||||
Table(tableName).
|
||||
Select("time_bucket as timestamp, SUM(download_count) as value").
|
||||
Where("time_bucket >= ? AND time_bucket <= ?", since, now)
|
||||
|
||||
if registry != "" {
|
||||
registryID, err := s.getRegistryID(registry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sumQuery = sumQuery.Where("registry_id = ? AND package_id IS NOT NULL", registryID)
|
||||
} else {
|
||||
sumQuery = sumQuery.Where("package_id IS NOT NULL")
|
||||
}
|
||||
|
||||
sumQuery = sumQuery.Group("time_bucket").Order("time_bucket ASC")
|
||||
|
||||
if err := sumQuery.Scan(&results).Error; err != nil {
|
||||
return nil, errors.Wrap(err, errors.ErrCodeStorageFailure, "failed to get time series stats from package data")
|
||||
}
|
||||
}
|
||||
|
||||
// Create a map of existing data points
|
||||
dataMap := make(map[time.Time]int64)
|
||||
for _, r := range results {
|
||||
dataMap[r.Timestamp] = r.Value
|
||||
}
|
||||
|
||||
// Generate continuous time series with 0 values for missing periods
|
||||
for t := since; t.Before(now) || t.Equal(now); t = t.Add(bucketDuration) {
|
||||
value := int64(0)
|
||||
if v, exists := dataMap[t]; exists {
|
||||
value = v
|
||||
}
|
||||
stats.DataPoints = append(stats.DataPoints, &metadata.TimeSeriesDataPoint{
|
||||
Timestamp: r.Timestamp,
|
||||
Value: r.Value,
|
||||
Timestamp: t,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -107,11 +107,17 @@ func (s *Scanner) Health(ctx context.Context) error {
|
||||
}
|
||||
defer resp.Body.Close() // #nosec G104 -- Cleanup, error not critical
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("github api returned status: %d", resp.StatusCode)
|
||||
// Accept any 2xx or 403 (rate limit) as healthy
|
||||
// Rate limits are expected without a GitHub token and shouldn't fail health checks
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
return nil
|
||||
}
|
||||
if resp.StatusCode == http.StatusForbidden {
|
||||
log.Debug().Msg("GitHub API rate limited (expected without token)")
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return fmt.Errorf("github api returned status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// mapRegistryToEcosystem maps our registry names to GitHub ecosystem names
|
||||
|
||||
+52
-2
@@ -121,13 +121,17 @@ func (s *Scanner) Scan(ctx context.Context, registry, packageName, version strin
|
||||
// Convert registry to OSV ecosystem
|
||||
ecosystem := s.registryToEcosystem(registry)
|
||||
|
||||
// Clean package name and version for Go modules
|
||||
// Go proxy cache keys include /@v/version.suffix which OSV doesn't understand
|
||||
cleanName, cleanVersion := s.cleanGoModuleName(packageName, version, ecosystem)
|
||||
|
||||
// Build request
|
||||
req := OSVRequest{
|
||||
Package: PackageInfo{
|
||||
Name: packageName,
|
||||
Name: cleanName,
|
||||
Ecosystem: ecosystem,
|
||||
},
|
||||
Version: version,
|
||||
Version: cleanVersion,
|
||||
}
|
||||
|
||||
// Marshal request
|
||||
@@ -199,6 +203,52 @@ func (s *Scanner) registryToEcosystem(registry string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// cleanGoModuleName cleans Go module cache keys to extract the actual module path and version
|
||||
// Go proxy cache keys include /@v/version.suffix patterns that need to be cleaned
|
||||
// Examples:
|
||||
// - "gorm.io/driver/sqlite/@v/v1.6.0.zip" -> "gorm.io/driver/sqlite", "v1.6.0"
|
||||
// - "github.com/pkg/errors/@v/v0.9.1.mod" -> "github.com/pkg/errors", "v0.9.1"
|
||||
// - "regular-package" -> "regular-package", "version" (unchanged for non-Go)
|
||||
func (s *Scanner) cleanGoModuleName(packageName, version, ecosystem string) (string, string) {
|
||||
// Only clean for Go modules
|
||||
if ecosystem != "Go" {
|
||||
return packageName, version
|
||||
}
|
||||
|
||||
// Check if package name contains /@v/ pattern (Go module proxy format)
|
||||
if strings.Contains(packageName, "/@v/") {
|
||||
// Split on /@v/ to get the module path
|
||||
parts := strings.Split(packageName, "/@v/")
|
||||
if len(parts) == 2 {
|
||||
// parts[0] is the clean module path (e.g., "gorm.io/driver/sqlite")
|
||||
// parts[1] might be "v1.6.0.zip" or "v1.6.0.mod" or "v1.6.0.info"
|
||||
cleanName := parts[0]
|
||||
|
||||
// Extract version from the second part if version wasn't already clean
|
||||
// Remove file suffixes like .zip, .mod, .info
|
||||
versionPart := parts[1]
|
||||
versionPart = strings.TrimSuffix(versionPart, ".zip")
|
||||
versionPart = strings.TrimSuffix(versionPart, ".mod")
|
||||
versionPart = strings.TrimSuffix(versionPart, ".info")
|
||||
|
||||
// Use the extracted version if it looks valid, otherwise use the provided version
|
||||
if versionPart != "" && strings.HasPrefix(versionPart, "v") {
|
||||
return cleanName, versionPart
|
||||
}
|
||||
|
||||
return cleanName, version
|
||||
}
|
||||
}
|
||||
|
||||
// Also clean version of any file suffixes
|
||||
cleanVersion := version
|
||||
cleanVersion = strings.TrimSuffix(cleanVersion, ".zip")
|
||||
cleanVersion = strings.TrimSuffix(cleanVersion, ".mod")
|
||||
cleanVersion = strings.TrimSuffix(cleanVersion, ".info")
|
||||
|
||||
return packageName, cleanVersion
|
||||
}
|
||||
|
||||
// convertOSVResult converts OSV response to metadata.ScanResult
|
||||
func (s *Scanner) convertOSVResult(osvResp *OSVResponse, registry, packageName, version string) *metadata.ScanResult {
|
||||
vulnerabilities := make([]metadata.Vulnerability, 0, len(osvResp.Vulns))
|
||||
|
||||
Reference in New Issue
Block a user