29NativeAOT & limits for untrusted input
ZplCore, ZplCore.VectorLift, ZplCore.Testing, the CLI and the MCP server are NativeAOT-compatible (IsAotCompatible, 0 trim/AOT warnings). Rendering, barcodes, label model, JSON, compliance, scan-proof, extraction/redaction, GS1, RFID, printer twin and Vector-Lift produce the same output under NativeAOT as with the JIT – build/aot-smoke.sh compares about 790 outputs byte for byte (verified on osx-arm64, linux-arm64 and linux-x64).
dotnet publish tools/ZplCli -p:PublishProfile=aot # host RID, or -r linux-arm64 / linux-x64 … dotnet publish tools/ZplCore.Mcp -p:PublishProfile=aot dotnet publish MyApp -c Release -p:PublishAot=true # your own app referencing ZplCore
For NativeAOT containers without the .NET runtime, use mcr.microsoft.com/dotnet/runtime-deps:10.0-noble-chiseled-extra (distroless, non-root, includes ICU for culture-aware formatting; ~79–105 MB total image size including the app). Tested on a Synology DS923+ NAS (linux-x64, AMD Ryzen Embedded R1600, 2 cores / 4 threads, Synology Container Manager), the full corpus ran 24 % faster under NativeAOT than under JIT (10.7 s vs. 14.0 s, 793/793 identical outputs) and reached 237,428 labels/s parsing, 1,233 labels/s bitmap rendering and 295 PNG labels/s (> 17,500 PNGs/min) inside the NativeAOT container. On 32-bit ARM devices such as the Raspberry Pi 2 (linux-arm / armv7l), zplCore runs as a self-contained or framework-dependent container on aspnet:10.0-noble-chiseled-extra (174 MB), and on 64-bit ARM (linux-arm64, Raspberry Pi 4/5) directly as NativeAOT.
T are kept; nested types ([Supplier.Name]) are removed by the trimmer unless referenced statically. Under NativeAOT this throws a MissingMemberException naming type, member and remedy (TrimmerRootAssembly) instead of printing an empty field. JSON, dictionary and DataRow binding are not affected. NativeAOT needs the platform's native toolchain; musl/Alpine is not a target yet.ZplLimits: one scope for everything
For untrusted input (uploads, web services, agents) ZplLimits.Enforce sets hard limits for every renderer in its scope – also those that the compliance check, scan-proof, import, extraction, GS1 migration, printer twin, cut plan and Vector-Lift create internally, across tasks and Parallel loops. Settings are clamped to the stricter value, font providers, system fonts and background images are turned off; optionally a fixed clock. The MCP server runs every call inside this scope.
// using ZplCore.Compliance;
using (ZplLimits.Enforce(new ZplLimits { MaxLabels = 10, MaxGraphicBytes = 4 << 20, FixedClock = DateTime.UnixEpoch }))
{
// applies to every renderer in this scope – also those that compliance, scan-proof,
// import, extraction, printer twin and Vector-Lift create internally (also across tasks/Parallel)
var report = ZplCompliance.Check(untrusted, CompliancePacks.Gs1LogisticLabel);
}

