Skip to content

MOB-71 — trim Play upload keystore passphrase at the prompt - #80

Merged
GenericJam merged 1 commit into
masterfrom
fix/MOB-71-keystore-trim
Sep 12, 2026
Merged

GenericJam merged 1 commit into
masterfrom
fix/MOB-71-keystore-trim

Conversation

@GenericJam

Copy link
Copy Markdown
Owner

Mix.shell().prompt/1 returns the input line including the trailing newline. SetupWizard.generate_keystore/2 passed that raw value into both keytool -storepass/-keypass and android/keystore.properties. Gradle later read that file literally on release signing, so every subsequent AAB was signed with a password ending in \n — a value nobody could re-type at the Play upload console. The failure is silent until an upload rejects the signature deep in bundletool, and by then the keystore is committed to being that value (Play Console binds the app to its signature).

Changes

  • prompt_trimmed/2 — new private helper wrapping shell.prompt/1 + String.trim/1. All three prompts in generate_keystore/2 (passphrase, name, org) now go through it.
  • keystore_properties_content/1 — extracted from write_keystore_properties/2 as a @doc'd public function, so the trim is testable at the seam without needing to mock Mix.shell(). Trims defensively — a raw string in still becomes a clean line out.

Tests

Three tests in test/mob_dev/google_play/setup_wizard_test.exs (describe "keystore_properties_content/1"):

  • does NOT bake a trailing newline — feeds \"secret\\n\", asserts the file body contains storePassword=secret on its own line and NOT storePassword=secret\\n\\n. Flips on revert.
  • strips leading/trailing whitespace — feeds \" secret \", asserts the same. Flips on revert.
  • leaves an already-clean passphrase alone — happy-path anchor; passes either way.

Manually revert-verified: temporarily removed String.trim(passphrase) from the pure function, forced a recompile, and reran the test — 2 of the 3 assertions fail with the actual expected diagnostic output. Restored → 15/15 pass.

Scope check

Other shell.prompt sites in the file (project number/id at line 282, developer account id at line 424, package name at line 510) already trim their input. Only the three keystore prompts were leaking the newline; the audit's "inconsistent" description was accurate.

Gates

  • mix test: 2450 pass, 0 fail
  • mix format --check-formatted: clean
  • mix credo --strict: 0 issues (3752 mods/funs)
  • mix compile --warnings-as-errors --force: clean
  • mix mob.security_scan: 0 findings

Closes MOB-71.

`Mix.shell().prompt/1` returns the whole input line including the trailing
newline, and `SetupWizard.generate_keystore/2` passed the raw value into
both `keytool -storepass` and `android/keystore.properties`. Gradle read
that file literally on subsequent release signing, so the keystore held a
password ending in `\n` — a value nobody could re-type at the next Play
upload. The failure is silent until an AAB upload rejects the signature.

- Extract `keystore_properties_content/1` as a public pure function that
  trims defensively, so the fix is testable at the seam.
- Prompt values (passphrase, name, org) run through a new
  `prompt_trimmed/2` helper. The `-dname` and `-storepass`/`-keypass`
  keytool args then get already-clean strings.
- Three tests on the content function; two flip on revert. Verified by
  temporarily removing the `String.trim` and rerunning — two assertions
  fail, one (the clean-passphrase happy-path anchor) passes either way.

Other prompt sites in the file (project number, developer account id,
package name) already trim their input; only the three keystore prompts
were leaking the newline through.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@GenericJam
GenericJam merged commit df745c7 into master Sep 12, 2026
3 checks passed
@GenericJam
GenericJam deleted the fix/MOB-71-keystore-trim branch September 12, 2026 00:54
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