diff --git a/PHPGuard/ContentView.swift b/PHPGuard/ContentView.swift index e4e41e3..0707715 100644 --- a/PHPGuard/ContentView.swift +++ b/PHPGuard/ContentView.swift @@ -168,68 +168,6 @@ struct ContentView: View { Text("Docker assessment: \(result.dockerAuditStatus)").foregroundStyle(.secondary) dataSection("Docker coverage (PHP CLI not detected)", result.dockerCoverage) dataSection("Security triage — follow-up actions", result.securityTriage) - VStack(alignment: .leading, spacing: 10) { - Text("CVE assessment").font(.title2.bold()) - Text(result.cveAssessmentStatus).foregroundStyle(.secondary) - Text("NVD identifies upstream candidates. Ubuntu advisory status is checked separately. A published fix does not prove that the installed package is patched.") - .font(.caption).foregroundStyle(.secondary) - if result.cveAssessments.isEmpty { - Text("No upstream PHP CVE candidates recorded. This is not a clean security bill.") - .foregroundStyle(.secondary) - } - ForEach(result.cveAssessments) { item in - VStack(alignment: .leading, spacing: 5) { - HStack { - Link(item.cveID, destination: URL(string: item.referenceURL)!) - .font(.headline) - Text(item.severity).font(.caption.bold()) - Spacer() - Text(item.status).foregroundStyle(.orange) - } - Text(item.runtime + " — " + item.summary).lineLimit(3) - Text(item.evidence) - .font(.caption) - .foregroundStyle(.secondary) - - if let vendorStatus = item.vendorStatus { - - HStack(spacing: 8) { - - Image( - systemName: - vendorStatus == "Not affected" || - vendorStatus == "Patched" - ? "checkmark.shield.fill" - : vendorStatus == "Vulnerable" - ? "exclamationmark.shield.fill" - : "shield.lefthalf.filled" - ) - - Text("Vendor: " + vendorStatus) - .font(.subheadline.bold()) - } - .foregroundStyle( - vendorStatus == "Not affected" || - vendorStatus == "Patched" - ? Color.green - : vendorStatus == "Vulnerable" - ? Color.red - : Color.orange - ) - .padding(.top, 5) - } - - if let vendorEvidence = item.vendorEvidence { - - Text(vendorEvidence) - .font(.caption) - .foregroundStyle(.secondary) - .textSelection(.enabled) - } - }.padding(10) - .background(.quaternary.opacity(0.22), in: RoundedRectangle(cornerRadius: 10)) - } - } // Repository matching and binary verification are // separate from vendor CVE verification. VStack(alignment: .leading, spacing: 10) { @@ -305,6 +243,81 @@ struct ContentView: View { in: RoundedRectangle(cornerRadius: 10) ) + VStack(alignment: .leading, spacing: 10) { + Text("CVE assessment").font(.title2.bold()) + Text(result.cveAssessmentStatus).foregroundStyle(.secondary) + Text("NVD identifies upstream candidates. Ubuntu advisory status is checked separately. A published fix does not prove that the installed package is patched.") + .font(.caption).foregroundStyle(.secondary) + if result.cveAssessments.isEmpty { + Text("No upstream PHP CVE candidates recorded. This is not a clean security bill.") + .foregroundStyle(.secondary) + } + ForEach(result.cveAssessments) { item in + VStack(alignment: .leading, spacing: 5) { + HStack { + Link(item.cveID, destination: URL(string: item.referenceURL)!) + .font(.headline) + Text(item.severity).font(.caption.bold()) + Spacer() + Text(item.status).foregroundStyle(.orange) + } + Text(item.runtime + " — " + item.summary) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity, alignment: .leading) + Text(item.evidence) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity, alignment: .leading) + + if let vendorStatus = item.vendorStatus { + + HStack(spacing: 8) { + + Image( + systemName: + vendorStatus == "Not affected" || + vendorStatus == "Patched" + ? "checkmark.shield.fill" + : vendorStatus == "Vulnerable" + ? "exclamationmark.shield.fill" + : "shield.lefthalf.filled" + ) + + Text("Vendor: " + vendorStatus) + .font(.subheadline.bold()) + } + .foregroundStyle( + vendorStatus == "Not affected" || + vendorStatus == "Patched" + ? Color.green + : vendorStatus == "Vulnerable" + ? Color.red + : Color.orange + ) + .padding(.top, 5) + } + + if let vendorEvidence = item.vendorEvidence, + !vendorEvidence.hasPrefix( + "Installed PHP package versions match official Ubuntu repository entries." + ) { + + Text(vendorEvidence) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + .textSelection(.enabled) + } + } + .padding(10) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + .quaternary.opacity(0.22), + in: RoundedRectangle(cornerRadius: 10) + ) + } + } HStack { Text("Security findings").font(.title2.bold()) Spacer() diff --git a/PHPGuard/VulnerabilityService.swift b/PHPGuard/VulnerabilityService.swift index 7a143ab..8add73f 100644 --- a/PHPGuard/VulnerabilityService.swift +++ b/PHPGuard/VulnerabilityService.swift @@ -393,35 +393,10 @@ extension VulnerabilityService { // may unlock vendor conclusions. Repository listings alone never qualify. line == "VERIFIED_INSTALLED_ORIGIN|ubuntu|" + report.ubuntuCodename } - guard hasUbuntuOrigin else { - return assessments.map { original in - var item = original - item.vendorStatus = "Unverified" - let ubuntuMatches = report.packageOrigins.filter { - $0.hasPrefix("UBUNTU_REPOSITORY_MATCH|") - } - - let otherMatches = report.packageOrigins.filter { - $0.hasPrefix("OTHER_REPOSITORY_MATCH|") - } - - if !ubuntuMatches.isEmpty && otherMatches.isEmpty { - item.vendorEvidence = - "Installed PHP package versions match official Ubuntu repository entries. " + - "Installed binary provenance has not been independently verified; " + - "vendor CVE status remains Unverified." - } else if !otherMatches.isEmpty { - item.vendorEvidence = - "One or more PHP package versions match third-party repositories. " + - "Ubuntu vendor CVE status remains Unverified." - } else { - item.vendorEvidence = - "Ubuntu archive provenance not established for installed PHP packages. " + - "No confirmed Ubuntu repository matches were collected." - } - return item - } - } + // Repository matching is sufficient to investigate Ubuntu + // advisories, but not to certify installed binary integrity. + // Advisory results remain Unverified unless independently + // verified installed-package provenance is available. let installed = installedPackages(report.packageSources) @@ -496,6 +471,11 @@ extension VulnerabilityService { forHTTPHeaderField: "Accept" ) + NSLog( + "PHPGuard Ubuntu advisory: fetching %@", + original.cveID + ) + let (data, response) = try await URLSession.shared.data(for: request) @@ -714,6 +694,21 @@ extension VulnerabilityService { error.localizedDescription } + if !hasUbuntuOrigin { + let advisoryStatus = item.vendorStatus ?? "Unverified" + let advisoryEvidence = item.vendorEvidence ?? "" + + item.vendorStatus = "Unverified" + + item.vendorEvidence = + "Ubuntu advisory assessment: " + + advisoryStatus + + ". " + + advisoryEvidence + + "\\nInstalled binary provenance has not been independently verified; " + + "this advisory result is not a confirmed host security verdict." + } + output.append(item) }