Updates to scanner
@@ -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
|
||||
}
|
||||
}
|
||||
|
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 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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ enum VulnerabilityService {
|
||||
forHTTPHeaderField: "Accept"
|
||||
)
|
||||
request.setValue(
|
||||
"PHPGuard/1.2",
|
||||
"PHPGuard/1.4",
|
||||
forHTTPHeaderField: "User-Agent"
|
||||
)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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"
|
||||
|
||||