Description
A source-built .NET macOS runtime pack can contain a native runtime asset with dependencies outside the runtime pack. dotnet publish --self-contained copies the runtime asset but does not copy or validate that external native dependency closure, so the resulting application is not actually self-contained.
This occurs with Homebrew's Apple-silicon dotnet 10.0.302 bottle. Its Microsoft.NETCore.App.Runtime.osx-arm64 copy of libSystem.IO.Compression.Native.dylib links to Homebrew's separately installed Brotli libraries. A Developer ID-signed application can pass strict code-signature verification, notarization, stapling, and Gatekeeper assessment, but then abort in dyld before managed startup because those libraries are outside the application and have a different signing Team ID.
The corresponding runtime asset in Microsoft's official 10.0.302 SDK does not have external Brotli dependencies.
Homebrew discussion: https://github.com/orgs/Homebrew/discussions/6991
The Homebrew maintainer's position is that the formula builds .NET using the supplied build/install instructions and that dependency closure introduced by that build should be handled by .NET.
Reproduction Steps
The affected runtime-pack asset can be inspected without installing the formula:
brew update
brew fetch --formula dotnet
bottle="$(brew --cache --formula dotnet)"
work="$(mktemp -d)"
tar -xzf "$bottle" -C "$work"
runtime_lib="$(find "$work" -path '*Microsoft.NETCore.App.Runtime.osx-arm64*' -name libSystem.IO.Compression.Native.dylib -print -quit)"
otool -L "$runtime_lib"
For the ARM64 Tahoe bottle of dotnet 10.0.302 (SHA-256 a420ba0e0a6f87c5b62327a4f40c6d2130bf589af985ec6ed02f7c00d9067cec), the dependencies include:
@@HOMEBREW_PREFIX@@/opt/dotnet/libexec/shared/Microsoft.NETCore.App/10.0.10/libSystem.IO.Compression.Native.dylib
/usr/lib/libz.1.dylib
@@HOMEBREW_PREFIX@@/opt/brotli/lib/libbrotlidec.1.dylib
@@HOMEBREW_PREFIX@@/opt/brotli/lib/libbrotlienc.1.dylib
/usr/lib/libSystem.B.dylib
After installation, the placeholders resolve under /opt/homebrew.
Publish a net10.0/net10.0-macos application for osx-arm64 with --self-contained true. The published libSystem.IO.Compression.Native.dylib retains those absolute paths. In an application bundle it may appear under Contents/MonoBundle.
Sign the application with Developer ID and hardened runtime, then launch it. The observed failure is:
Termination Reason: Namespace DYLD, Code 1, Library missing
Library not loaded: /opt/homebrew/opt/brotli/lib/libbrotlidec.1.dylib
Referenced from: MyApp.app/Contents/MonoBundle/libSystem.IO.Compression.Native.dylib
Reason: mapping process and mapped file (non-platform) have different Team IDs
The same failure mode also makes the output non-portable to a clean Mac where /opt/homebrew/opt/brotli is absent.
For comparison, the corresponding native library from Microsoft's official .NET 10.0.302 SDK reports only:
@rpath/libSystem.IO.Compression.Native.dylib
/usr/lib/libSystem.B.dylib
Rebuilding the same application with that SDK produced no non-system absolute dependencies and launched successfully.
Expected behavior
A runtime pack used by --self-contained publication should be closed over its native runtime dependencies and remain relocatable.
Possible ownership models include:
- source-build/runtime-pack construction packages the external native dependency closure in a relocatable form;
- the build/install instructions prevent runtime-pack assets from acquiring dependencies outside the pack; or
- publication recursively discovers, copies, and validates the native dependency closure.
The exact layer is a design decision, but a --self-contained output should not retain absolute dependencies on the build machine's package-manager prefix.
Actual behavior
dotnet publish --self-contained copies libSystem.IO.Compression.Native.dylib from the source-built runtime pack without copying its Brotli closure or rejecting its absolute load commands.
The output can therefore:
- fail on a clean target where Homebrew Brotli is absent; or
- fail hardened-runtime library validation when Homebrew Brotli is installed but signed by a different Team ID.
Standard signing and notarization checks did not detect the dependency because the referenced Brotli libraries remained outside the application bundle.
Regression?
Unknown. The issue is present in the Homebrew-built .NET 10.0.302 / Microsoft.NETCore.App 10.0.10 ARM64 runtime pack. Microsoft's official SDK of the same version does not reproduce it.
Known Workarounds
Using Microsoft's official SDK produces a relocatable runtime asset.
We also verified a narrow post-publish repair:
- Detect the exact Brotli links in
libSystem.IO.Compression.Native.dylib.
- Copy
libbrotlidec, libbrotlienc, and their transitive libbrotlicommon dependency into Contents/Frameworks.
- Rewrite load commands and dylib IDs to bundle-relative
@loader_path/@rpath references.
- Preserve effective application entitlements (including
com.apple.security.cs.allow-jit) and sign the copied/modified libraries and enclosing application with the same Developer ID identity.
- Include Brotli's license notice.
After this repair, all 14 Mach-O files in the tested bundle had only system or bundle-relative dependencies, strict signature verification passed, and both the application and the application extracted from a newly signed installer launched successfully. Brotli was the only external dependency family found in that bundle.
Configuration
Homebrew 6.0.15
dotnet formula 10.0.302
Microsoft.NETCore.App runtime 10.0.10
macOS 26.6 arm64
Xcode 26.6
Apple silicon; Rosetta not installed
Other information
This report intentionally does not assume whether dotnet/runtime, source-build, or the SDK publisher should implement the final correction. Please transfer or redirect it if another .NET repository owns the dependency-closure contract.
Disclosure: I used OpenAI Codex to help inspect the binaries and draft this report. I verified the commands and output and will answer maintainer questions myself.
Description
A source-built .NET macOS runtime pack can contain a native runtime asset with dependencies outside the runtime pack.
dotnet publish --self-containedcopies the runtime asset but does not copy or validate that external native dependency closure, so the resulting application is not actually self-contained.This occurs with Homebrew's Apple-silicon
dotnet10.0.302 bottle. ItsMicrosoft.NETCore.App.Runtime.osx-arm64copy oflibSystem.IO.Compression.Native.dyliblinks to Homebrew's separately installed Brotli libraries. A Developer ID-signed application can pass strict code-signature verification, notarization, stapling, and Gatekeeper assessment, but then abort indyldbefore managed startup because those libraries are outside the application and have a different signing Team ID.The corresponding runtime asset in Microsoft's official 10.0.302 SDK does not have external Brotli dependencies.
Homebrew discussion: https://github.com/orgs/Homebrew/discussions/6991
The Homebrew maintainer's position is that the formula builds .NET using the supplied build/install instructions and that dependency closure introduced by that build should be handled by .NET.
Reproduction Steps
The affected runtime-pack asset can be inspected without installing the formula:
For the ARM64 Tahoe bottle of
dotnet10.0.302 (SHA-256a420ba0e0a6f87c5b62327a4f40c6d2130bf589af985ec6ed02f7c00d9067cec), the dependencies include:After installation, the placeholders resolve under
/opt/homebrew.Publish a
net10.0/net10.0-macosapplication forosx-arm64with--self-contained true. The publishedlibSystem.IO.Compression.Native.dylibretains those absolute paths. In an application bundle it may appear underContents/MonoBundle.Sign the application with Developer ID and hardened runtime, then launch it. The observed failure is:
The same failure mode also makes the output non-portable to a clean Mac where
/opt/homebrew/opt/brotliis absent.For comparison, the corresponding native library from Microsoft's official .NET 10.0.302 SDK reports only:
Rebuilding the same application with that SDK produced no non-system absolute dependencies and launched successfully.
Expected behavior
A runtime pack used by
--self-containedpublication should be closed over its native runtime dependencies and remain relocatable.Possible ownership models include:
The exact layer is a design decision, but a
--self-containedoutput should not retain absolute dependencies on the build machine's package-manager prefix.Actual behavior
dotnet publish --self-containedcopieslibSystem.IO.Compression.Native.dylibfrom the source-built runtime pack without copying its Brotli closure or rejecting its absolute load commands.The output can therefore:
Standard signing and notarization checks did not detect the dependency because the referenced Brotli libraries remained outside the application bundle.
Regression?
Unknown. The issue is present in the Homebrew-built .NET 10.0.302 / Microsoft.NETCore.App 10.0.10 ARM64 runtime pack. Microsoft's official SDK of the same version does not reproduce it.
Known Workarounds
Using Microsoft's official SDK produces a relocatable runtime asset.
We also verified a narrow post-publish repair:
libSystem.IO.Compression.Native.dylib.libbrotlidec,libbrotlienc, and their transitivelibbrotlicommondependency intoContents/Frameworks.@loader_path/@rpathreferences.com.apple.security.cs.allow-jit) and sign the copied/modified libraries and enclosing application with the same Developer ID identity.After this repair, all 14 Mach-O files in the tested bundle had only system or bundle-relative dependencies, strict signature verification passed, and both the application and the application extracted from a newly signed installer launched successfully. Brotli was the only external dependency family found in that bundle.
Configuration
Other information
This report intentionally does not assume whether
dotnet/runtime, source-build, or the SDK publisher should implement the final correction. Please transfer or redirect it if another .NET repository owns the dependency-closure contract.Disclosure: I used OpenAI Codex to help inspect the binaries and draft this report. I verified the commands and output and will answer maintainer questions myself.