Skip to content

Build the tag's APK in CI without the upload key - #10

Merged
ialakey merged 1 commit into
masterfrom
ci/tag-apk-without-upload-key
Sep 6, 2026
Merged

Build the tag's APK in CI without the upload key#10
ialakey merged 1 commit into
masterfrom
ci/tag-apk-without-upload-key

Conversation

@ialakey

@ialakey ialakey commented Sep 6, 2026

Copy link
Copy Markdown
Owner

The release workflow kept the upload keystore in four repository secrets and
signed with it. That is a lot of blast radius for what CI is for: a compromised
runner, or a workflow edited in a pull request, walks off with the key that
identifies this app on Play — and once the listing exists, that key cannot be
rotated.

What CI does now

A v* tag builds only the APK, with no android/key.properties, so Gradle
falls back to the debug signing config it already has for that case. No secrets
are read.

  • The signature check is inverted. The workflow now requires the Android
    debug certificate: a real one on a runner would mean the key had leaked.
  • The tag is checked against pubspec.yaml. v2.0.0 must be 2.0.0, and
    the build is passed that versionName/versionCode explicitly, so a tag can
    never claim a version the artifact does not carry.
  • The artifact is srbguide-<version>-debug-signed.apk with its SHA-256, and
    the draft release states plainly what it is: sideload and QA only. Every run
    generates its own debug key, so two CI builds do not update each other in
    place — uninstall first.
  • analyze, the unit tests, validate_guide.dart and the targetSdk 36 check
    on the merged manifest all stay.

The Play bundle is built on the release machine with tool/build_release.ps1,
which is now the only place the upload key is used, and which still verifies
the signature scheme, the certificate digest and targetSdk before writing to
dist/.

create_upload_key.ps1 no longer offers to base64 the keystore for a secret
that no longer exists.

After merging

Delete ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD,
ANDROID_KEY_ALIAS and ANDROID_KEY_PASSWORD from
Settings → Secrets and variables → Actions. Nothing reads them, and a secret
that exists is a secret that can leak.

Verification

Ran the workflow's build path locally with android/key.properties moved
aside, which is exactly the state a runner is in:

√ Built build/app/outputs/flutter-apk/app-release.apk (64.2MB)
Signer #1 certificate DN: C=US, O=Android, CN=Android Debug
android:targetSdkVersion="36"  android:versionCode="14"  android:versionName="2.0.0"

So the build succeeds without a keystore, the inverted certificate check
passes, and the manifest the targetSdk step reads is still produced by an
APK-only build. The workflow YAML parses.

The release workflow held the upload keystore in four repository secrets and
signed with it. That is a lot of blast radius for what CI is actually for: a
runner that is compromised, or a workflow edited in a pull request, walks away
with the key that identifies this app on Play, and a key cannot be rotated
after the listing exists.

CI now builds only the APK, with no key.properties, so Gradle falls back to the
debug signing config. The artifact is for sideloading a tagged build onto a
phone; the bundle for Play is built on the release machine by
tool/build_release.ps1, which is now the only place the key is used.

- The signature check is inverted: the workflow requires the Android debug
  certificate, because a real one here would mean the key had reached a runner.
- The tag is checked against `version:` in pubspec.yaml, so `v2.0.0` cannot
  ship anything but 2.0.0, and the build is passed that version explicitly.
- The artifact is named `srbguide-<version>-debug-signed.apk`, with its
  SHA-256, and the draft release says what it is and is not. Every run signs
  with a fresh debug key, so these do not update each other in place.
- `create_upload_key.ps1` no longer offers to base64 the keystore for a secret
  that no longer exists.

The four ANDROID_* secrets should be deleted from the repository; nothing reads
them any more.
@ialakey
ialakey merged commit 5414276 into master Sep 6, 2026
2 checks passed
@ialakey
ialakey deleted the ci/tag-apk-without-upload-key branch September 7, 2026 06:18
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.

1 participant