Updates to scanner
This commit is contained in:
@@ -7,6 +7,11 @@
|
||||
objects = {
|
||||
|
||||
/* 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 */; };
|
||||
0000000000000000000000C9 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000065 /* Models.swift */; };
|
||||
0000000000000000000000CA /* SSHScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 000000000000000000000066 /* SSHScanner.swift */; };
|
||||
@@ -15,6 +20,13 @@
|
||||
/* End PBXBuildFile 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; };
|
||||
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>"; };
|
||||
@@ -39,6 +51,7 @@
|
||||
000000000000000000000065 /* Models.swift */,
|
||||
000000000000000000000066 /* SSHScanner.swift */,
|
||||
000000000000000000000067 /* AuditStore.swift */,
|
||||
A10000000000000000000001 /* VulnerabilityService.swift */,
|
||||
000000000000000000000068 /* ContentView.swift */,
|
||||
);
|
||||
path = PHPGuard;
|
||||
@@ -110,6 +123,7 @@
|
||||
0000000000000000000000C9 /* Models.swift in Sources */,
|
||||
0000000000000000000000CA /* SSHScanner.swift in Sources */,
|
||||
0000000000000000000000CB /* AuditStore.swift in Sources */,
|
||||
A10000000000000000000002 /* VulnerabilityService.swift in Sources */,
|
||||
0000000000000000000000CC /* ContentView.swift in Sources */,
|
||||
);
|
||||
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 */;
|
||||
}
|
||||
@@ -104,6 +104,7 @@ struct ContentView: View {
|
||||
dataSection("Nginx FastCGI routes", result.nginxUpstreams)
|
||||
dataSection("Installed packages", result.packageVersions)
|
||||
dataSection("Available PHP package upgrades", result.availableUpdates)
|
||||
dataSection("APT package origins and candidate revisions", result.packageOrigins)
|
||||
Text("Package assessment: \(result.packageAuditStatus)").foregroundStyle(.secondary)
|
||||
dataSection("PHP detected in running Docker containers", result.dockerPHP)
|
||||
Text("Docker assessment: \(result.dockerAuditStatus)").foregroundStyle(.secondary)
|
||||
|
||||
+46
-1
@@ -28,14 +28,59 @@ struct AuditResult: Codable, Identifiable {
|
||||
var nginxUpstreams: [String] = []
|
||||
var packageVersions: [String] = []
|
||||
var availableUpdates: [String] = []
|
||||
var packageOrigins: [String] = []
|
||||
var dockerPHP: [String] = []
|
||||
var packageAuditStatus = "Not checked"
|
||||
var dockerAuditStatus = "Not checked"
|
||||
var findings: [Finding] = []
|
||||
var rawOutput = ""
|
||||
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 {
|
||||
var id: String
|
||||
|
||||
@@ -28,6 +28,18 @@ enum SSHScanner {
|
||||
else
|
||||
printf 'Package manager not supported\n'
|
||||
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'
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
ids=$(docker ps -q 2>/dev/null)
|
||||
@@ -36,7 +48,7 @@ enum SSHScanner {
|
||||
else
|
||||
for id in $ids; do
|
||||
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
|
||||
done
|
||||
fi
|
||||
@@ -103,8 +115,11 @@ enum SSHScanner {
|
||||
result.nginxUpstreams = fields["NGINX"] ?? []
|
||||
result.packageVersions = fields["PACKAGES"] ?? []
|
||||
result.availableUpdates = fields["UPDATES"] ?? []
|
||||
result.packageOrigins = fields["PACKAGE_ORIGINS"] ?? []
|
||||
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"
|
||||
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."))
|
||||
@@ -119,6 +134,9 @@ enum SSHScanner {
|
||||
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."))
|
||||
}
|
||||
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 {
|
||||
result.findings.append(Finding(severity: "Info", title: "PHP runtime in Docker", detail: line + ". Container image security status requires a separate image/advisory assessment."))
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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; }
|
||||
PROFILE="${NOTARY_PROFILE:-}"
|
||||
[[ -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"
|
||||
xcodebuild -project "$ROOT/PHPGuard.xcodeproj" -scheme PHPGuard -configuration Release -derivedDataPath "$DERIVED" CODE_SIGNING_ALLOWED=NO build
|
||||
APP="$DERIVED/Build/Products/Release/PHPGuard.app"
|
||||
|
||||
Reference in New Issue
Block a user