Skip to content

bug: Brotli manifest entries miss uncompressed iOS builtin files #886

Description

@sengoontoh

Bug Report

Capacitor Version

Capacitor iOS 8.x; the proposed fix was verified with CocoaPods and Swift Package Manager.

Plugin Version

@capgo/capacitor-updater 8.51.15; CLI 8.47.1. Reproducing lookup code is also present on current main (a0f85588).

Context

Automatic signed v2 delta updates (autoUpdate: 'atInstall', publicKey configured). Brotli is enabled during a --delta-only upload with --key-data-v2.

Platform

iOS; occurs in Debug and Release. Validated the fix on a physical iPhone 17 Pro Max running iOS 26.6 and on the iOS simulator.

Current Behavior

For a compressed manifest entry such as assets/app.js.br, downloadManifest removes .br for the destination but looks for builtin assets/app.js.br. The native bundle contains the decoded assets/app.js, so the builtin reuse check cannot find it and downloads an otherwise identical file.

The optional getMissingBundleFiles path has the same filename mismatch.

This is a path issue, not a checksum-format incompatibility: CLI 8.47.1 hashes/signs the uncompressed bytes, then applies Brotli and AES. iOS already decrypts, decompresses, and compares the decoded file hash correctly when it downloads the entry.

Expected Behavior

An unchanged compressed manifest entry should reuse the corresponding uncompressed builtin asset after verifying its hash. A mismatching builtin must still be downloaded and checked normally.

Reproduction

  1. Put a compressible file at public/assets/app.js in the native app.
  2. Upload a signed delta with Brotli enabled and identical assets/app.js content. Confirm the manifest entry is named assets/app.js.br.
  3. Run the automatic update on a fresh install with an empty OTA cache.
  4. Observe a download instead of builtin reuse. --disable-brotli avoids the filename mismatch, but sacrifices the compression benefit.

A native unit reproduction can write a builtin file into an overridden builtinFolderURL, provide its hash under a .br manifest filename, and use an invalid download URL. Successful assembly must then come from verified builtin reuse, without network access.

Proposed Fix

Use the existing resolveManifestTargetPath helper for the builtin lookups as well. It removes one transport .br suffix and retains directory-boundary/path validation. Keep SHA-256 checks unchanged.

Validation

Two regression tests using temporary synthetic builtin files fail against unmodified upstream: the missing-files query reports the .br entry missing, and signed manifest assembly attempts the deliberately invalid download URL. With the path fix, both pass. The signed test uses the existing upstream RSA contract fixture for an empty file.

The full 17-test cache/reuse/path suite passes with the fix, including a modified-content checksum check and rejection of a path outside builtin. Android's builtin resolver already strips .br and does not share this specific lookup mismatch. This fix requires a native plugin release; an OTA cannot patch already-installed Swift code.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions