Volt CLI: source-available under AGPSL v5.0

Complete infrastructure platform CLI:
- Container runtime (systemd-nspawn)
- VoltVisor VMs (Neutron Stardust / QEMU)
- Stellarium CAS (content-addressed storage)
- ORAS Registry
- GitOps integration
- Landlock LSM security
- Compose orchestration
- Mesh networking

Copyright (c) Armored Gates LLC. All rights reserved.
Licensed under AGPSL v5.0
This commit is contained in:
Karl Clinger
2026-03-21 00:30:23 -05:00
commit 81ad0b597c
106 changed files with 35984 additions and 0 deletions

View File

@@ -0,0 +1,992 @@
package security
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// ── TestDetectOS ─────────────────────────────────────────────────────────────
func TestDetectOS_Alpine(t *testing.T) {
rootfs := createTempRootfs(t, map[string]string{
"etc/os-release": `NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.19.1
PRETTY_NAME="Alpine Linux v3.19"
HOME_URL="https://alpinelinux.org/"
`,
})
name, eco, err := DetectOS(rootfs)
if err != nil {
t.Fatalf("DetectOS failed: %v", err)
}
if name != "Alpine Linux v3.19" {
t.Errorf("expected 'Alpine Linux v3.19', got %q", name)
}
if eco != "Alpine" {
t.Errorf("expected ecosystem 'Alpine', got %q", eco)
}
}
func TestDetectOS_Debian(t *testing.T) {
rootfs := createTempRootfs(t, map[string]string{
"etc/os-release": `PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
`,
})
name, eco, err := DetectOS(rootfs)
if err != nil {
t.Fatalf("DetectOS failed: %v", err)
}
if name != "Debian GNU/Linux 12 (bookworm)" {
t.Errorf("expected 'Debian GNU/Linux 12 (bookworm)', got %q", name)
}
if eco != "Debian" {
t.Errorf("expected ecosystem 'Debian', got %q", eco)
}
}
func TestDetectOS_Ubuntu(t *testing.T) {
rootfs := createTempRootfs(t, map[string]string{
"etc/os-release": `PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"
ID=ubuntu
ID_LIKE=debian
`,
})
name, eco, err := DetectOS(rootfs)
if err != nil {
t.Fatalf("DetectOS failed: %v", err)
}
if name != "Ubuntu 24.04.1 LTS" {
t.Errorf("expected 'Ubuntu 24.04.1 LTS', got %q", name)
}
if eco != "Ubuntu" {
t.Errorf("expected ecosystem 'Ubuntu', got %q", eco)
}
}
func TestDetectOS_Rocky(t *testing.T) {
rootfs := createTempRootfs(t, map[string]string{
"etc/os-release": `NAME="Rocky Linux"
VERSION="9.3 (Blue Onyx)"
ID="rocky"
VERSION_ID="9.3"
PRETTY_NAME="Rocky Linux 9.3 (Blue Onyx)"
`,
})
name, eco, err := DetectOS(rootfs)
if err != nil {
t.Fatalf("DetectOS failed: %v", err)
}
if name != "Rocky Linux 9.3 (Blue Onyx)" {
t.Errorf("expected 'Rocky Linux 9.3 (Blue Onyx)', got %q", name)
}
if eco != "Rocky Linux" {
t.Errorf("expected ecosystem 'Rocky Linux', got %q", eco)
}
}
func TestDetectOS_NoFile(t *testing.T) {
rootfs := t.TempDir()
_, _, err := DetectOS(rootfs)
if err == nil {
t.Fatal("expected error for missing os-release")
}
}
func TestDetectOS_NoPrettyName(t *testing.T) {
rootfs := createTempRootfs(t, map[string]string{
"etc/os-release": `ID=alpine
VERSION_ID=3.19.1
`,
})
name, _, err := DetectOS(rootfs)
if err != nil {
t.Fatalf("DetectOS failed: %v", err)
}
if name != "alpine 3.19.1" {
t.Errorf("expected 'alpine 3.19.1', got %q", name)
}
}
// ── TestListPackagesDpkg ─────────────────────────────────────────────────────
func TestListPackagesDpkg(t *testing.T) {
rootfs := createTempRootfs(t, map[string]string{
"var/lib/dpkg/status": `Package: base-files
Status: install ok installed
Priority: required
Section: admin
Installed-Size: 338
Maintainer: Santiago Vila <sanvila@debian.org>
Architecture: amd64
Version: 12.4+deb12u5
Description: Debian base system miscellaneous files
Package: libc6
Status: install ok installed
Priority: optional
Section: libs
Installed-Size: 13364
Maintainer: GNU Libc Maintainers <debian-glibc@lists.debian.org>
Architecture: amd64
Multi-Arch: same
Version: 2.36-9+deb12u7
Description: GNU C Library: Shared libraries
Package: removed-pkg
Status: deinstall ok not-installed
Priority: optional
Section: libs
Architecture: amd64
Version: 1.0.0
Description: This should not appear
Package: openssl
Status: install ok installed
Priority: optional
Section: utils
Installed-Size: 1420
Architecture: amd64
Version: 3.0.11-1~deb12u2
Description: Secure Sockets Layer toolkit
`,
})
pkgs, err := ListPackages(rootfs)
if err != nil {
t.Fatalf("ListPackages failed: %v", err)
}
if len(pkgs) != 3 {
t.Fatalf("expected 3 packages, got %d: %+v", len(pkgs), pkgs)
}
// Check that we got the right packages
names := map[string]string{}
for _, p := range pkgs {
names[p.Name] = p.Version
if p.Source != "dpkg" {
t.Errorf("expected source 'dpkg', got %q for %s", p.Source, p.Name)
}
}
if names["base-files"] != "12.4+deb12u5" {
t.Errorf("wrong version for base-files: %q", names["base-files"])
}
if names["libc6"] != "2.36-9+deb12u7" {
t.Errorf("wrong version for libc6: %q", names["libc6"])
}
if names["openssl"] != "3.0.11-1~deb12u2" {
t.Errorf("wrong version for openssl: %q", names["openssl"])
}
if _, ok := names["removed-pkg"]; ok {
t.Error("removed-pkg should not be listed")
}
}
func TestListPackagesDpkg_NoTrailingNewline(t *testing.T) {
rootfs := createTempRootfs(t, map[string]string{
"var/lib/dpkg/status": `Package: curl
Status: install ok installed
Version: 7.88.1-10+deb12u5`,
})
pkgs, err := ListPackages(rootfs)
if err != nil {
t.Fatalf("ListPackages failed: %v", err)
}
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}
if pkgs[0].Name != "curl" || pkgs[0].Version != "7.88.1-10+deb12u5" {
t.Errorf("unexpected package: %+v", pkgs[0])
}
}
// ── TestListPackagesApk ──────────────────────────────────────────────────────
func TestListPackagesApk(t *testing.T) {
rootfs := createTempRootfs(t, map[string]string{
"lib/apk/db/installed": `C:Q1abc123=
P:musl
V:1.2.4_git20230717-r4
A:x86_64
S:383152
I:622592
T:the musl c library
U:https://musl.libc.org/
L:MIT
o:musl
m:Natanael Copa <ncopa@alpinelinux.org>
t:1700000000
c:abc123
C:Q1def456=
P:busybox
V:1.36.1-r15
A:x86_64
S:512000
I:924000
T:Size optimized toolbox
U:https://busybox.net/
L:GPL-2.0-only
o:busybox
m:Natanael Copa <ncopa@alpinelinux.org>
t:1700000001
c:def456
C:Q1ghi789=
P:openssl
V:3.1.4-r5
A:x86_64
S:1234567
I:2345678
T:Toolkit for SSL/TLS
U:https://www.openssl.org/
L:Apache-2.0
o:openssl
m:Natanael Copa <ncopa@alpinelinux.org>
t:1700000002
c:ghi789
`,
})
pkgs, err := ListPackages(rootfs)
if err != nil {
t.Fatalf("ListPackages failed: %v", err)
}
if len(pkgs) != 3 {
t.Fatalf("expected 3 packages, got %d: %+v", len(pkgs), pkgs)
}
names := map[string]string{}
for _, p := range pkgs {
names[p.Name] = p.Version
if p.Source != "apk" {
t.Errorf("expected source 'apk', got %q for %s", p.Source, p.Name)
}
}
if names["musl"] != "1.2.4_git20230717-r4" {
t.Errorf("wrong version for musl: %q", names["musl"])
}
if names["busybox"] != "1.36.1-r15" {
t.Errorf("wrong version for busybox: %q", names["busybox"])
}
if names["openssl"] != "3.1.4-r5" {
t.Errorf("wrong version for openssl: %q", names["openssl"])
}
}
func TestListPackagesApk_NoTrailingNewline(t *testing.T) {
rootfs := createTempRootfs(t, map[string]string{
"lib/apk/db/installed": `P:curl
V:8.5.0-r0`,
})
pkgs, err := ListPackages(rootfs)
if err != nil {
t.Fatalf("ListPackages failed: %v", err)
}
if len(pkgs) != 1 {
t.Fatalf("expected 1 package, got %d", len(pkgs))
}
if pkgs[0].Name != "curl" || pkgs[0].Version != "8.5.0-r0" {
t.Errorf("unexpected package: %+v", pkgs[0])
}
}
// ── TestListPackages_NoPackageManager ────────────────────────────────────────
func TestListPackages_NoPackageManager(t *testing.T) {
rootfs := t.TempDir()
_, err := ListPackages(rootfs)
if err == nil {
t.Fatal("expected error when no package manager found")
}
if !strings.Contains(err.Error(), "no supported package manager") {
t.Errorf("unexpected error: %v", err)
}
}
// ── TestOSVQueryParsing ──────────────────────────────────────────────────────
func TestOSVQueryParsing(t *testing.T) {
// Recorded OSV response for openssl 3.1.4 on Alpine
osvResponse := `{
"vulns": [
{
"id": "CVE-2024-0727",
"summary": "PKCS12 Decoding crashes",
"details": "Processing a maliciously crafted PKCS12 file may lead to OpenSSL crashing.",
"severity": [
{"type": "CVSS_V3", "score": "5.5"}
],
"affected": [
{
"package": {"name": "openssl", "ecosystem": "Alpine"},
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{"introduced": "0"},
{"fixed": "3.1.5-r0"}
]
}
]
}
],
"references": [
{"type": "ADVISORY", "url": "https://www.openssl.org/news/secadv/20240125.txt"},
{"type": "WEB", "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-0727"}
]
},
{
"id": "CVE-2024-2511",
"summary": "Unbounded memory growth with session handling in TLSv1.3",
"severity": [
{"type": "CVSS_V3", "score": "3.7"}
],
"affected": [
{
"package": {"name": "openssl", "ecosystem": "Alpine"},
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{"introduced": "3.1.0"},
{"fixed": "3.1.6-r0"}
]
}
]
}
],
"references": [
{"type": "ADVISORY", "url": "https://www.openssl.org/news/secadv/20240408.txt"}
]
}
]
}`
// Verify our conversion logic
var resp osvQueryResponse
if err := json.Unmarshal([]byte(osvResponse), &resp); err != nil {
t.Fatalf("failed to parse mock OSV response: %v", err)
}
vulns := convertOSVVulns(resp.Vulns, "openssl", "3.1.4-r5")
if len(vulns) != 2 {
t.Fatalf("expected 2 vulns, got %d", len(vulns))
}
// First vuln: CVE-2024-0727
v1 := vulns[0]
if v1.ID != "CVE-2024-0727" {
t.Errorf("expected CVE-2024-0727, got %s", v1.ID)
}
if v1.Package != "openssl" {
t.Errorf("expected package 'openssl', got %q", v1.Package)
}
if v1.Version != "3.1.4-r5" {
t.Errorf("expected version '3.1.4-r5', got %q", v1.Version)
}
if v1.FixedIn != "3.1.5-r0" {
t.Errorf("expected fixed in '3.1.5-r0', got %q", v1.FixedIn)
}
if v1.Severity != "MEDIUM" {
t.Errorf("expected severity MEDIUM (CVSS 5.5), got %q", v1.Severity)
}
if v1.Summary != "PKCS12 Decoding crashes" {
t.Errorf("unexpected summary: %q", v1.Summary)
}
if len(v1.References) != 2 {
t.Errorf("expected 2 references, got %d", len(v1.References))
}
// Second vuln: CVE-2024-2511
v2 := vulns[1]
if v2.ID != "CVE-2024-2511" {
t.Errorf("expected CVE-2024-2511, got %s", v2.ID)
}
if v2.FixedIn != "3.1.6-r0" {
t.Errorf("expected fixed in '3.1.6-r0', got %q", v2.FixedIn)
}
if v2.Severity != "LOW" {
t.Errorf("expected severity LOW (CVSS 3.7), got %q", v2.Severity)
}
}
func TestOSVQueryParsing_BatchResponse(t *testing.T) {
batchResponse := `{
"results": [
{
"vulns": [
{
"id": "CVE-2024-0727",
"summary": "PKCS12 Decoding crashes",
"severity": [{"type": "CVSS_V3", "score": "5.5"}],
"affected": [
{
"package": {"name": "openssl", "ecosystem": "Alpine"},
"ranges": [{"type": "ECOSYSTEM", "events": [{"introduced": "0"}, {"fixed": "3.1.5-r0"}]}]
}
],
"references": []
}
]
},
{
"vulns": []
},
{
"vulns": [
{
"id": "CVE-2024-9681",
"summary": "curl: HSTS subdomain overwrites parent cache entry",
"severity": [{"type": "CVSS_V3", "score": "6.5"}],
"affected": [
{
"package": {"name": "curl", "ecosystem": "Alpine"},
"ranges": [{"type": "ECOSYSTEM", "events": [{"introduced": "0"}, {"fixed": "8.11.1-r0"}]}]
}
],
"references": [{"type": "WEB", "url": "https://curl.se/docs/CVE-2024-9681.html"}]
}
]
}
]
}`
var resp osvBatchResponse
if err := json.Unmarshal([]byte(batchResponse), &resp); err != nil {
t.Fatalf("failed to parse batch response: %v", err)
}
if len(resp.Results) != 3 {
t.Fatalf("expected 3 result entries, got %d", len(resp.Results))
}
// First result: openssl has vulns
vulns0 := convertOSVVulns(resp.Results[0].Vulns, "openssl", "3.1.4")
if len(vulns0) != 1 {
t.Errorf("expected 1 vuln for openssl, got %d", len(vulns0))
}
// Second result: musl has no vulns
vulns1 := convertOSVVulns(resp.Results[1].Vulns, "musl", "1.2.4")
if len(vulns1) != 0 {
t.Errorf("expected 0 vulns for musl, got %d", len(vulns1))
}
// Third result: curl has vulns
vulns2 := convertOSVVulns(resp.Results[2].Vulns, "curl", "8.5.0")
if len(vulns2) != 1 {
t.Errorf("expected 1 vuln for curl, got %d", len(vulns2))
}
if vulns2[0].FixedIn != "8.11.1-r0" {
t.Errorf("expected curl fix 8.11.1-r0, got %q", vulns2[0].FixedIn)
}
}
func TestOSVQueryParsing_DatabaseSpecificSeverity(t *testing.T) {
response := `{
"vulns": [
{
"id": "DSA-5678-1",
"summary": "Some advisory",
"database_specific": {"severity": "HIGH"},
"affected": [
{
"package": {"name": "libc6", "ecosystem": "Debian"},
"ranges": [{"type": "ECOSYSTEM", "events": [{"introduced": "0"}, {"fixed": "2.36-10"}]}]
}
],
"references": []
}
]
}`
var resp osvQueryResponse
if err := json.Unmarshal([]byte(response), &resp); err != nil {
t.Fatalf("failed to parse: %v", err)
}
vulns := convertOSVVulns(resp.Vulns, "libc6", "2.36-9")
if len(vulns) != 1 {
t.Fatalf("expected 1 vuln, got %d", len(vulns))
}
if vulns[0].Severity != "HIGH" {
t.Errorf("expected HIGH from database_specific, got %q", vulns[0].Severity)
}
}
func TestOSVQueryParsing_DuplicateIDs(t *testing.T) {
response := `{
"vulns": [
{
"id": "CVE-2024-0727",
"summary": "First mention",
"affected": [],
"references": []
},
{
"id": "CVE-2024-0727",
"summary": "Duplicate mention",
"affected": [],
"references": []
}
]
}`
var resp osvQueryResponse
json.Unmarshal([]byte(response), &resp)
vulns := convertOSVVulns(resp.Vulns, "openssl", "3.1.4")
if len(vulns) != 1 {
t.Errorf("expected dedup to 1 vuln, got %d", len(vulns))
}
}
// ── TestScanReport ───────────────────────────────────────────────────────────
func TestScanReport_Format(t *testing.T) {
report := &ScanReport{
Target: "alpine-3.19",
OS: "Alpine Linux v3.19",
Ecosystem: "Alpine",
PackageCount: 42,
Vulns: []VulnResult{
{
ID: "CVE-2024-0727", Package: "openssl", Version: "3.1.4",
FixedIn: "3.1.5", Severity: "CRITICAL", Summary: "PKCS12 crash",
},
{
ID: "CVE-2024-2511", Package: "openssl", Version: "3.1.4",
FixedIn: "3.1.6", Severity: "HIGH", Summary: "TLS memory growth",
},
{
ID: "CVE-2024-9999", Package: "busybox", Version: "1.36.1",
FixedIn: "", Severity: "MEDIUM", Summary: "Buffer overflow",
},
},
ScanTime: 1200 * time.Millisecond,
}
out := FormatReport(report, "")
// Check key elements
if !strings.Contains(out, "alpine-3.19") {
t.Error("report missing target name")
}
if !strings.Contains(out, "Alpine Linux v3.19") {
t.Error("report missing OS name")
}
if !strings.Contains(out, "42 detected") {
t.Error("report missing package count")
}
if !strings.Contains(out, "CRITICAL") {
t.Error("report missing CRITICAL severity")
}
if !strings.Contains(out, "CVE-2024-0727") {
t.Error("report missing CVE ID")
}
if !strings.Contains(out, "(fixed in 3.1.5)") {
t.Error("report missing fixed version")
}
if !strings.Contains(out, "(no fix available)") {
t.Error("report missing 'no fix available' for busybox")
}
if !strings.Contains(out, "1 critical, 1 high, 1 medium, 0 low (3 total)") {
t.Errorf("report summary wrong, got:\n%s", out)
}
if !strings.Contains(out, "1.2s") {
t.Error("report missing scan time")
}
}
func TestScanReport_FormatWithSeverityFilter(t *testing.T) {
report := &ScanReport{
Target: "test",
OS: "Debian",
PackageCount: 10,
Vulns: []VulnResult{
{ID: "CVE-1", Severity: "LOW", Package: "pkg1", Version: "1.0"},
{ID: "CVE-2", Severity: "MEDIUM", Package: "pkg2", Version: "2.0"},
{ID: "CVE-3", Severity: "HIGH", Package: "pkg3", Version: "3.0"},
},
ScanTime: 500 * time.Millisecond,
}
out := FormatReport(report, "high")
if strings.Contains(out, "CVE-1") {
t.Error("LOW vuln should be filtered out")
}
if strings.Contains(out, "CVE-2") {
t.Error("MEDIUM vuln should be filtered out")
}
if !strings.Contains(out, "CVE-3") {
t.Error("HIGH vuln should be included")
}
}
func TestScanReport_FormatNoVulns(t *testing.T) {
report := &ScanReport{
Target: "clean-image",
OS: "Alpine",
PackageCount: 5,
Vulns: nil,
ScanTime: 200 * time.Millisecond,
}
out := FormatReport(report, "")
if !strings.Contains(out, "No vulnerabilities found") {
t.Error("report should indicate no vulnerabilities")
}
}
func TestScanReport_JSON(t *testing.T) {
report := &ScanReport{
Target: "test",
OS: "Alpine Linux v3.19",
Ecosystem: "Alpine",
PackageCount: 3,
Vulns: []VulnResult{
{
ID: "CVE-2024-0727", Package: "openssl", Version: "3.1.4",
FixedIn: "3.1.5", Severity: "MEDIUM", Summary: "PKCS12 crash",
References: []string{"https://example.com"},
},
},
ScanTime: 1 * time.Second,
}
jsonStr, err := FormatReportJSON(report)
if err != nil {
t.Fatalf("FormatReportJSON failed: %v", err)
}
// Verify it's valid JSON that round-trips
var parsed ScanReport
if err := json.Unmarshal([]byte(jsonStr), &parsed); err != nil {
t.Fatalf("JSON doesn't round-trip: %v", err)
}
if parsed.Target != "test" {
t.Errorf("target mismatch after round-trip: %q", parsed.Target)
}
if len(parsed.Vulns) != 1 {
t.Errorf("expected 1 vuln after round-trip, got %d", len(parsed.Vulns))
}
}
// ── TestSeverity ─────────────────────────────────────────────────────────────
func TestSeverityAtLeast(t *testing.T) {
tests := []struct {
sev string
threshold string
expected bool
}{
{"CRITICAL", "HIGH", true},
{"HIGH", "HIGH", true},
{"MEDIUM", "HIGH", false},
{"LOW", "MEDIUM", false},
{"CRITICAL", "LOW", true},
{"LOW", "LOW", true},
{"UNKNOWN", "LOW", false},
}
for _, tt := range tests {
if got := SeverityAtLeast(tt.sev, tt.threshold); got != tt.expected {
t.Errorf("SeverityAtLeast(%q, %q) = %v, want %v", tt.sev, tt.threshold, got, tt.expected)
}
}
}
func TestCVSSToSeverity(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"9.8", "CRITICAL"},
{"9.0", "CRITICAL"},
{"7.5", "HIGH"},
{"7.0", "HIGH"},
{"5.5", "MEDIUM"},
{"4.0", "MEDIUM"},
{"3.7", "LOW"},
{"0.5", "LOW"},
}
for _, tt := range tests {
if got := cvssToSeverity(tt.input); got != tt.expected {
t.Errorf("cvssToSeverity(%q) = %q, want %q", tt.input, got, tt.expected)
}
}
}
func TestNormalizeSeverity(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"CRITICAL", "CRITICAL"},
{"critical", "CRITICAL"},
{"IMPORTANT", "HIGH"},
{"MODERATE", "MEDIUM"},
{"NEGLIGIBLE", "LOW"},
{"UNIMPORTANT", "LOW"},
{"whatever", "UNKNOWN"},
}
for _, tt := range tests {
if got := normalizeSeverity(tt.input); got != tt.expected {
t.Errorf("normalizeSeverity(%q) = %q, want %q", tt.input, got, tt.expected)
}
}
}
// ── TestCountBySeverity ──────────────────────────────────────────────────────
func TestCountBySeverity(t *testing.T) {
report := &ScanReport{
Vulns: []VulnResult{
{Severity: "CRITICAL"},
{Severity: "CRITICAL"},
{Severity: "HIGH"},
{Severity: "MEDIUM"},
{Severity: "MEDIUM"},
{Severity: "MEDIUM"},
{Severity: "LOW"},
{Severity: "UNKNOWN"},
},
}
counts := report.CountBySeverity()
if counts.Critical != 2 {
t.Errorf("critical: got %d, want 2", counts.Critical)
}
if counts.High != 1 {
t.Errorf("high: got %d, want 1", counts.High)
}
if counts.Medium != 3 {
t.Errorf("medium: got %d, want 3", counts.Medium)
}
if counts.Low != 1 {
t.Errorf("low: got %d, want 1", counts.Low)
}
if counts.Unknown != 1 {
t.Errorf("unknown: got %d, want 1", counts.Unknown)
}
if counts.Total != 8 {
t.Errorf("total: got %d, want 8", counts.Total)
}
}
// ── TestScanRootfs (with mock OSV server) ────────────────────────────────────
func TestScanRootfs_WithMockOSV(t *testing.T) {
// Create a mock OSV batch server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/querybatch" {
http.Error(w, "not found", 404)
return
}
// Return a canned response: one vuln for openssl, nothing for musl
resp := osvBatchResponse{
Results: []osvQueryResponse{
{ // openssl result
Vulns: []osvVuln{
{
ID: "CVE-2024-0727",
Summary: "PKCS12 crash",
Severity: []struct {
Type string `json:"type"`
Score string `json:"score"`
}{
{Type: "CVSS_V3", Score: "9.8"},
},
Affected: []struct {
Package struct {
Name string `json:"name"`
Ecosystem string `json:"ecosystem"`
} `json:"package"`
Ranges []struct {
Type string `json:"type"`
Events []struct {
Introduced string `json:"introduced,omitempty"`
Fixed string `json:"fixed,omitempty"`
} `json:"events"`
} `json:"ranges"`
}{
{
Package: struct {
Name string `json:"name"`
Ecosystem string `json:"ecosystem"`
}{Name: "openssl", Ecosystem: "Alpine"},
Ranges: []struct {
Type string `json:"type"`
Events []struct {
Introduced string `json:"introduced,omitempty"`
Fixed string `json:"fixed,omitempty"`
} `json:"events"`
}{
{
Type: "ECOSYSTEM",
Events: []struct {
Introduced string `json:"introduced,omitempty"`
Fixed string `json:"fixed,omitempty"`
}{
{Introduced: "0"},
{Fixed: "3.1.5-r0"},
},
},
},
},
},
},
},
},
{ // musl result - no vulns
Vulns: nil,
},
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}))
defer server.Close()
// Patch the batch URL for this test
origURL := osvQueryBatchURL
// We can't modify the const, so we test via the lower-level functions
// Instead, test the integration manually
// Create a rootfs with Alpine packages
rootfs := createTempRootfs(t, map[string]string{
"etc/os-release": `PRETTY_NAME="Alpine Linux v3.19"
ID=alpine
VERSION_ID=3.19.1`,
"lib/apk/db/installed": `P:openssl
V:3.1.4-r5
P:musl
V:1.2.4-r4
`,
})
// Test DetectOS
osName, eco, err := DetectOS(rootfs)
if err != nil {
t.Fatalf("DetectOS: %v", err)
}
if osName != "Alpine Linux v3.19" {
t.Errorf("OS: got %q", osName)
}
if eco != "Alpine" {
t.Errorf("ecosystem: got %q", eco)
}
// Test ListPackages
pkgs, err := ListPackages(rootfs)
if err != nil {
t.Fatalf("ListPackages: %v", err)
}
if len(pkgs) != 2 {
t.Fatalf("expected 2 packages, got %d", len(pkgs))
}
// Test batch query against mock server using the internal function
client := server.Client()
_ = origURL // acknowledge to avoid lint
vulnMap, err := queryOSVBatchWithURL(client, eco, pkgs, server.URL+"/v1/querybatch")
if err != nil {
t.Fatalf("queryOSVBatch: %v", err)
}
// Should have vulns for openssl, not for musl
if len(vulnMap) == 0 {
t.Fatal("expected some vulnerabilities")
}
opensslKey := "openssl@3.1.4-r5"
if _, ok := vulnMap[opensslKey]; !ok {
t.Errorf("expected vulns for %s, keys: %v", opensslKey, mapKeys(vulnMap))
}
}
// ── TestRpmOutput ────────────────────────────────────────────────────────────
func TestRpmOutputParsing(t *testing.T) {
data := []byte("bash\t5.2.15-3.el9\nzlib\t1.2.11-40.el9\nopenssl-libs\t3.0.7-27.el9\n")
pkgs, err := parseRpmOutput(data)
if err != nil {
t.Fatalf("parseRpmOutput: %v", err)
}
if len(pkgs) != 3 {
t.Fatalf("expected 3 packages, got %d", len(pkgs))
}
names := map[string]string{}
for _, p := range pkgs {
names[p.Name] = p.Version
if p.Source != "rpm" {
t.Errorf("expected source 'rpm', got %q", p.Source)
}
}
if names["bash"] != "5.2.15-3.el9" {
t.Errorf("wrong version for bash: %q", names["bash"])
}
if names["openssl-libs"] != "3.0.7-27.el9" {
t.Errorf("wrong version for openssl-libs: %q", names["openssl-libs"])
}
}
// ── Helpers ──────────────────────────────────────────────────────────────────
// createTempRootfs creates a temporary directory structure mimicking a rootfs.
func createTempRootfs(t *testing.T, files map[string]string) string {
t.Helper()
root := t.TempDir()
for relPath, content := range files {
fullPath := filepath.Join(root, relPath)
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
t.Fatalf("mkdir %s: %v", filepath.Dir(fullPath), err)
}
if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil {
t.Fatalf("write %s: %v", fullPath, err)
}
}
return root
}
func mapKeys(m map[string][]VulnResult) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}