Skip to content

fix(android-release): wire release signing with key.properties and required secrets#653

Merged
joryirving merged 2 commits into
mainfrom
saffron-cloud/issue-628-android-release-signing
Jul 1, 2026
Merged

fix(android-release): wire release signing with key.properties and required secrets#653
joryirving merged 2 commits into
mainfrom
saffron-cloud/issue-628-android-release-signing

Conversation

@itsmiso-ai

Copy link
Copy Markdown
Contributor

Fixes #628

The release build type in android/app/build.gradle had no signingConfig, so ./gradlew assembleRelease / bundleRelease shipped unsigned (or debug-signed) APKs and AABs while the OTA update manifest advertised them as the stable / beta artifacts. Android refuses to install those on real devices, so a release today would silently produce an artifact that cannot actually be used.

What changes

  • android/app/build.gradle loads android/key.properties at configure time and exposes a signingConfigs.release block driven by it. The release build type now assigns that signing config and fails fast with a clear GradleException when key.properties is missing — no more silently unsigned APKs.
  • android/.gitignore excludes key.properties, *.keystore, and *.jks so secrets stay out of git; a committed android/key.properties.example documents the expected shape.
  • .github/workflows/android-release.yml injects a populated key.properties and the decoded release.keystore from four new GitHub Actions secrets before invoking ./gradlew assembleRelease / bundleRelease. If any of ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, or ANDROID_KEY_PASSWORD is missing, the build step exits 1 with a clear error instead of producing an unsigned release.
  • RELEASE.md documents the new section, the required secrets, and the local-keystore workflow.
  • tests/android-release-signing.test.js asserts the build script reads key.properties, the gitignore keeps secrets out, the example file matches the keys consumed by gradle, the workflow injects the four secrets before assembleRelease, and RELEASE.md documents the contract.

Required GitHub Actions secrets

Secret Purpose
ANDROID_KEYSTORE_BASE64 base64-encoded release keystore
ANDROID_KEYSTORE_PASSWORD keystore password
ANDROID_KEY_ALIAS signing key alias
ANDROID_KEY_PASSWORD signing key password

Until those secrets are configured the release job will fail with a clear error pointing at RELEASE.md#android-release-signing — that is the intended behavior until the production keystore is provisioned.

Tested locally: npm test 324/324 passing (6 new tests), eslint clean on the new file.

Worker: saffron-cloud
Model: litellm-anthropic/MiniMax

…quired secrets

The release build type had no signingConfig, so assembleRelease /
bundleRelease produced unsigned APKs / AABs that Android refuses to
install on real devices — and the OTA manifest still advertised them as
the stable / beta artifacts.

Wire signingConfigs.release to read android/key.properties at configure
time, fall back to throwing a clear GradleException when the file is
missing so we never silently publish an unsigned APK, and document the
ANDROID_KEYSTORE_BASE64 / ANDROID_KEYSTORE_PASSWORD / ANDROID_KEY_ALIAS /
ANDROID_KEY_PASSWORD secrets the workflow now decodes from CI. Add
android/key.properties.example as the committed shape reference, keep
android/key.properties and *.jks / *.keystore out of git, and cover the
new contract in tests/android-release-signing.test.js.
@itsmiso-ai itsmiso-ai requested a review from joryirving as a code owner July 1, 2026 02:09

@its-saffron its-saffron Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — routed smart (risk match: path_handling_changes)

Recommendation: Request Changes

This PR is well-targeted to issue PR 628 and the implementation is fundamentally sound, but the test suite is failing in CI while the PR body claims 324/324 passing. That discrepancy must be resolved before merge.


Change-by-Change Findings

.github/workflows/android-release.yml

The workflow step "Configure release signing" reads four GitHub Actions secrets, validates none are empty, decodes the keystore to android/app/release.keystore, and writes a populated android/key.properties. The ordering is correct — this step runs before assembleRelease/bundleRelease. The set -euo pipefail guard and the explicit error exit when any secret is missing provide fail-fast behavior that matches the issue's intent.

android/app/build.gradle

The Gradle file now loads key.properties at configure time and exposes a signingConfigs.release block. The release build type conditionally applies the signing config only when key.properties exists, and throws a GradleException with a pointer to RELEASE.md#android-release-signing when it does not. This is the correct fail-fast behavior described in issue PR 628 — unsigned APKs will no longer silently ship.

