From c522b161da28cb1f13c8118c99de0bc4a4e9ffd2 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Thu, 22 Oct 2026 12:49:07 +0100 Subject: [PATCH] Updates to scanner --- PHPGuard.xcodeproj/project.pbxproj | 14 ++ .../project.pbxproj.20261022-124532.bak | 194 ++++++++++++++++++ PHPGuard/ContentView.swift | 1 + PHPGuard/Models.swift | 47 ++++- PHPGuard/SSHScanner.swift | 22 +- PHPGuard/VulnerabilityService.swift | 120 +++++++++++ RELEASE_NOTES.md | 22 ++ Scripts/release.sh | 1 - 8 files changed, 417 insertions(+), 4 deletions(-) create mode 100644 PHPGuard.xcodeproj/project.pbxproj.20261022-124532.bak create mode 100644 PHPGuard/VulnerabilityService.swift create mode 100644 RELEASE_NOTES.md diff --git a/PHPGuard.xcodeproj/project.pbxproj b/PHPGuard.xcodeproj/project.pbxproj index 75508c1..fe55116 100644 --- a/PHPGuard.xcodeproj/project.pbxproj +++ b/PHPGuard.xcodeproj/project.pbxproj @@ -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 = ""; + }; + 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 = ""; }; 000000000000000000000065 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; @@ -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; diff --git a/PHPGuard.xcodeproj/project.pbxproj.20261022-124532.bak b/PHPGuard.xcodeproj/project.pbxproj.20261022-124532.bak new file mode 100644 index 0000000..75508c1 --- /dev/null +++ b/PHPGuard.xcodeproj/project.pbxproj.20261022-124532.bak @@ -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 = ""; }; + 000000000000000000000065 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; + 000000000000000000000066 /* SSHScanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSHScanner.swift; sourceTree = ""; }; + 000000000000000000000067 /* AuditStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuditStore.swift; sourceTree = ""; }; + 000000000000000000000068 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXGroup section */ + 000000000000000000000010 = { + isa = PBXGroup; + children = ( + 000000000000000000000011 /* PHPGuard */, + 000000000000000000000012 /* Products */, + ); + sourceTree = ""; + }; + 000000000000000000000011 /* PHPGuard */ = { + isa = PBXGroup; + children = ( + 000000000000000000000064 /* PHPGuardApp.swift */, + 000000000000000000000065 /* Models.swift */, + 000000000000000000000066 /* SSHScanner.swift */, + 000000000000000000000067 /* AuditStore.swift */, + 000000000000000000000068 /* ContentView.swift */, + ); + path = PHPGuard; + sourceTree = ""; + }; + 000000000000000000000012 /* Products */ = { + isa = PBXGroup; + children = ( + 000000000000000000000030 /* PHPGuard.app */, + ); + name = Products; + sourceTree = ""; + }; +/* 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 */; +} diff --git a/PHPGuard/ContentView.swift b/PHPGuard/ContentView.swift index ec63349..82584f6 100644 --- a/PHPGuard/ContentView.swift +++ b/PHPGuard/ContentView.swift @@ -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) diff --git a/PHPGuard/Models.swift b/PHPGuard/Models.swift index 9220516..205905a 100644 --- a/PHPGuard/Models.swift +++ b/PHPGuard/Models.swift @@ -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 diff --git a/PHPGuard/SSHScanner.swift b/PHPGuard/SSHScanner.swift index 6037d79..d76c435 100644 --- a/PHPGuard/SSHScanner.swift +++ b/PHPGuard/SSHScanner.swift @@ -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.")) } diff --git a/PHPGuard/VulnerabilityService.swift b/PHPGuard/VulnerabilityService.swift new file mode 100644 index 0000000..bbf3dc8 --- /dev/null +++ b/PHPGuard/VulnerabilityService.swift @@ -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 } + } +} diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 0000000..37dc5a2 --- /dev/null +++ b/RELEASE_NOTES.md @@ -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. diff --git a/Scripts/release.sh b/Scripts/release.sh index 59d966a..aafb40a 100755 --- a/Scripts/release.sh +++ b/Scripts/release.sh @@ -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"