Skip to content

同印方载,异印即拒 · Accept host-matched re-signed macOS artifacts - #59

Merged
eric8810 merged 5 commits into
arcships:mainfrom
zerob13:fix/macos-resigned-artifact-acceptance
Aug 5, 2026
Merged

同印方载,异印即拒 · Accept host-matched re-signed macOS artifacts#59
eric8810 merged 5 commits into
arcships:mainfrom
zerob13:fix/macos-resigned-artifact-acceptance

Conversation

@zerob13

@zerob13 zerob13 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

@arcships/light-ocr-darwin-* native packages are re-signed by downstream macOS packaging pipelines: osx-sign rewrites the LC_CODE_SIGNATURE blob of every Mach-O under Contents with --force (including light_ocr_node.node and libonnxruntime.1.22.0.dylib), changing both file size and sha256. The runtime descriptor's strict bytes+sha256 gate then rejects intact binaries with package_load_failed: Descriptor artifact byte count mismatch.

What changed (packages/runtime/src/load-native.cjs):

  • The descriptor check stays the primary gate: stats.size === bytes and sha256(file) === sha256 → load (unchanged path).
  • On macOS only, when the size or hash diverges, a Mach-O is accepted as an equivalent integrity proof iff:
    1. it is a Mach-O (magic pre-check),
    2. codesign --verify --strict passes,
    3. its signing identity matches the host process (process.execPath): identical TeamIdentifier, or both sides ad-hoc signed.
  • Everything else keeps the existing error contract (package_load_failed, byte count / hash mismatch) — no silent fallback, no partial state.
  • win32/linux keep the strict gate (payloads are never re-signed there). The native directory inventory check and the post-load validateNativeContract() ABI check are unchanged.
  • Malformed descriptor records (non-integer bytes, non-hex sha256) still fail hard; the relaxation never applies to them.

Security boundary: ad-hoc signatures are reproducible by anyone, so the both-ad-hoc branch is a documented, macOS-only relaxation. The host-match rule rejects payloads re-signed by any other identity.

Electron integration (no config required)

Nothing changes for Electron apps — the loader auto-adapts to the host signature:

Host app signature Package state Result
Your Developer ID (notarized app, e.g. electron-builder + osx-sign) Re-signed by the same Developer ID during packaging ✅ loads
Ad-hoc (dev build / unsigned local app) Re-signed ad-hoc (or untouched) ✅ loads
Any team ≠ host team Re-signed by a different vendor package_load_failed (by design)
Untouched npm package (any host) Descriptor matches ✅ loads (strict path, no codesign involved)
  • electron-builder / osx-sign re-signing node_modules/@arcships/light-ocr-darwin-*/native/*.node and *.dylib with your Developer ID now loads instead of failing.
  • No env flag, no configuration, no extra dependency: codesign is a stock macOS binary and the loader only verifies (no keychain, no certificate required).
  • If packaging re-signs the native payload with an identity different from the app's own executable, the load fails closed — re-sign it with the same identity as the app bundle.

Verification

  • bindings/node/test/signed-artifact.test.cjs: policy matrix (all platforms) + macOS integration with real codesign (pristine accept; unsigned mutation reject on the size and hash branches; ad-hoc re-sign accepted when the host is ad-hoc, rejected when the host is Developer ID signed; non-Mach-O mutation reject; Developer ID round-trip self-skips when a keychain identity is unavailable — the suite never hangs on a keychain prompt).
  • Manual run on a Developer ID signed host (TeamIdentifier 3AA79YWT4C): pristine pass; tampered-unsigned rejected; ad-hoc re-sign rejected (different signer).
  • Existing descriptor / WebGPU / runtime tests unchanged and passing; python release-contract suite 83/83.
  • Versions advanced conservatively to 0.5.7 (runtime 0.1.7, tiny/medium 0.1.6, document 0.1.3); CHANGELOG.md, README (EN/ZH), npm package README, and decision record D117 updated.

Checklist

  • The change is focused; relevant tests were added or updated when behavior changed.
  • Documentation and CHANGELOG.md were updated for user-visible changes.
  • No credentials, private OCR inputs, generated build trees, or unrelated artifacts are included.

zerob13 and others added 5 commits August 5, 2026 14:33
Downstream macOS packaging pipelines re-sign every Mach-O in the app
bundle (including light_ocr_node.node and the ONNX Runtime dylib) with a
Developer ID or ad-hoc identity while notarizing, which rewrites
LC_CODE_SIGNATURE and changes both file size and sha256. The strict
runtime-descriptor gate therefore rejected intact, verified binaries.

On macOS only, treat a Mach-O whose code signature verifies
(codesign --verify --strict) AND whose signing identity matches the host
process (same TeamIdentifier as process.execPath, or both ad-hoc signed)
as an equivalent integrity proof. All other mismatches keep the existing
package_load_failed contract: no silent fallback, no partial state, and
win32/linux payloads keep the strict bytes+sha256 gate. The descriptor
record format checks, the native directory inventory check, and the
post-load validateNativeContract() ABI check are unchanged.

Ad-hoc acceptance is deliberately macOS-only and documented because
ad-hoc signatures are reproducible by anyone.

Add adaptive macOS integration tests (policy matrix, unsigned mutation
rejection, ad-hoc re-signing per host identity, non-Mach-O rejection,
Developer ID round-trip when a keychain identity is available) and bump
the release closure to conservative patch versions (0.5.7 / runtime
0.1.7 / tiny+medium 0.1.6 / document 0.1.3).
Validate the descriptor digest syntax before the macOS signed-mutation fallback can accept a size-divergent artifact. Add a macOS regression test that keeps malformed records fail-closed.
Update the workflow input and offline smoke package references to the patch versions declared by this release candidate.
Run the signed-artifact policy on both the default macOS Node host and an ad-hoc re-signed host during package smoke. Correct the positive-path assertions uncovered by the new gate and align the build-and-release guide with the current package closure.
@eric8810
eric8810 merged commit 1a26d53 into arcships:main Aug 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants