ci: give Dependabot PRs a working test run - #1276
Merged
Merged
Conversation
GitHub does not expose repository Actions secrets to `pull_request` runs opened by dependabot[bot] (Dependabot reads from its own secret store) or by forks, so every `secrets.*` in the CI job resolved to an empty string on those runs. The job died at the `timheuer/base64-to-file@v1` step with "encodedString value is not set" — before Gradle ever ran. Every dependency PR was permanently red, so a genuinely broken bump looked exactly like a healthy one and merges had to go through `--admin`. The `flipcash-tests` lane only runs `generateEmojiList flipcashTestDebug :apps:flipcash:app:lintDebug` — it compiles, unit-tests and lints, and never contacts Firebase, Bugsnag, Mixpanel or Coinbase — so it does not need real credentials. Fall back to committed placeholders when a secret is empty: - google-services.json: replace the third-party base64 action with a plain `base64 --decode`, falling back to .github/ci/google-services.placeholder.json. - local.properties: an *empty* value is not a safe fallback either, because the secrets Gradle plugin copies each entry into BuildConfig verbatim and emits `public static final String BUGSNAG_API_KEY = ;`. Each key gets a zero-filled placeholder instead, substituted independently so a run with partial secrets still uses the real ones it has. Both fall back on the secret being empty rather than on `github.actor`, which covers fork PRs too. Runs with real secrets produce a byte-identical local.properties to before. The job logs a notice naming every value it faked. Secrets are now passed through the environment instead of being interpolated into the shell script, so a value containing a quote or backtick cannot break or escape the command. Also adds gradle/actions/wrapper-validation@v4 as a supply-chain guard on gradle-wrapper.jar (the committed jar matches the published Gradle 9.7.0 checksum, and it is the only wrapper jar in the tree). Verified by running the exact Fastlane lane task set locally with only the placeholders in place: BUILD SUCCESSFUL, lint found no new issues.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every Dependabot PR fails CI the same way, before Gradle runs at all:
GitHub does not expose repository Actions secrets to
pull_requestruns opened bydependabot[bot]— Dependabot reads from its own secret store — or by forks. So everysecrets.*inflipcash-testsresolves to an empty string, andtimheuer/base64-to-file@v1fails on the empty
FLIPCASH2_GOOGLE_SERVICES.The effect is loss of signal: dependency PRs are permanently red, so a genuinely broken bump
looks exactly like a healthy one, and merges go through
--adminon the strength of localverification.
A second failure was hiding behind the first
Fixing only
google-services.jsonmoves the red one step later. The secrets Gradle plugin copiesevery
local.propertiesentry intoBuildConfigverbatim, so writing empty values emits invalidJava and
compileDebugJavaWithJavacfails with 4 errors:An empty string is not a safe fallback. Each key needs a non-empty value.
Approach
Of the options on the table, this takes the committed-placeholder route, because it needs no
repo admin and it gates on the secret being empty rather than on
github.actor == 'dependabot[bot]'— which fixes fork PRs for free.That is safe because this lane runs
generateEmojiList flipcashTestDebug :apps:flipcash:app:lintDebug. It compiles, unit-tests and lints; it never contacts Firebase,Bugsnag, Mixpanel or Coinbase.
Changes
.github/workflows/ci.ymlProvision google-services.json— drops the third-partytimheuer/base64-to-file@v1for aplain
base64 --decode, falling back to.github/ci/google-services.placeholder.json.Write local.properties— one step instead of four, with a zero-filled placeholder per key.Keys are substituted independently, so a run with partial secrets still uses the real ones it
has. The job logs a
::noticenaming every value it faked.env:instead of being interpolated into the shell script. The previousform pasted secret text straight into a command; a value containing a quote or backtick could
break or escape it.
gradle/actions/wrapper-validation@v4after checkout — a cheap supply-chain guard againsta tampered
gradle-wrapper.jar..github/ci/— the placeholder Firebase config plus a README explaining why these fixturesexist and that real keys must never go in them.
Verification
generateEmojiList flipcashTestDebug :apps:flipcash:app:lintDebug→ BUILD SUCCESSFUL,"Lint found no new issues". This is the real Dependabot path end-to-end, not just a YAML check.
real secrets the generated
local.propertiesis byte-identical to what the old workflowproduced, so nothing changes for normal PRs.
ci.ymlparses as YAML and the step list is as expected.gradle-wrapper.jaris the only one inthe tree and its sha256
7a9ce74c…matches Gradle's published 9.7.0 checksum.Checked and deliberately left alone
gh pr checkson live Dependabot PRs showsarea-labelsandtype-labelboth passing.maestro.ymlandbuild-fcash2-upload-android.ymlstill usetimheuer/base64-to-file@v1and the interpolate-secrets-into-shell pattern. They genuinely need real credentials and never
run on Dependabot PRs, so they are out of scope here — the same env-var hardening would apply as
a follow-up.