android/.gitignore

Entries for **/key.properties, *.keystore, and *.jks are added under a clear comment referencing the gradle config and release doc. This correctly keeps signing material out of the repository.

android/key.properties.example

The template documents storeFile, storePassword, keyAlias, and keyPassword — matching the keys read by gradle. Comments reference CI injection and the release doc.

RELEASE.md

The new "Android Release Signing" section documents the mechanism, required secrets, CI injection workflow, and local development steps. A test explicitly asserts RELEASE.md must not leak any actual keystore secret value (assert.doesNotMatch(releaseMd, /^storePassword=\S+/m)), which is good.

tests/android-release-signing.test.js

New structural/static tests covering: gradle reads key.properties, gitignore entries, example file shape, release doc completeness, workflow injects secrets before assembleRelease, and gradlew executability. These are reasonable static assertions given no Android SDK is installed in CI.


Standards Compliance

The implementation follows AGENTS.md conventions: fail-fast behavior instead of silent failures, secrets are gitignored, the workflow clearly documents required inputs, and the gradle change comments the intent. No deviations from repository standards were identified.


Linked Issue Fit

Issue PR 628 (parent: PR 622) explicitly requires:

"Wire Android release signing: add signingConfigs.release driven by env-injected keystore (with android/key.properties gitignored) and document the required GitHub Actions secrets; reject release builds when signing config is missing instead of silently publishing unsigned APKs."

All three requirements are satisfied:

  • signingConfigs.release is wired and driven by key.properties
  • key.properties is gitignored ✓
  • Release builds fail fast with a clear error when signing config is missing ✓

Required Checks — Explicit Status

Check Status
Check for breaking API changes in updated dependencies Does not apply. This PR introduces no dependency changes — it is a feature/infrastructure change only.
Run full test suite after upgrade Failed in CI. The test check completed with failure. The PR body claims "324/324 passing (6 new tests)", which contradicts the CI result. The specific failing tests are not visible in the corpus. The new tests/android-release-signing.test.js assertions are static/file-based and should not require an Android SDK, so the failures may be in pre-existing tests.
Review for path traversal vulnerabilities Low risk, one open concern. In the workflow, storeFile is hardcoded to release.keystore — no user input. In local development, storeFile is user-supplied via key.properties. Gradle's file() resolves relative paths from the gradle project root (android/). A storeFile value like ../secrets/keystore.jks would resolve to android/secrets/keystore.jks, which is still within the project. However, there is no validation in the gradle config or tests that constrains storeFile to the android/app/ directory or prevents traversal beyond it.
Test with edge-case paths (null bytes, symlinks) Not covered. There are no tests or guards for null bytes, symlinks, or unusual storeFile path values in key.properties.

Evidence Provider Findings

No evidence providers were configured for this review.


Tool Harness Findings

No tool calls were issued; the review proceeded from the provided corpus directly.


Unknowns / Needs Verification

  1. Test failures in CI (BLOCKER): The test job failed but the specific failing tests are not visible in the corpus. The claim of "324/324 passing" in the PR body must be reconciled with the CI failure result. It is unclear whether the failures are in the new tests/android-release-signing.test.js or in pre-existing tests broken by side effects.
  2. Path constraints for storeFile: No validation exists to ensure storeFile resolves inside android/app/. A local developer could accidentally or adversarially point it outside the expected directory. Consider adding a gradle assertion that storeFile resolves within android/app/.
  3. Edge-case path testing: Null bytes, symlinks, and unusual path characters in key.properties values are not tested or guarded against.

Conclusion

The implementation correctly addresses issue PR 628's requirements and follows repository conventions. However, the CI test job failure contradicts the PR author's claim of 324/324 passing. This discrepancy must be explained and resolved. The path-handling concern (lack of storeFile validation) is a minor hardening issue rather than a blocker, but the test failure is.

Verdict derived from structured findings (verdict_policy=findings_severity_gated): 0 blocker finding(s) out of 2; model verdict was 'request_changes'.

@joryirving joryirving merged commit 4536fc5 into main Jul 1, 2026
9 checks passed
@joryirving joryirving deleted the saffron-cloud/issue-628-android-release-signing branch July 1, 2026 18:22
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.

Wire Android release signing

2 participants