Sign update manifests so clients can reject a forged update #1

Open
opened 2026-08-25 09:50:29 +00:00 by uwayss · 0 comments
Owner

The problem

The server sends every manifest unsigned. sendMultipart in
apps/backend/src/routes/expo.ts writes the JSON body and no signature.

An app trusts whatever answers its manifest request. If someone takes control of
the domain, the host, or the connection, that person can send their own
JavaScript bundle. The app runs it with the permissions of the real app.

TLS stops an attacker on the network. It does not stop an attacker who gets the
server or the DNS record.

What code signing adds

expo-updates supports a second layer. The build embeds a public certificate. The
server holds the private key and signs each manifest. The client checks the
signature before it loads the update, and rejects a manifest that does not match.

Then a stolen server alone is not enough. The attacker also needs the private key.

Work

  • Make a key pair and a certificate.
  • Store the private key outside the database, and keep it out of the image.
  • Sign the manifest body and send the expo-signature header.
  • Sign the noUpdateAvailable directive as well.
  • Document how a user makes the certificate and configures the app.
  • Test with a real build that a wrong signature stops the update.

Open questions

  • One key for the whole server, or one key for each application?
  • Where does the private key live: a file path in the config, or a value in the environment?
  • Does the dashboard show the certificate, or is this a server setting only?

Expo ships expo-updates codesigning:generate and expo-updates codesigning:configure for the client side. Confirm the flags against the
current Expo documentation before you write the guide.

## The problem The server sends every manifest unsigned. `sendMultipart` in `apps/backend/src/routes/expo.ts` writes the JSON body and no signature. An app trusts whatever answers its manifest request. If someone takes control of the domain, the host, or the connection, that person can send their own JavaScript bundle. The app runs it with the permissions of the real app. TLS stops an attacker on the network. It does not stop an attacker who gets the server or the DNS record. ## What code signing adds expo-updates supports a second layer. The build embeds a public certificate. The server holds the private key and signs each manifest. The client checks the signature before it loads the update, and rejects a manifest that does not match. Then a stolen server alone is not enough. The attacker also needs the private key. ## Work - [ ] Make a key pair and a certificate. - [ ] Store the private key outside the database, and keep it out of the image. - [ ] Sign the manifest body and send the `expo-signature` header. - [ ] Sign the `noUpdateAvailable` directive as well. - [ ] Document how a user makes the certificate and configures the app. - [ ] Test with a real build that a wrong signature stops the update. ## Open questions - One key for the whole server, or one key for each application? - Where does the private key live: a file path in the config, or a value in the environment? - Does the dashboard show the certificate, or is this a server setting only? Expo ships `expo-updates codesigning:generate` and `expo-updates codesigning:configure` for the client side. Confirm the flags against the current Expo documentation before you write the guide.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
uwayss/bareed#1
No description provided.