Skip to content

CodeSigningPlugin signs too late (assetEmitted) – incompatible with in-memory asset consumers (e.g. Zephyr)Β #1377

@JhohellsDL

Description

@JhohellsDL

Describe the bug

πŸ› CodeSigningPlugin signs too late (assetEmitted) – incompatible with in-memory asset consumers (e.g. Zephyr)

Problem

CodeSigningPlugin currently signs bundles using compiler.hooks.assetEmitted.

When using withZephyr(), Zephyr captures and uploads assets during the processAssets phase at PROCESS_ASSETS_STAGE_REPORT (5000), which occurs before assetEmitted.

As a result, bundles are uploaded to the CDN unsigned, making verifyScriptSignature: 'strict' ineffective when using Re.Pack together with Zephyr.


Root Cause

There is a timing mismatch in the compilation lifecycle:

processAssets (ANALYSE 2000)   ← no signing happens here
processAssets (REPORT 5000)    ← Zephyr uploads assets (unsigned βœ—)
emit                           ← assets written to disk
assetEmitted                   ← CodeSigningPlugin signs (too late)

Zephyr registers its upload step at PROCESS_ASSETS_STAGE_REPORT:

compilation.hooks.processAssets.tapPromise(
  {
    name: pluginName,
    stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT, // 5000
  },
  async (assets) => {
    // uploads to CDN
  }
);

Expected Behavior

Assets should be signed before they are consumed by other plugins/tools during the compilation pipeline.


Proposed Solution

Move the signing step earlier in the lifecycle by using processAssets, specifically at PROCESS_ASSETS_STAGE_ANALYSE (2000), while assets are still in memory.

processAssets (ANALYSE 2000)   ← sign here in memory βœ“
processAssets (REPORT 5000)    ← Zephyr uploads signed assets βœ“
emit                           ← assets written to disk (already signed) βœ“

Suggested Implementation

Instead of signing in assetEmitted, perform signing during processAssets and update assets in memory using compilation.updateAsset():

compiler.hooks.thisCompilation.tap('RepackCodeSigningPlugin', (compilation) => {
  const { Compilation, sources } = compiler.webpack;

  compilation.hooks.processAssets.tap(
    {
      name: 'RepackCodeSigningPlugin',
      stage: Compilation.PROCESS_ASSETS_STAGE_ANALYSE,
    },
    () => {
      for (const chunk of compilation.chunks) {
        for (const file of chunk.files) {
          // apply existing signing logic here
          compilation.updateAsset(file, new sources.RawSource(signed));
        }
      }
    }
  );
});

Compatibility Consideration

To preserve backward compatibility, this could be introduced as a configurable option:

type CodeSigningPluginConfig = {
  signingStage?: 'assetEmitted' | 'processAssets';
};
  • assetEmitted β†’ current default behavior
  • processAssets β†’ enables in-memory signing (recommended for Zephyr)

Verified

This approach has been tested using a custom plugin:

  • Bundles are signed in memory before PROCESS_ASSETS_STAGE_REPORT
  • Zephyr uploads already signed assets
  • Output files on disk remain correctly signed
  • verifyScriptSignature: 'strict' works as expected end-to-end

Environment

  • @callstack/repack: [version]
  • Zephyr integration: [version]
  • Platform: Android / iOS

System Info

macOS (Apple Silicon)
Node: 22.19.0
Pnpm: 9.15.3
React Native: 0.78.2
Re.Pack: 5.2.5
Rspack: 1.3.4

Build type: Release
Platform: Android / iOS

Re.Pack Version

5.2.5

Reproduction

Not a minimal reproduction repository, but the issue can be consistently reproduced when using CodeSigningPlugin together with Zephyr integration (withZephyr()). Happy to provide a minimal reproduction if needed.

Steps to reproduce

  1. Configure Re.Pack with CodeSigningPlugin enabled
  2. Integrate Zephyr using withZephyr()
  3. Build the project (e.g. release bundle)
  4. Observe that Zephyr uploads assets during processAssets
  5. Inspect uploaded bundle β†’ it is not signed
  6. Inspect output on disk β†’ bundle is signed

Result:
Assets uploaded to CDN are unsigned, while local output is signed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    status:newNew issue, not reviewed by the team yet.type:bugA bug report.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions