up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
using StellaOps.Scanner.Analyzers.OS.MacOsBundle;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.OS.MacOsBundle.Tests;
|
||||
|
||||
public sealed class EntitlementsParserTests
|
||||
{
|
||||
private static readonly string FixturesRoot = Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
"Fixtures");
|
||||
|
||||
private readonly EntitlementsParser _parser = new();
|
||||
|
||||
[Fact]
|
||||
public void Parse_ValidEntitlements_ReturnsEntitlements()
|
||||
{
|
||||
// Arrange
|
||||
var entPath = Path.Combine(FixturesRoot, "Applications", "SandboxedApp.app", "Contents", "_CodeSignature", "test.xcent");
|
||||
|
||||
// Act
|
||||
var result = _parser.Parse(entPath);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsSandboxed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_DetectsHighRiskEntitlements()
|
||||
{
|
||||
// Arrange
|
||||
var entPath = Path.Combine(FixturesRoot, "Applications", "SandboxedApp.app", "Contents", "_CodeSignature", "test.xcent");
|
||||
|
||||
// Act
|
||||
var result = _parser.Parse(entPath);
|
||||
|
||||
// Assert
|
||||
Assert.NotEmpty(result.HighRiskEntitlements);
|
||||
Assert.Contains("com.apple.security.device.camera", result.HighRiskEntitlements);
|
||||
Assert.Contains("com.apple.security.device.microphone", result.HighRiskEntitlements);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_CategorizeEntitlements()
|
||||
{
|
||||
// Arrange
|
||||
var entPath = Path.Combine(FixturesRoot, "Applications", "SandboxedApp.app", "Contents", "_CodeSignature", "test.xcent");
|
||||
|
||||
// Act
|
||||
var result = _parser.Parse(entPath);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("network", result.Categories);
|
||||
Assert.Contains("camera", result.Categories);
|
||||
Assert.Contains("microphone", result.Categories);
|
||||
Assert.Contains("filesystem", result.Categories);
|
||||
Assert.Contains("sandbox", result.Categories);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_NonExistentFile_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var entPath = Path.Combine(FixturesRoot, "nonexistent.xcent");
|
||||
|
||||
// Act
|
||||
var result = _parser.Parse(entPath);
|
||||
|
||||
// Assert
|
||||
Assert.Same(BundleEntitlements.Empty, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindEntitlementsFile_FindsXcentFile()
|
||||
{
|
||||
// Arrange
|
||||
var bundlePath = Path.Combine(FixturesRoot, "Applications", "SandboxedApp.app");
|
||||
|
||||
// Act
|
||||
var result = _parser.FindEntitlementsFile(bundlePath);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.EndsWith(".xcent", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindEntitlementsFile_NoBundlePath_ReturnsNull()
|
||||
{
|
||||
// Act
|
||||
var result = _parser.FindEntitlementsFile("");
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindEntitlementsFile_NoEntitlements_ReturnsNull()
|
||||
{
|
||||
// Arrange - bundle without entitlements
|
||||
var bundlePath = Path.Combine(FixturesRoot, "Applications", "TestApp.app");
|
||||
|
||||
// Act
|
||||
var result = _parser.FindEntitlementsFile(bundlePath);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasEntitlement_ReturnsTrueForExistingEntitlement()
|
||||
{
|
||||
// Arrange
|
||||
var entPath = Path.Combine(FixturesRoot, "Applications", "SandboxedApp.app", "Contents", "_CodeSignature", "test.xcent");
|
||||
var result = _parser.Parse(entPath);
|
||||
|
||||
// Act & Assert
|
||||
Assert.True(result.HasEntitlement("com.apple.security.app-sandbox"));
|
||||
Assert.True(result.HasEntitlement("com.apple.security.device.camera"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasEntitlement_ReturnsFalseForMissingEntitlement()
|
||||
{
|
||||
// Arrange
|
||||
var entPath = Path.Combine(FixturesRoot, "Applications", "SandboxedApp.app", "Contents", "_CodeSignature", "test.xcent");
|
||||
var result = _parser.Parse(entPath);
|
||||
|
||||
// Act & Assert
|
||||
Assert.False(result.HasEntitlement("com.apple.security.nonexistent"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.stellaops.sandboxed</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>SandboxedApp</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>100</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>13.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SandboxedApp</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
# Placeholder executable
|
||||
echo "SandboxedApp"
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>files</key>
|
||||
<dict>
|
||||
<key>Contents/Info.plist</key>
|
||||
<data>aGFzaA==</data>
|
||||
</dict>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>^.*</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.microphone</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.stellaops.testapp</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>TestApp</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Test Application</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>123</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.3</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>TestApp</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
# Placeholder executable
|
||||
echo "TestApp"
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>files</key>
|
||||
<dict>
|
||||
<key>Contents/Info.plist</key>
|
||||
<data>aGFzaA==</data>
|
||||
</dict>
|
||||
<key>rules</key>
|
||||
<dict>
|
||||
<key>^.*</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,115 @@
|
||||
using StellaOps.Scanner.Analyzers.OS.MacOsBundle;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.OS.MacOsBundle.Tests;
|
||||
|
||||
public sealed class InfoPlistParserTests
|
||||
{
|
||||
private static readonly string FixturesRoot = Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
"Fixtures");
|
||||
|
||||
private readonly InfoPlistParser _parser = new();
|
||||
|
||||
[Fact]
|
||||
public void Parse_ValidInfoPlist_ReturnsBundleInfo()
|
||||
{
|
||||
// Arrange
|
||||
var plistPath = Path.Combine(FixturesRoot, "Applications", "TestApp.app", "Contents", "Info.plist");
|
||||
|
||||
// Act
|
||||
var result = _parser.Parse(plistPath);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("com.stellaops.testapp", result.BundleIdentifier);
|
||||
Assert.Equal("TestApp", result.BundleName);
|
||||
Assert.Equal("Test Application", result.BundleDisplayName);
|
||||
Assert.Equal("123", result.Version);
|
||||
Assert.Equal("1.2.3", result.ShortVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ExtractsMinimumSystemVersion()
|
||||
{
|
||||
// Arrange
|
||||
var plistPath = Path.Combine(FixturesRoot, "Applications", "TestApp.app", "Contents", "Info.plist");
|
||||
|
||||
// Act
|
||||
var result = _parser.Parse(plistPath);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("12.0", result.MinimumSystemVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ExtractsExecutable()
|
||||
{
|
||||
// Arrange
|
||||
var plistPath = Path.Combine(FixturesRoot, "Applications", "TestApp.app", "Contents", "Info.plist");
|
||||
|
||||
// Act
|
||||
var result = _parser.Parse(plistPath);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("TestApp", result.Executable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ExtractsSupportedPlatforms()
|
||||
{
|
||||
// Arrange
|
||||
var plistPath = Path.Combine(FixturesRoot, "Applications", "TestApp.app", "Contents", "Info.plist");
|
||||
|
||||
// Act
|
||||
var result = _parser.Parse(plistPath);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result.SupportedPlatforms);
|
||||
Assert.Contains("MacOSX", result.SupportedPlatforms);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_NonExistentFile_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var plistPath = Path.Combine(FixturesRoot, "nonexistent.plist");
|
||||
|
||||
// Act
|
||||
var result = _parser.Parse(plistPath);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_MissingBundleIdentifier_ReturnsNull()
|
||||
{
|
||||
// Arrange - Create a temp file without CFBundleIdentifier
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.plist");
|
||||
File.WriteAllText(tempPath, @"<?xml version=""1.0"" encoding=""UTF-8""?>
|
||||
<!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd"">
|
||||
<plist version=""1.0"">
|
||||
<dict>
|
||||
<key>CFBundleName</key>
|
||||
<string>TestApp</string>
|
||||
</dict>
|
||||
</plist>");
|
||||
|
||||
try
|
||||
{
|
||||
// Act
|
||||
var result = _parser.Parse(tempPath);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scanner.Analyzers.OS.MacOsBundle;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.OS.MacOsBundle.Tests;
|
||||
|
||||
public sealed class MacOsBundleAnalyzerTests
|
||||
{
|
||||
private static readonly string FixturesRoot = Path.Combine(
|
||||
AppContext.BaseDirectory,
|
||||
"Fixtures");
|
||||
|
||||
private readonly MacOsBundleAnalyzer _analyzer;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MacOsBundleAnalyzerTests()
|
||||
{
|
||||
_logger = NullLoggerFactory.Instance.CreateLogger<MacOsBundleAnalyzer>();
|
||||
_analyzer = new MacOsBundleAnalyzer((ILogger<MacOsBundleAnalyzer>)_logger);
|
||||
}
|
||||
|
||||
private OSPackageAnalyzerContext CreateContext(string rootPath)
|
||||
{
|
||||
return new OSPackageAnalyzerContext(
|
||||
rootPath,
|
||||
workspacePath: null,
|
||||
TimeProvider.System,
|
||||
_logger);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzerId_ReturnsMacosBundleIdentifier()
|
||||
{
|
||||
Assert.Equal("macos-bundle", _analyzer.AnalyzerId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_WithValidBundles_ReturnsPackages()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("macos-bundle", result.AnalyzerId);
|
||||
Assert.True(result.Packages.Count > 0, "Expected at least one bundle");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_FindsTestApp()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var testApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.testapp");
|
||||
Assert.NotNull(testApp);
|
||||
Assert.Equal("1.2.3", testApp.Version);
|
||||
Assert.Equal("Test Application", testApp.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVersionCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var testApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.testapp");
|
||||
Assert.NotNull(testApp);
|
||||
// ShortVersion takes precedence
|
||||
Assert.Equal("1.2.3", testApp.Version);
|
||||
// Build number goes to release
|
||||
Assert.Equal("123", testApp.Release);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_BuildsCorrectPurl()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var testApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.testapp");
|
||||
Assert.NotNull(testApp);
|
||||
Assert.Contains("pkg:generic/macos-app/com.stellaops.testapp@1.2.3", testApp.PackageUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVendorFromBundleId()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var testApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.testapp");
|
||||
Assert.NotNull(testApp);
|
||||
Assert.Equal("stellaops", testApp.SourcePackage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_SetsEvidenceSourceToMacOsBundle()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
foreach (var package in result.Packages)
|
||||
{
|
||||
Assert.Equal(PackageEvidenceSource.MacOsBundle, package.EvidenceSource);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ExtractsVendorMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var testApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.testapp");
|
||||
Assert.NotNull(testApp);
|
||||
Assert.Equal("com.stellaops.testapp", testApp.VendorMetadata["macos:bundle_id"]);
|
||||
Assert.Equal("APPL", testApp.VendorMetadata["macos:bundle_type"]);
|
||||
Assert.Equal("12.0", testApp.VendorMetadata["macos:min_os_version"]);
|
||||
Assert.Equal("TestApp", testApp.VendorMetadata["macos:executable"]);
|
||||
Assert.Equal("MacOSX", testApp.VendorMetadata["macos:platforms"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_IncludesCodeResourcesHash()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var testApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.testapp");
|
||||
Assert.NotNull(testApp);
|
||||
Assert.True(testApp.VendorMetadata.ContainsKey("macos:code_resources_hash"));
|
||||
var hash = testApp.VendorMetadata["macos:code_resources_hash"];
|
||||
Assert.StartsWith("sha256:", hash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_DetectsSandboxedApp()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var sandboxedApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.sandboxed");
|
||||
Assert.NotNull(sandboxedApp);
|
||||
Assert.Equal("true", sandboxedApp.VendorMetadata["macos:sandboxed"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_DetectsHighRiskEntitlements()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var sandboxedApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.sandboxed");
|
||||
Assert.NotNull(sandboxedApp);
|
||||
Assert.True(sandboxedApp.VendorMetadata.ContainsKey("macos:high_risk_entitlements"));
|
||||
var highRisk = sandboxedApp.VendorMetadata["macos:high_risk_entitlements"];
|
||||
// Full entitlement keys are stored
|
||||
Assert.Contains("com.apple.security.device.camera", highRisk);
|
||||
Assert.Contains("com.apple.security.device.microphone", highRisk);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_DetectsCapabilityCategories()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var sandboxedApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.sandboxed");
|
||||
Assert.NotNull(sandboxedApp);
|
||||
Assert.True(sandboxedApp.VendorMetadata.ContainsKey("macos:capability_categories"));
|
||||
var categories = sandboxedApp.VendorMetadata["macos:capability_categories"];
|
||||
Assert.Contains("network", categories);
|
||||
Assert.Contains("camera", categories);
|
||||
Assert.Contains("microphone", categories);
|
||||
Assert.Contains("filesystem", categories);
|
||||
Assert.Contains("sandbox", categories);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_IncludesFileEvidence()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
var testApp = result.Packages.FirstOrDefault(p =>
|
||||
p.VendorMetadata.TryGetValue("macos:bundle_id", out var id) &&
|
||||
id == "com.stellaops.testapp");
|
||||
Assert.NotNull(testApp);
|
||||
Assert.True(testApp.Files.Count > 0);
|
||||
|
||||
var executable = testApp.Files.FirstOrDefault(f => f.Path.Contains("MacOS/TestApp"));
|
||||
Assert.NotNull(executable);
|
||||
Assert.False(executable.IsConfigFile);
|
||||
|
||||
var infoPlist = testApp.Files.FirstOrDefault(f => f.Path.Contains("Info.plist"));
|
||||
Assert.NotNull(infoPlist);
|
||||
Assert.True(infoPlist.IsConfigFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_ResultsAreDeterministicallySorted()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result1 = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
var result2 = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result1.Packages.Count, result2.Packages.Count);
|
||||
for (int i = 0; i < result1.Packages.Count; i++)
|
||||
{
|
||||
Assert.Equal(result1.Packages[i].PackageUrl, result2.Packages[i].PackageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_NoApplicationsDirectory_ReturnsEmptyPackages()
|
||||
{
|
||||
// Arrange - use temp directory without Applications
|
||||
var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||
Directory.CreateDirectory(tempPath);
|
||||
|
||||
try
|
||||
{
|
||||
var context = CreateContext(tempPath);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result.Packages);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempPath, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeAsync_PopulatesTelemetry()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext(FixturesRoot);
|
||||
|
||||
// Act
|
||||
var result = await _analyzer.AnalyzeAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result.Telemetry);
|
||||
Assert.True(result.Telemetry.PackageCount > 0);
|
||||
Assert.True(result.Telemetry.Duration > TimeSpan.Zero);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS/StellaOps.Scanner.Analyzers.OS.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Scanner.Analyzers.OS.MacOsBundle/StellaOps.Scanner.Analyzers.OS.MacOsBundle.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures\**\*" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user