diff --git a/PHPGuard/AuditStore.swift b/PHPGuard/AuditStore.swift index a4cd827..8e5b480 100644 --- a/PHPGuard/AuditStore.swift +++ b/PHPGuard/AuditStore.swift @@ -166,8 +166,8 @@ import SwiftUI ) } - scanStage = "Checking Ubuntu advisories" - scanDetail = "Comparing CVEs with Ubuntu package advisories" + scanStage = "Checking vendor advisories" + scanDetail = "Verifying available vendor evidence; unsupported distributions remain unverified" scanFraction = 0.95 if report.ubuntuReleaseID == "ubuntu" { @@ -177,9 +177,25 @@ import SwiftUI ) } + // NVD CPE matches are candidates, never proof of host exposure. + // Only Ubuntu has a vendor advisory adapter at present. + if report.ubuntuReleaseID != "ubuntu" { + all = all.map { candidate in + var item = candidate + item.vendorStatus = "Unverified" + item.vendorEvidence = + "No vendor advisory adapter for this distribution. " + + "NVD upstream version matches cannot establish package exposure." + return item + } + } report.cveAssessments = all.sorted { $0.cveID > $1.cveID } + let verifiedCount = all.filter { + ["Vulnerable", "Patched", "Not affected"].contains($0.vendorStatus ?? "") + }.count report.cveAssessmentStatus = failures.isEmpty - ? "NVD assessment complete; Ubuntu advisories checked where available" + ? "NVD candidates: \(all.count); vendor-verified: \(verifiedCount). " + + "Unverified entries are not security clearance." : "Partial/unknown: " + failures.joined(separator: "; ") report.cveAssessedAt = Date() } diff --git a/PHPGuard/ContentView.swift b/PHPGuard/ContentView.swift index 2ccba46..f875cbb 100644 --- a/PHPGuard/ContentView.swift +++ b/PHPGuard/ContentView.swift @@ -205,7 +205,7 @@ struct ContentView: View { : "shield.lefthalf.filled" ) - Text("Ubuntu: " + vendorStatus) + Text("Vendor: " + vendorStatus) .font(.subheadline.bold()) } .foregroundStyle( diff --git a/PHPGuard/SSHScanner.swift b/PHPGuard/SSHScanner.swift index 30c8f2e..b52fe5f 100644 --- a/PHPGuard/SSHScanner.swift +++ b/PHPGuard/SSHScanner.swift @@ -20,6 +20,7 @@ enum SSHScanner { . /etc/os-release printf 'ID=%s\n' "${ID:-unknown}" printf 'ID_LIKE=%s\n' "${ID_LIKE:-}" + printf 'PRETTY_NAME=%s\n' "${PRETTY_NAME:-}" printf 'VERSION_ID=%s\n' "${VERSION_ID:-unknown}" printf 'VERSION_CODENAME=%s\n' "${VERSION_CODENAME:-unknown}" fi @@ -69,7 +70,7 @@ enum SSHScanner { apk) apk info -v 2>/dev/null | - grep -E '^php[0-9]*-' | + grep -E '^php([0-9]+)?(-|[0-9])' | sort -u || true ;; @@ -119,11 +120,11 @@ enum SSHScanner { while IFS= read -r entry; do case "$entry" in php*) ;; *) continue ;; esac + # APK revisions can include suffixes such as _p1 and -r0. + # Keep the original package string if parsing is ambiguous. name=$(printf '%s\n' "$entry" | - sed -E 's/-[0-9]+(\.[0-9]+)+(-r[0-9]+)?$//') - + sed -E 's/-[0-9]+(\.[0-9]+)+([^[:space:]]*)$//') version=${entry#"$name"-} - [ "$version" != "$entry" ] || continue printf '%s|%s|%s|%s\n' \ @@ -557,9 +558,13 @@ enum SSHScanner { } else { result.dockerAuditStatus = "\(result.dockerPHP.count) PHP runtimes; \(noPHP.count) containers without PHP CLI (not a security finding)" } - result.packageAuditStatus = result.packageOrigins.contains("APT metadata unavailable") - ? "APT package provenance unavailable" - : "Installed and candidate package versions collected; vendor CVE fix status requires advisory verification" + let packageManager = fields["PACKAGE_MANAGER"]?.first ?? "unknown" + let metadataUnavailable = result.packageOrigins.contains { + $0.localizedCaseInsensitiveContains("unavailable") + } + result.packageAuditStatus = metadataUnavailable + ? "\(packageManager.uppercased()) package provenance unavailable; CVE exposure unverified" + : "\(packageManager.uppercased()) inventory collected; vendor CVE status requires independent verification" // Recommendations are derived from this server's collected evidence. var actions: [String] = [] diff --git a/PHPGuard/VulnerabilityService.swift b/PHPGuard/VulnerabilityService.swift index 439566f..5abd1f0 100644 --- a/PHPGuard/VulnerabilityService.swift +++ b/PHPGuard/VulnerabilityService.swift @@ -385,6 +385,21 @@ extension VulnerabilityService { } } + // A package from a third-party PPA or custom repository cannot be + // certified solely using the Ubuntu distribution advisory. + // Require positive repository provenance before issuing a verdict. + let hasUbuntuOrigin = false // Conservative: repository listing does not prove installed-version origin. + guard hasUbuntuOrigin else { + return assessments.map { original in + var item = original + item.vendorStatus = "Unverified" + item.vendorEvidence = + "Ubuntu archive provenance not established for installed PHP packages. " + + "Third-party or custom builds require independent verification." + return item + } + } + let installed = installedPackages(report.packageSources) guard !installed.isEmpty else { diff --git a/Scripts/backup.sh b/Scripts/backup.sh new file mode 100755 index 0000000..6d89c62 --- /dev/null +++ b/Scripts/backup.sh @@ -0,0 +1,9 @@ +zip -r ~/Downloads/PHPGuard-Source.zip PHPGuard \ + -x '*/.git/*' \ + '*/.build/*' \ + '*/DerivedData/*' \ + '*/build/*' \ + '*/dist/*' \ + '*/.DS_Store' \ + '*.dmg' \ + '*.zip' \ No newline at end of file diff --git a/Scripts/build.sh.bak-20261022-145251 b/Scripts/build.sh.bak-20261022-145251 deleted file mode 100755 index 93d3a30..0000000 --- a/Scripts/build.sh.bak-20261022-145251 +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/zsh -set -euo pipefail -ROOT="$(cd "$(dirname "$0")/.." && pwd)" -CONFIG="${CONFIGURATION:-Debug}" -DERIVED="${DERIVED_DATA_PATH:-$ROOT/.build}" -command -v xcodebuild >/dev/null || { echo "Xcode command line tools required" >&2; exit 1; } -xcodebuild -project "$ROOT/PHPGuard.xcodeproj" -scheme PHPGuard -configuration "$CONFIG" -derivedDataPath "$DERIVED" CODE_SIGNING_ALLOWED=NO build -APP="$DERIVED/Build/Products/$CONFIG/PHPGuard.app" -[[ -d "$APP" ]] || { echo "Built application not found: $APP" >&2; exit 1; } -echo "Built: $APP" -if [[ "${LAUNCH_AFTER_BUILD:-1}" == "1" ]]; then open "$APP"; fi diff --git a/Scripts/test.sh b/Scripts/test.sh new file mode 100755 index 0000000..0ebcc7e --- /dev/null +++ b/Scripts/test.sh @@ -0,0 +1 @@ +xcodebuild -list -project PHPGuard.xcodeproj \ No newline at end of file diff --git a/docs/SECURITY-READINESS.md b/docs/SECURITY-READINESS.md new file mode 100644 index 0000000..ea3ac8f --- /dev/null +++ b/docs/SECURITY-READINESS.md @@ -0,0 +1,22 @@ +# PHPGuard security assessment readiness + +## Status: not production-validated + +The scan collects package inventory and upstream NVD PHP CVE *candidates*. A version match, lack of upgrades, or an available upgrade does not establish exposure or remediation. + +### Verified statuses + +- `Unverified` is the default for unsupported distribution/vendor combinations, unknown package provenance, absent advisories, and network failures. +- Ubuntu's existing advisory adapter is intentionally **fail closed** until installed package provenance is independently proven at the installed-version level. Seeing an Ubuntu repository in APT policy is insufficient: the installed package may be from a PPA or custom build. +- Docker PHP is not covered by host package advisories. +- NVD discovery is bounded and may omit CVEs; no-results is not a clean security bill. + +### Before production release + +1. Implement per-installed-version repository origin verification and test Ubuntu advisory response parsing against current fixtures. +2. Implement and test Debian, Red Hat-family, SUSE, Amazon Linux and Alpine advisory adapters using official vendor data and native package version comparison. +3. Add test fixtures for package manager outputs, package origin edge cases, CVE states, missing metadata, errors, and containers. +4. Run integration tests on each supported distribution with minimal and restricted SSH permissions. +5. Run Xcode build, UI checks, signing and notarization on macOS; this archive has not been built in Xcode here. + +This package is a security-hardening development snapshot, **not a production release**. diff --git a/tests/test_scanner.py b/tests/test_scanner.py new file mode 100644 index 0000000..b921247 --- /dev/null +++ b/tests/test_scanner.py @@ -0,0 +1,27 @@ +"""Static safety regression tests; run with python3 -m unittest discover -s tests.""" +from pathlib import Path +import subprocess +import unittest +ROOT = Path(__file__).resolve().parents[1] +class ScannerTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + source = (ROOT / 'PHPGuard/SSHScanner.swift').read_text() + cls.script = source.split('static let script = #"""',1)[1].split('"""#',1)[0] + def test_posix_shell_syntax(self): + p = subprocess.run(['sh','-n'],input=self.script,text=True,capture_output=True) + self.assertEqual(p.returncode,0,p.stderr) + def test_required_sections(self): + for section in ('OS_RELEASE','PACKAGE_MANAGER','PACKAGES','PACKAGE_SOURCES','UPDATES','PACKAGE_ORIGINS','DOCKER','END'): + self.assertIn('===%s===' % section,self.script) + def test_supported_managers(self): + for manager in ('apt','dnf','yum','rpm','zypper','apk'): + self.assertIn(manager,self.script) + def test_no_package_mutation(self): + for command in ('apt upgrade','apt install','dnf update','yum update','apk upgrade','zypper update'): + self.assertNotIn(command,self.script) + def test_unknown_vendor_not_verified(self): + store=(ROOT/'PHPGuard/AuditStore.swift').read_text() + self.assertIn('No vendor advisory adapter for this distribution',store) + self.assertIn('vendor-verified:',store) +if __name__=='__main__': unittest.main()