Skip to content

Fix missing hash verification - #49

Open
ofalvai wants to merge 2 commits into
masterfrom
push-uzwkruuvynnv
Open

Fix missing hash verification#49
ofalvai wants to merge 2 commits into
masterfrom
push-uzwkruuvynnv

Conversation

@ofalvai

@ofalvai ofalvai commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Why

Android and iOS skipped hash verification entirely for a full (non-diff), unsigned update - so a corrupted or tampered download could install with no integrity check. Diff updates and signed updates are not affected.

Public key Signature in bundle Update type Before After
Yes Yes Any Verified Unchanged
Yes No Any Rejected Unchanged
No Yes Any Verified (+warning) Unchanged
No No Diff Verified Unchanged
No No Full Not verified Verified (this PR)

What

  • Android (CodePushUpdateManager.java) and iOS (CodePushPackage.m): hash verification now always runs, regardless of diff/full or signing status.
  • Mock test server: now computes and serves the real hash of each update archive it builds, so the e2e suite exercises real verification instead of a random placeholder.
  • Adjusted the test case localPackage.install.revert.dorevert to match actual native rollback behavior (rejected on the first revert, not the second). It only passed before because the mock's random hash bug made a re-served identical package look like a different update on retry.

Decisions

  • Instead of updating 30+ E2E test cases with unique hash values, I think it makes more sense at this point to dynamically override the server's response in the test harness with a previously "caught" hash value. See updateMockPackageHash() / applyKnownPackageHash().
  • No new hash-mismatch/signing test scenarios added, even though those are not covered with tests at the moment. I was working on adding unit test coverage when discovered this bug, I am still planning to cover this codepath with unit tests instead.

@ofalvai
ofalvai force-pushed the push-uzwkruuvynnv branch from 55be8cc to 5529153 Compare August 31, 2026 10:03
@ofalvai
ofalvai requested a balanced review from Copilot August 31, 2026 10:03
@ofalvai
ofalvai marked this pull request as ready for review August 31, 2026 10:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds integrity verification for unsigned full ZIP updates on Android and iOS.

Changes:

  • Runs folder-hash verification unconditionally for ZIP updates.
  • Generates real hashes in the E2E mock server.
  • Aligns rollback expectations with native behavior.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
android/.../CodePushUpdateManager.java Enforces Android ZIP content verification.
ios/CodePush/CodePushPackage.m Enforces iOS ZIP content verification.
test/test.ts Computes mock hashes and updates rollback expectations.
code-push-plugin-testing-framework/script/serverUtil.js Associates archives with computed hashes.
code-push-plugin-testing-framework/typings/code-push-plugin-testing-framework.d.ts Declares the new hash API.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

);
}

CodePushUpdateUtils.verifyFolderHash(newUpdateFolderPath, newUpdateHash);

@ofalvai ofalvai Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acceptable, the non-ZIP update codepath is considered legacy, we don't recommend generating plain JS bundle updates (fortunately, only around 1% of our users do). It's not just the integrity check that's missing from this codepath, but also code signing.

Comment thread ios/CodePush/CodePushPackage.m Outdated
Comment thread test/test.ts Outdated
Comment on lines +557 to +560
private updateMockPackageHash(bundleFolder: string, isDiff: boolean, archivePath: string): string {
if (!isDiff) {
ServerUtil.setPackageHashForPath(archivePath, computeUpdateContentsHash(bundleFolder));
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged in PR description, see decisions section.

);
}

if (!isSignatureVerificationEnabled && isSignatureAppearedInBundle) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How likely it is in the wild? Can you release an app without signing it to the store (if not, that app should in practice always have a public key)?

@ofalvai ofalvai Sep 1, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't about code signing the native app itself, it's just the CodePush OTA upload. And CodePush updates are not code-signed by default, we don't enforce this. Other products also don't enfoce code signing, in fact, Expo limits this feature to their highest paid tiers.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will provide the stringPublicKey used for the isSignatureVerificationEnabled?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from here:

Essentially, this is coming from the app's strings.xml

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the public key in strings.xml and Info.plist is optional or mandatory?

* pointing exports.updatePackagePath at this archive gets the matching package_hash instead
* of the one filled in by default.
*/
function setPackageHashForPath(archivePath, packageHash) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can 2 tests use the same archivePath and different packageHash? If so running those tests migth step en each others to by overriding the hash for the path in the map.

Comment thread ios/CodePush/CodePushPackage.m Outdated
"Please ensure that public key is properly configured within your application.");
}

if (![CodePushUpdateUtils verifyFolderHash:newUpdateFolderPath

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also store the return value in a variable as we do for the signature check?

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.

3 participants