Updates to scanner

This commit is contained in:
Mark Gregory
2026-10-22 12:54:32 +01:00
parent c522b161da
commit 8a8061b1e8
21 changed files with 153 additions and 22 deletions
+16
View File
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
B10000000000000000000002 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B10000000000000000000001 /* Assets.xcassets */; };
A10000000000000000000002 /* VulnerabilityService.swift in Sources */ = {
isa = PBXBuildFile;
fileRef = A10000000000000000000001 /* VulnerabilityService.swift */;
@@ -20,6 +21,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
B10000000000000000000001 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A10000000000000000000001 /* VulnerabilityService.swift */ = {
isa = PBXFileReference;
lastKnownFileType = sourcecode.swift;
@@ -53,6 +55,7 @@
000000000000000000000067 /* AuditStore.swift */,
A10000000000000000000001 /* VulnerabilityService.swift */,
000000000000000000000068 /* ContentView.swift */,
B10000000000000000000001 /* Assets.xcassets */,
);
path = PHPGuard;
sourceTree = "<group>";
@@ -73,6 +76,7 @@
buildConfigurationList = 000000000000000000000042 /* Build configuration list for PBXNativeTarget "PHPGuard" */;
buildPhases = (
000000000000000000000021 /* Sources */,
B10000000000000000000003 /* Resources */,
);
buildRules = (
);
@@ -114,6 +118,10 @@
};
/* 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 */
000000000000000000000021 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -158,6 +166,10 @@
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
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";
MACOSX_DEPLOYMENT_TARGET = 15.0;
PRODUCT_BUNDLE_IDENTIFIER = net.absdev.PHPGuard;
@@ -173,6 +185,10 @@
DEVELOPMENT_TEAM = 39R6U3437D;
ENABLE_APP_SANDBOX = NO;
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";
MACOSX_DEPLOYMENT_TARGET = 15.0;
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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,12 @@
{
"images": [
{
"filename": "icon.png",
"idiom": "universal"
}
],
"info": {
"author": "xcode",
"version": 1
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+10 -1
View File
@@ -33,10 +33,13 @@ struct AuditResult: Codable, Identifiable {
var packageAuditStatus = "Not checked"
var dockerAuditStatus = "Not checked"
var findings: [Finding] = []
var recentPHPFiles: [String] = []
var webErrorSignals: [String] = []
var investigationStatus = "Not checked"
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 }
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(from decoder: Decoder) throws {
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"
dockerAuditStatus = try container.decodeIfPresent(String.self, forKey: .dockerAuditStatus) ?? "Not checked"
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) ?? ""
error = try container.decodeIfPresent(String.self, forKey: .error)
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(dockerAuditStatus, forKey: .dockerAuditStatus)
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.encodeIfPresent(error, forKey: .error)
try container.encode(id, forKey: .id)
+25
View File
@@ -55,6 +55,18 @@ enum SSHScanner {
else
printf 'Docker not installed\n'
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'
"""#
@@ -116,6 +128,10 @@ enum SSHScanner {
result.packageVersions = fields["PACKAGES"] ?? []
result.availableUpdates = fields["UPDATES"] ?? []
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.packageAuditStatus = result.packageOrigins.contains("APT metadata unavailable")
? "APT package provenance unavailable"
@@ -140,6 +156,15 @@ enum SSHScanner {
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."))
}
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."))
return result
}
+1 -1
View File
@@ -73,7 +73,7 @@ enum VulnerabilityService {
forHTTPHeaderField: "Accept"
)
request.setValue(
"PHPGuard/1.2",
"PHPGuard/1.4",
forHTTPHeaderField: "User-Agent"
)
+7
View File
@@ -29,3 +29,10 @@ SSH requires existing known_hosts entries and key-based authentication. Scans ar
## 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.
## 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.
+8 -20
View File
@@ -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 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.
- Added a native macOS application icon and branded installer graphic asset.
- Added read-only security investigation: recently modified PHP files and selected Nginx error signals.
- Added investigation coverage reporting and non-conclusive findings.
- Preserved existing server inventory, Docker inspection, CVE research and scan history.
- Security signals are not proof of compromise; vendor CVE fix matching remains pending.
## 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.
Build: `./Scripts/build.sh`
Release: `NOTARY_PROFILE=orion-notary ./Scripts/release.sh`
+6
View File
@@ -23,6 +23,12 @@ codesign --verify --strict --verbose=2 "$STAGING/PHPGuard.app"
DMG="$ROOT/dist/PHPGuard-$VERSION.dmg"
rm -f "$DMG"
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"
xcrun notarytool submit "$DMG" --keychain-profile "$PROFILE" --wait
xcrun stapler staple "$DMG"