Updates to scanner

This commit is contained in:
Mark Gregory
2026-10-22 12:49:07 +01:00
parent 9970beadaf
commit c522b161da
8 changed files with 417 additions and 4 deletions
+14
View File
@@ -7,6 +7,11 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
A10000000000000000000002 /* VulnerabilityService.swift in Sources */ = {
isa = PBXBuildFile;
fileRef = A10000000000000000000001 /* VulnerabilityService.swift */;
};
0000000000000000000000C8 /* PHPGuardApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000064 /* PHPGuardApp.swift */; }; 0000000000000000000000C8 /* PHPGuardApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000064 /* PHPGuardApp.swift */; };
0000000000000000000000C9 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000065 /* Models.swift */; }; 0000000000000000000000C9 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000065 /* Models.swift */; };
0000000000000000000000CA /* SSHScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000066 /* SSHScanner.swift */; }; 0000000000000000000000CA /* SSHScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000066 /* SSHScanner.swift */; };
@@ -15,6 +20,13 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
A10000000000000000000001 /* VulnerabilityService.swift */ = {
isa = PBXFileReference;
lastKnownFileType = sourcecode.swift;
path = VulnerabilityService.swift;
sourceTree = "<group>";
};
000000000000000000000030 /* PHPGuard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PHPGuard.app; sourceTree = BUILT_PRODUCTS_DIR; }; 000000000000000000000030 /* PHPGuard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PHPGuard.app; sourceTree = BUILT_PRODUCTS_DIR; };
000000000000000000000064 /* PHPGuardApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPGuardApp.swift; sourceTree = "<group>"; }; 000000000000000000000064 /* PHPGuardApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPGuardApp.swift; sourceTree = "<group>"; };
000000000000000000000065 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; }; 000000000000000000000065 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; };
@@ -39,6 +51,7 @@
000000000000000000000065 /* Models.swift */, 000000000000000000000065 /* Models.swift */,
000000000000000000000066 /* SSHScanner.swift */, 000000000000000000000066 /* SSHScanner.swift */,
000000000000000000000067 /* AuditStore.swift */, 000000000000000000000067 /* AuditStore.swift */,
A10000000000000000000001 /* VulnerabilityService.swift */,
000000000000000000000068 /* ContentView.swift */, 000000000000000000000068 /* ContentView.swift */,
); );
path = PHPGuard; path = PHPGuard;
@@ -110,6 +123,7 @@
0000000000000000000000C9 /* Models.swift in Sources */, 0000000000000000000000C9 /* Models.swift in Sources */,
0000000000000000000000CA /* SSHScanner.swift in Sources */, 0000000000000000000000CA /* SSHScanner.swift in Sources */,
0000000000000000000000CB /* AuditStore.swift in Sources */, 0000000000000000000000CB /* AuditStore.swift in Sources */,
A10000000000000000000002 /* VulnerabilityService.swift in Sources */,
0000000000000000000000CC /* ContentView.swift in Sources */, 0000000000000000000000CC /* ContentView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -0,0 +1,194 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
0000000000000000000000C8 /* PHPGuardApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000064 /* PHPGuardApp.swift */; };
0000000000000000000000C9 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000065 /* Models.swift */; };
0000000000000000000000CA /* SSHScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000066 /* SSHScanner.swift */; };
0000000000000000000000CB /* AuditStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000067 /* AuditStore.swift */; };
0000000000000000000000CC /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000068 /* ContentView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
000000000000000000000030 /* PHPGuard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PHPGuard.app; sourceTree = BUILT_PRODUCTS_DIR; };
000000000000000000000064 /* PHPGuardApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPGuardApp.swift; sourceTree = "<group>"; };
000000000000000000000065 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; };
000000000000000000000066 /* SSHScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHScanner.swift; sourceTree = "<group>"; };
000000000000000000000067 /* AuditStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuditStore.swift; sourceTree = "<group>"; };
000000000000000000000068 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXGroup section */
000000000000000000000010 = {
isa = PBXGroup;
children = (
000000000000000000000011 /* PHPGuard */,
000000000000000000000012 /* Products */,
);
sourceTree = "<group>";
};
000000000000000000000011 /* PHPGuard */ = {
isa = PBXGroup;
children = (
000000000000000000000064 /* PHPGuardApp.swift */,
000000000000000000000065 /* Models.swift */,
000000000000000000000066 /* SSHScanner.swift */,
000000000000000000000067 /* AuditStore.swift */,
000000000000000000000068 /* ContentView.swift */,
);
path = PHPGuard;
sourceTree = "<group>";
};
000000000000000000000012 /* Products */ = {
isa = PBXGroup;
children = (
000000000000000000000030 /* PHPGuard.app */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
000000000000000000000020 /* PHPGuard */ = {
isa = PBXNativeTarget;
buildConfigurationList = 000000000000000000000042 /* Build configuration list for PBXNativeTarget "PHPGuard" */;
buildPhases = (
000000000000000000000021 /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = PHPGuard;
productName = PHPGuard;
productReference = 000000000000000000000030 /* PHPGuard.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
000000000000000000000001 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1600;
TargetAttributes = {
000000000000000000000020 = {
CreatedOnToolsVersion = 16.0;
};
};
};
buildConfigurationList = 000000000000000000000041 /* Build configuration list for PBXProject "PHPGuard" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 000000000000000000000010;
productRefGroup = 000000000000000000000012 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
000000000000000000000020 /* PHPGuard */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
000000000000000000000021 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0000000000000000000000C8 /* PHPGuardApp.swift in Sources */,
0000000000000000000000C9 /* Models.swift in Sources */,
0000000000000000000000CA /* SSHScanner.swift in Sources */,
0000000000000000000000CB /* AuditStore.swift in Sources */,
0000000000000000000000CC /* ContentView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
000000000000000000000051 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
SDKROOT = macosx;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
000000000000000000000052 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
SDKROOT = macosx;
SWIFT_VERSION = 5.0;
};
name = Release;
};
000000000000000000000053 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
MACOSX_DEPLOYMENT_TARGET = 15.0;
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.PHPGuard;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
000000000000000000000054 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
MACOSX_DEPLOYMENT_TARGET = 15.0;
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.PHPGuard;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
000000000000000000000041 /* Build configuration list for PBXProject "PHPGuard" */ = {
isa = XCConfigurationList;
buildConfigurations = (
000000000000000000000051 /* Debug */,
000000000000000000000052 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
000000000000000000000042 /* Build configuration list for PBXNativeTarget "PHPGuard" */ = {
isa = XCConfigurationList;
buildConfigurations = (
000000000000000000000053 /* Debug */,
000000000000000000000054 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Debug;
};
/* End XCConfigurationList section */
};
rootObject = 000000000000000000000001 /* Project object */;
}
+1
View File
@@ -104,6 +104,7 @@ struct ContentView: View {
dataSection("Nginx FastCGI routes", result.nginxUpstreams) dataSection("Nginx FastCGI routes", result.nginxUpstreams)
dataSection("Installed packages", result.packageVersions) dataSection("Installed packages", result.packageVersions)
dataSection("Available PHP package upgrades", result.availableUpdates) dataSection("Available PHP package upgrades", result.availableUpdates)
dataSection("APT package origins and candidate revisions", result.packageOrigins)
Text("Package assessment: \(result.packageAuditStatus)").foregroundStyle(.secondary) Text("Package assessment: \(result.packageAuditStatus)").foregroundStyle(.secondary)
dataSection("PHP detected in running Docker containers", result.dockerPHP) dataSection("PHP detected in running Docker containers", result.dockerPHP)
Text("Docker assessment: \(result.dockerAuditStatus)").foregroundStyle(.secondary) Text("Docker assessment: \(result.dockerAuditStatus)").foregroundStyle(.secondary)
+46 -1
View File
@@ -28,14 +28,59 @@ struct AuditResult: Codable, Identifiable {
var nginxUpstreams: [String] = [] var nginxUpstreams: [String] = []
var packageVersions: [String] = [] var packageVersions: [String] = []
var availableUpdates: [String] = [] var availableUpdates: [String] = []
var packageOrigins: [String] = []
var dockerPHP: [String] = [] var dockerPHP: [String] = []
var packageAuditStatus = "Not checked" var packageAuditStatus = "Not checked"
var dockerAuditStatus = "Not checked" var dockerAuditStatus = "Not checked"
var findings: [Finding] = [] var findings: [Finding] = []
var rawOutput = "" var rawOutput = ""
var error: String? = nil var error: String? = nil
}
enum CodingKeys: String, CodingKey { case serverID, serverName, scannedAt, hostname, os, phpVersions, fpmServices, nginxUpstreams, packageVersions, availableUpdates, packageOrigins, dockerPHP, packageAuditStatus, dockerAuditStatus, findings, rawOutput, error, id }
init(serverID: UUID, serverName: String) { self.serverID = serverID; self.serverName = serverName }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
serverID = try container.decode(UUID.self, forKey: .serverID)
serverName = try container.decode(String.self, forKey: .serverName)
scannedAt = try container.decodeIfPresent(Date.self, forKey: .scannedAt) ?? Date()
hostname = try container.decodeIfPresent(String.self, forKey: .hostname) ?? ""
os = try container.decodeIfPresent(String.self, forKey: .os) ?? ""
phpVersions = try container.decodeIfPresent([String].self, forKey: .phpVersions) ?? []
fpmServices = try container.decodeIfPresent([String].self, forKey: .fpmServices) ?? []
nginxUpstreams = try container.decodeIfPresent([String].self, forKey: .nginxUpstreams) ?? []
packageVersions = try container.decodeIfPresent([String].self, forKey: .packageVersions) ?? []
availableUpdates = try container.decodeIfPresent([String].self, forKey: .availableUpdates) ?? []
packageOrigins = try container.decodeIfPresent([String].self, forKey: .packageOrigins) ?? []
dockerPHP = try container.decodeIfPresent([String].self, forKey: .dockerPHP) ?? []
packageAuditStatus = try container.decodeIfPresent(String.self, forKey: .packageAuditStatus) ?? "Not checked"
dockerAuditStatus = try container.decodeIfPresent(String.self, forKey: .dockerAuditStatus) ?? "Not checked"
findings = try container.decodeIfPresent([Finding].self, forKey: .findings) ?? []
rawOutput = try container.decodeIfPresent(String.self, forKey: .rawOutput) ?? ""
error = try container.decodeIfPresent(String.self, forKey: .error)
id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(serverID, forKey: .serverID)
try container.encode(serverName, forKey: .serverName)
try container.encode(scannedAt, forKey: .scannedAt)
try container.encode(hostname, forKey: .hostname)
try container.encode(os, forKey: .os)
try container.encode(phpVersions, forKey: .phpVersions)
try container.encode(fpmServices, forKey: .fpmServices)
try container.encode(nginxUpstreams, forKey: .nginxUpstreams)
try container.encode(packageVersions, forKey: .packageVersions)
try container.encode(availableUpdates, forKey: .availableUpdates)
try container.encode(packageOrigins, forKey: .packageOrigins)
try container.encode(dockerPHP, forKey: .dockerPHP)
try container.encode(packageAuditStatus, forKey: .packageAuditStatus)
try container.encode(dockerAuditStatus, forKey: .dockerAuditStatus)
try container.encode(findings, forKey: .findings)
try container.encode(rawOutput, forKey: .rawOutput)
try container.encodeIfPresent(error, forKey: .error)
try container.encode(id, forKey: .id)
}
}
struct CVERecord: Codable, Identifiable { struct CVERecord: Codable, Identifiable {
var id: String var id: String
+20 -2
View File
@@ -28,6 +28,18 @@ enum SSHScanner {
else else
printf 'Package manager not supported\n' printf 'Package manager not supported\n'
fi fi
printf '===PACKAGE_ORIGINS===\n'
if command -v apt-cache >/dev/null 2>&1; then
dpkg-query -W -f='${Package}\n' 'php*-fpm' 'php*-cli' 2>/dev/null | sort -u | while IFS= read -r pkg; do
[ -n "$pkg" ] || continue
installed=$(dpkg-query -W -f='${Version}' "$pkg" 2>/dev/null || true)
candidate=$(apt-cache policy "$pkg" 2>/dev/null | sed -n 's/^[[:space:]]*Candidate: //p' | head -1)
printf '%s installed=%s candidate=%s\n' "$pkg" "$installed" "${candidate:-unknown}"
apt-cache policy "$pkg" 2>/dev/null | grep -E 'https?://|/ubuntu |/ubuntu$' | head -3 | sed 's/^[[:space:]]*/ /'
done
else
printf 'APT metadata unavailable\n'
fi
printf '===DOCKER===\n' printf '===DOCKER===\n'
if command -v docker >/dev/null 2>&1; then if command -v docker >/dev/null 2>&1; then
ids=$(docker ps -q 2>/dev/null) ids=$(docker ps -q 2>/dev/null)
@@ -36,7 +48,7 @@ enum SSHScanner {
else else
for id in $ids; do for id in $ids; do
name=$(docker inspect --format '{{.Name}}' "$id" 2>/dev/null | sed 's,^/,,') name=$(docker inspect --format '{{.Name}}' "$id" 2>/dev/null | sed 's,^/,,')
version=$(docker exec "$id" sh -c 'php -r '''echo PHP_VERSION;''' 2>/dev/null' 2>/dev/null) version=$(docker exec "$id" php -v 2>/dev/null | head -1)
if [ -n "$version" ]; then printf '%s %s\n' "$name" "$version"; fi if [ -n "$version" ]; then printf '%s %s\n' "$name" "$version"; fi
done done
fi fi
@@ -103,8 +115,11 @@ enum SSHScanner {
result.nginxUpstreams = fields["NGINX"] ?? [] result.nginxUpstreams = fields["NGINX"] ?? []
result.packageVersions = fields["PACKAGES"] ?? [] result.packageVersions = fields["PACKAGES"] ?? []
result.availableUpdates = fields["UPDATES"] ?? [] result.availableUpdates = fields["UPDATES"] ?? []
result.packageOrigins = fields["PACKAGE_ORIGINS"] ?? []
result.dockerPHP = (fields["DOCKER"] ?? []).filter { !$0.hasPrefix("Docker ") && $0 != "No running containers" } result.dockerPHP = (fields["DOCKER"] ?? []).filter { !$0.hasPrefix("Docker ") && $0 != "No running containers" }
result.packageAuditStatus = result.availableUpdates.contains("Package manager not supported") ? "Unsupported package manager" : "APT upgrade inventory (not a security advisory assessment)" 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"
result.dockerAuditStatus = fields["DOCKER"]?.first(where: { $0.hasPrefix("Docker ") || $0 == "No running containers" }) ?? "Running containers inspected where accessible" result.dockerAuditStatus = fields["DOCKER"]?.first(where: { $0.hasPrefix("Docker ") || $0 == "No running containers" }) ?? "Running containers inspected where accessible"
if result.phpVersions.isEmpty { if result.phpVersions.isEmpty {
result.findings.append(Finding(severity: "Info", title: "No PHP CLI detected", detail: "No PHP binary found in standard paths; this does not exclude bundled runtimes or containers.")) result.findings.append(Finding(severity: "Info", title: "No PHP CLI detected", detail: "No PHP binary found in standard paths; this does not exclude bundled runtimes or containers."))
@@ -119,6 +134,9 @@ enum SSHScanner {
for line in result.availableUpdates where line.hasPrefix("php") && line.contains("/") { for line in result.availableUpdates where line.hasPrefix("php") && line.contains("/") {
result.findings.append(Finding(severity: "Warning", title: "PHP package update available", detail: line + ". Review the package changelog and vendor security notice before classifying CVE exposure.")) result.findings.append(Finding(severity: "Warning", title: "PHP package update available", detail: line + ". Review the package changelog and vendor security notice before classifying CVE exposure."))
} }
if result.packageOrigins.contains("APT metadata unavailable") {
result.findings.append(Finding(severity: "Warning", title: "Package provenance unavailable", detail: "Could not inspect APT package origins; vendor security fixes cannot be verified."))
}
for line in result.dockerPHP { for line in result.dockerPHP {
result.findings.append(Finding(severity: "Info", title: "PHP runtime in Docker", detail: line + ". Container image security status requires a separate image/advisory assessment.")) result.findings.append(Finding(severity: "Info", title: "PHP runtime in Docker", detail: line + ". Container image security status requires a separate image/advisory assessment."))
} }
+120
View File
@@ -0,0 +1,120 @@
import Foundation
enum VulnerabilityService {
enum ServiceError: LocalizedError {
case invalidResponse
case httpError(Int)
var errorDescription: String? {
switch self {
case .invalidResponse:
return "Invalid response from the vulnerability database."
case .httpError(let code):
return "Vulnerability database returned HTTP \(code)."
}
}
}
private struct NVDResponse: Decodable {
let vulnerabilities: [Entry]
}
private struct Entry: Decodable {
let cve: CVE
}
private struct CVE: Decodable {
let id: String
let published: String
let descriptions: [Description]
let metrics: Metrics?
}
private struct Description: Decodable {
let lang: String
let value: String
}
private struct Metrics: Decodable {
let cvssMetricV31: [Metric]?
let cvssMetricV30: [Metric]?
let cvssMetricV2: [Metric]?
}
private struct Metric: Decodable {
let cvssData: CVSSData
}
private struct CVSSData: Decodable {
let baseScore: Double?
let baseSeverity: String?
}
static func discover() async throws -> [CVERecord] {
var components = URLComponents(
string: "https://services.nvd.nist.gov/rest/json/cves/2.0"
)!
components.queryItems = [
URLQueryItem(name: "keywordSearch", value: "PHP"),
URLQueryItem(name: "resultsPerPage", value: "100")
]
guard let url = components.url else {
throw ServiceError.invalidResponse
}
var request = URLRequest(url: url)
request.timeoutInterval = 30
request.setValue(
"application/json",
forHTTPHeaderField: "Accept"
)
request.setValue(
"PHPGuard/1.2",
forHTTPHeaderField: "User-Agent"
)
let (data, response) = try await URLSession.shared.data(
for: request
)
guard let http = response as? HTTPURLResponse else {
throw ServiceError.invalidResponse
}
guard (200...299).contains(http.statusCode) else {
throw ServiceError.httpError(http.statusCode)
}
let decoded = try JSONDecoder().decode(
NVDResponse.self,
from: data
)
return decoded.vulnerabilities.map { entry in
let cve = entry.cve
let description = cve.descriptions.first {
$0.lang == "en"
}?.value ?? "No description available."
let metric =
cve.metrics?.cvssMetricV31?.first ??
cve.metrics?.cvssMetricV30?.first ??
cve.metrics?.cvssMetricV2?.first
return CVERecord(
id: cve.id,
description: description,
published: cve.published,
severity: metric?.cvssData.baseSeverity ?? "UNKNOWN",
score: metric?.cvssData.baseScore
)
}
.sorted { $0.published > $1.published }
}
}
+22
View File
@@ -0,0 +1,22 @@
# PHPGuard 1.3.0 — Release Notes
- Added read-only APT package provenance and candidate version inventory for PHP CLI and PHP-FPM packages.
- Added package-origin visibility to server details and exported JSON reports.
- Corrected the Docker PHP version probe.
- Preserved compatibility with earlier stored scan results.
- Updated build number to 4 and version to 1.3.0.
- Retained the Debug build and signed/notarized DMG release scripts.
## Limitations
APT origin and version information **does not** establish whether a specific CVE is fixed. Ubuntu security backports, package provenance, and vendor advisories still require verification. The CVE Research tab lists keyword-search results, not confirmed server exposures. Scans are read-only and do not remediate systems.
## Build on macOS
`./Scripts/build.sh`
## Release on macOS
`NOTARY_PROFILE=orion-notary ./Scripts/release.sh`
Requires Xcode, a Developer ID Application signing identity, and a configured notarytool keychain profile.
-1
View File
@@ -8,7 +8,6 @@ IDENTITY="${SIGNING_IDENTITY:-$(security find-identity -v -p codesigning | sed -
[[ -n "$IDENTITY" ]] || { echo "Developer ID Application signing identity required. Set SIGNING_IDENTITY." >&2; exit 1; } [[ -n "$IDENTITY" ]] || { echo "Developer ID Application signing identity required. Set SIGNING_IDENTITY." >&2; exit 1; }
PROFILE="${NOTARY_PROFILE:-}" PROFILE="${NOTARY_PROFILE:-}"
[[ -n "$PROFILE" ]] || { echo "Set NOTARY_PROFILE to an existing notarytool keychain profile." >&2; exit 1; } [[ -n "$PROFILE" ]] || { echo "Set NOTARY_PROFILE to an existing notarytool keychain profile." >&2; exit 1; }
VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$ROOT/.build/Build/Products/Release/PHPGuard.app/Contents/Info.plist" 2>/dev/null || true)"
DERIVED="$ROOT/.build" DERIVED="$ROOT/.build"
xcodebuild -project "$ROOT/PHPGuard.xcodeproj" -scheme PHPGuard -configuration Release -derivedDataPath "$DERIVED" CODE_SIGNING_ALLOWED=NO build xcodebuild -project "$ROOT/PHPGuard.xcodeproj" -scheme PHPGuard -configuration Release -derivedDataPath "$DERIVED" CODE_SIGNING_ALLOWED=NO build
APP="$DERIVED/Build/Products/Release/PHPGuard.app" APP="$DERIVED/Build/Products/Release/PHPGuard.app"