Updates to scanner
@@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
B10000000000000000000002 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B10000000000000000000001 /* Assets.xcassets */; };
|
||||||
A10000000000000000000002 /* VulnerabilityService.swift in Sources */ = {
|
A10000000000000000000002 /* VulnerabilityService.swift in Sources */ = {
|
||||||
isa = PBXBuildFile;
|
isa = PBXBuildFile;
|
||||||
fileRef = A10000000000000000000001 /* VulnerabilityService.swift */;
|
fileRef = A10000000000000000000001 /* VulnerabilityService.swift */;
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
B10000000000000000000001 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
A10000000000000000000001 /* VulnerabilityService.swift */ = {
|
A10000000000000000000001 /* VulnerabilityService.swift */ = {
|
||||||
isa = PBXFileReference;
|
isa = PBXFileReference;
|
||||||
lastKnownFileType = sourcecode.swift;
|
lastKnownFileType = sourcecode.swift;
|
||||||
@@ -53,6 +55,7 @@
|
|||||||
000000000000000000000067 /* AuditStore.swift */,
|
000000000000000000000067 /* AuditStore.swift */,
|
||||||
A10000000000000000000001 /* VulnerabilityService.swift */,
|
A10000000000000000000001 /* VulnerabilityService.swift */,
|
||||||
000000000000000000000068 /* ContentView.swift */,
|
000000000000000000000068 /* ContentView.swift */,
|
||||||
|
B10000000000000000000001 /* Assets.xcassets */,
|
||||||
);
|
);
|
||||||
path = PHPGuard;
|
path = PHPGuard;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -73,6 +76,7 @@
|
|||||||
buildConfigurationList = 000000000000000000000042 /* Build configuration list for PBXNativeTarget "PHPGuard" */;
|
buildConfigurationList = 000000000000000000000042 /* Build configuration list for PBXNativeTarget "PHPGuard" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
000000000000000000000021 /* Sources */,
|
000000000000000000000021 /* Sources */,
|
||||||
|
B10000000000000000000003 /* Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -114,6 +118,10 @@
|
|||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
B10000000000000000000003 /* Resources */ = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (B10000000000000000000002 /* Assets.xcassets in Resources */,); runOnlyForDeploymentPostprocessing = 0; };
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
000000000000000000000021 /* Sources */ = {
|
000000000000000000000021 /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
@@ -158,6 +166,10 @@
|
|||||||
DEVELOPMENT_TEAM = 39R6U3437D;
|
DEVELOPMENT_TEAM = 39R6U3437D;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = PHPGuard;
|
||||||
|
MARKETING_VERSION = 1.4.0;
|
||||||
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.PHPGuard;
|
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.PHPGuard;
|
||||||
@@ -173,6 +185,10 @@
|
|||||||
DEVELOPMENT_TEAM = 39R6U3437D;
|
DEVELOPMENT_TEAM = 39R6U3437D;
|
||||||
ENABLE_APP_SANDBOX = NO;
|
ENABLE_APP_SANDBOX = NO;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = PHPGuard;
|
||||||
|
MARKETING_VERSION = 1.4.0;
|
||||||
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
MACOSX_DEPLOYMENT_TARGET = 15.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.PHPGuard;
|
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.PHPGuard;
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "16x16",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "icon_16x16_1x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "16x16",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "icon_16x16_2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "32x32",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "icon_32x32_1x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "32x32",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "icon_32x32_2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "128x128",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "icon_128x128_1x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "128x128",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "icon_128x128_2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "256x256",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "icon_256x256_1x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "256x256",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "icon_256x256_2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "512x512",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "icon_512x512_1x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "512x512",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "icon_512x512_2x.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"author": "xcode",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 707 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"filename": "icon.png",
|
||||||
|
"idiom": "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"author": "xcode",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 14 KiB |
@@ -33,10 +33,13 @@ struct AuditResult: Codable, Identifiable {
|
|||||||
var packageAuditStatus = "Not checked"
|
var packageAuditStatus = "Not checked"
|
||||||
var dockerAuditStatus = "Not checked"
|
var dockerAuditStatus = "Not checked"
|
||||||
var findings: [Finding] = []
|
var findings: [Finding] = []
|
||||||
|
var recentPHPFiles: [String] = []
|
||||||
|
var webErrorSignals: [String] = []
|
||||||
|
var investigationStatus = "Not checked"
|
||||||
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 }
|
enum CodingKeys: String, CodingKey { case serverID, serverName, scannedAt, hostname, os, phpVersions, fpmServices, nginxUpstreams, packageVersions, availableUpdates, packageOrigins, dockerPHP, packageAuditStatus, dockerAuditStatus, recentPHPFiles, webErrorSignals, investigationStatus, findings, rawOutput, error, id }
|
||||||
init(serverID: UUID, serverName: String) { self.serverID = serverID; self.serverName = serverName }
|
init(serverID: UUID, serverName: String) { self.serverID = serverID; self.serverName = serverName }
|
||||||
init(from decoder: Decoder) throws {
|
init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
@@ -55,6 +58,9 @@ struct AuditResult: Codable, Identifiable {
|
|||||||
packageAuditStatus = try container.decodeIfPresent(String.self, forKey: .packageAuditStatus) ?? "Not checked"
|
packageAuditStatus = try container.decodeIfPresent(String.self, forKey: .packageAuditStatus) ?? "Not checked"
|
||||||
dockerAuditStatus = try container.decodeIfPresent(String.self, forKey: .dockerAuditStatus) ?? "Not checked"
|
dockerAuditStatus = try container.decodeIfPresent(String.self, forKey: .dockerAuditStatus) ?? "Not checked"
|
||||||
findings = try container.decodeIfPresent([Finding].self, forKey: .findings) ?? []
|
findings = try container.decodeIfPresent([Finding].self, forKey: .findings) ?? []
|
||||||
|
recentPHPFiles = try container.decodeIfPresent([String].self, forKey: .recentPHPFiles) ?? []
|
||||||
|
webErrorSignals = try container.decodeIfPresent([String].self, forKey: .webErrorSignals) ?? []
|
||||||
|
investigationStatus = try container.decodeIfPresent(String.self, forKey: .investigationStatus) ?? "Not checked"
|
||||||
rawOutput = try container.decodeIfPresent(String.self, forKey: .rawOutput) ?? ""
|
rawOutput = try container.decodeIfPresent(String.self, forKey: .rawOutput) ?? ""
|
||||||
error = try container.decodeIfPresent(String.self, forKey: .error)
|
error = try container.decodeIfPresent(String.self, forKey: .error)
|
||||||
id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
|
id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
|
||||||
@@ -76,6 +82,9 @@ struct AuditResult: Codable, Identifiable {
|
|||||||
try container.encode(packageAuditStatus, forKey: .packageAuditStatus)
|
try container.encode(packageAuditStatus, forKey: .packageAuditStatus)
|
||||||
try container.encode(dockerAuditStatus, forKey: .dockerAuditStatus)
|
try container.encode(dockerAuditStatus, forKey: .dockerAuditStatus)
|
||||||
try container.encode(findings, forKey: .findings)
|
try container.encode(findings, forKey: .findings)
|
||||||
|
try container.encode(recentPHPFiles, forKey: .recentPHPFiles)
|
||||||
|
try container.encode(webErrorSignals, forKey: .webErrorSignals)
|
||||||
|
try container.encode(investigationStatus, forKey: .investigationStatus)
|
||||||
try container.encode(rawOutput, forKey: .rawOutput)
|
try container.encode(rawOutput, forKey: .rawOutput)
|
||||||
try container.encodeIfPresent(error, forKey: .error)
|
try container.encodeIfPresent(error, forKey: .error)
|
||||||
try container.encode(id, forKey: .id)
|
try container.encode(id, forKey: .id)
|
||||||
|
|||||||
@@ -55,6 +55,18 @@ enum SSHScanner {
|
|||||||
else
|
else
|
||||||
printf 'Docker not installed\n'
|
printf 'Docker not installed\n'
|
||||||
fi
|
fi
|
||||||
|
printf '===RECENT_PHP_FILES===\n'
|
||||||
|
if [ -d /var/www ]; then
|
||||||
|
find /var/www -type f -name '*.php' -mtime -3 -printf '%TY-%Tm-%Td %TH:%TM %p\n' 2>/dev/null | head -40
|
||||||
|
else
|
||||||
|
printf 'Web root unavailable\n'
|
||||||
|
fi
|
||||||
|
printf '===WEB_ERROR_SIGNALS===\n'
|
||||||
|
if [ -r /var/log/nginx/error.log ]; then
|
||||||
|
tail -n 1000 /var/log/nginx/error.log 2>/dev/null | grep -Ei 'segfault|upstream prematurely closed|PHP Fatal|out of memory' | tail -30 || true
|
||||||
|
else
|
||||||
|
printf 'Nginx error log unavailable (permissions or absent)\n'
|
||||||
|
fi
|
||||||
printf '===END===\n'
|
printf '===END===\n'
|
||||||
"""#
|
"""#
|
||||||
|
|
||||||
@@ -116,6 +128,10 @@ enum SSHScanner {
|
|||||||
result.packageVersions = fields["PACKAGES"] ?? []
|
result.packageVersions = fields["PACKAGES"] ?? []
|
||||||
result.availableUpdates = fields["UPDATES"] ?? []
|
result.availableUpdates = fields["UPDATES"] ?? []
|
||||||
result.packageOrigins = fields["PACKAGE_ORIGINS"] ?? []
|
result.packageOrigins = fields["PACKAGE_ORIGINS"] ?? []
|
||||||
|
result.recentPHPFiles = (fields["RECENT_PHP_FILES"] ?? []).filter { $0 != "Web root unavailable" }
|
||||||
|
result.webErrorSignals = (fields["WEB_ERROR_SIGNALS"] ?? []).filter { !$0.hasPrefix("Nginx error log unavailable") }
|
||||||
|
result.investigationStatus = (fields["WEB_ERROR_SIGNALS"] ?? []).contains(where: { $0.hasPrefix("Nginx error log unavailable") })
|
||||||
|
? "Partial: Nginx error log inaccessible or absent" : "Read-only indicators collected (not proof of compromise)"
|
||||||
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.packageOrigins.contains("APT metadata unavailable")
|
result.packageAuditStatus = result.packageOrigins.contains("APT metadata unavailable")
|
||||||
? "APT package provenance unavailable"
|
? "APT package provenance unavailable"
|
||||||
@@ -140,6 +156,15 @@ enum SSHScanner {
|
|||||||
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."))
|
||||||
}
|
}
|
||||||
|
if !result.webErrorSignals.isEmpty {
|
||||||
|
result.findings.append(Finding(severity: "Warning", title: "Web server error signals", detail: "\(result.webErrorSignals.count) potentially relevant log entries. Review context; errors alone do not establish an attack."))
|
||||||
|
}
|
||||||
|
if !result.recentPHPFiles.isEmpty {
|
||||||
|
result.findings.append(Finding(severity: "Info", title: "Recently modified PHP files", detail: "\(result.recentPHPFiles.count) PHP files changed in the last 3 days (up to 40 shown). Compare with expected deployments."))
|
||||||
|
}
|
||||||
|
if result.investigationStatus.hasPrefix("Partial") {
|
||||||
|
result.findings.append(Finding(severity: "Info", title: "Investigation coverage limited", detail: result.investigationStatus))
|
||||||
|
}
|
||||||
result.findings.append(Finding(severity: "Info", title: "CVE status unverified", detail: "Version strings and available upgrades do not establish CVE exposure. Ubuntu backports, package origins, and current vendor advisories require independent verification. No server is labelled secure based on this scan."))
|
result.findings.append(Finding(severity: "Info", title: "CVE status unverified", detail: "Version strings and available upgrades do not establish CVE exposure. Ubuntu backports, package origins, and current vendor advisories require independent verification. No server is labelled secure based on this scan."))
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ enum VulnerabilityService {
|
|||||||
forHTTPHeaderField: "Accept"
|
forHTTPHeaderField: "Accept"
|
||||||
)
|
)
|
||||||
request.setValue(
|
request.setValue(
|
||||||
"PHPGuard/1.2",
|
"PHPGuard/1.4",
|
||||||
forHTTPHeaderField: "User-Agent"
|
forHTTPHeaderField: "User-Agent"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -29,3 +29,10 @@ SSH requires existing known_hosts entries and key-based authentication. Scans ar
|
|||||||
|
|
||||||
## Known limitations
|
## Known limitations
|
||||||
Not built/tested with Xcode in this environment. CVE discovery is broad keyword search and may include unrelated products or omit relevant vulnerabilities. NVD may rate-limit requests. Automatic backport assessment is a future phase.
|
Not built/tested with Xcode in this environment. CVE discovery is broad keyword search and may include unrelated products or omit relevant vulnerabilities. NVD may rate-limit requests. Automatic backport assessment is a future phase.
|
||||||
|
|
||||||
|
|
||||||
|
## Version 1.4 security investigation
|
||||||
|
The SSH audit collects at most 40 recently modified PHP paths from `/var/www` and up to 30 matching Nginx error lines when accessible. It never modifies remote files. Access may be limited by permissions. The scan is not a compromise determination.
|
||||||
|
|
||||||
|
## Branding
|
||||||
|
The app icon is compiled from `PHPGuard/Assets.xcassets/AppIcon.appiconset`. The DMG includes a branded image at `.background/PHPGuard-Installer.png`; this is an included asset, not a custom Finder DMG window background layout.
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
# PHPGuard 1.3.0 — Release Notes
|
# PHPGuard 1.4.0 (Build 5)
|
||||||
|
|
||||||
- Added read-only APT package provenance and candidate version inventory for PHP CLI and PHP-FPM packages.
|
- Added a native macOS application icon and branded installer graphic asset.
|
||||||
- Added package-origin visibility to server details and exported JSON reports.
|
- Added read-only security investigation: recently modified PHP files and selected Nginx error signals.
|
||||||
- Corrected the Docker PHP version probe.
|
- Added investigation coverage reporting and non-conclusive findings.
|
||||||
- Preserved compatibility with earlier stored scan results.
|
- Preserved existing server inventory, Docker inspection, CVE research and scan history.
|
||||||
- Updated build number to 4 and version to 1.3.0.
|
- Security signals are not proof of compromise; vendor CVE fix matching remains pending.
|
||||||
- Retained the Debug build and signed/notarized DMG release scripts.
|
|
||||||
|
|
||||||
## Limitations
|
Build: `./Scripts/build.sh`
|
||||||
|
Release: `NOTARY_PROFILE=orion-notary ./Scripts/release.sh`
|
||||||
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.
|
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ codesign --verify --strict --verbose=2 "$STAGING/PHPGuard.app"
|
|||||||
DMG="$ROOT/dist/PHPGuard-$VERSION.dmg"
|
DMG="$ROOT/dist/PHPGuard-$VERSION.dmg"
|
||||||
rm -f "$DMG"
|
rm -f "$DMG"
|
||||||
ln -s /Applications "$STAGING/Applications"
|
ln -s /Applications "$STAGING/Applications"
|
||||||
|
# Include a branded installer graphic in the disk image.
|
||||||
|
INSTALLER_ICON="$ROOT/PHPGuard/Assets.xcassets/InstallerIcon.imageset/icon.png"
|
||||||
|
if [[ -f "$INSTALLER_ICON" ]]; then
|
||||||
|
mkdir -p "$STAGING/.background"
|
||||||
|
cp "$INSTALLER_ICON" "$STAGING/.background/PHPGuard-Installer.png"
|
||||||
|
fi
|
||||||
hdiutil create -volname "PHPGuard $VERSION" -srcfolder "$STAGING" -ov -format UDZO "$DMG"
|
hdiutil create -volname "PHPGuard $VERSION" -srcfolder "$STAGING" -ov -format UDZO "$DMG"
|
||||||
xcrun notarytool submit "$DMG" --keychain-profile "$PROFILE" --wait
|
xcrun notarytool submit "$DMG" --keychain-profile "$PROFILE" --wait
|
||||||
xcrun stapler staple "$DMG"
|
xcrun stapler staple "$DMG"
|
||||||
|
|||||||