Skip to content

fix: pin 35 unpinned action(s),extract 22 unsafe expression(s) to env vars#8606

Open
dagecko wants to merge 1 commit intoAppFlowy-IO:mainfrom
dagecko:runner-guard/fix-ci-security
Open

fix: pin 35 unpinned action(s),extract 22 unsafe expression(s) to env vars#8606
dagecko wants to merge 1 commit intoAppFlowy-IO:mainfrom
dagecko:runner-guard/fix-ci-security

Conversation

@dagecko
Copy link
Copy Markdown

@dagecko dagecko commented Mar 26, 2026

This is a re-submission of #8605, which was closed due to a branch issue on my end. Same fixes, apologies for the noise.

Security: Harden GitHub Actions workflows

Hey, I found some CI/CD security issues in this repo's GitHub Actions workflows. These are the same vulnerability classes that were exploited in the tj-actions/changed-files supply chain attack. I've been reviewing repos that are affected and submitting fixes where I can.

This PR applies mechanical fixes and flags anything else that needs a manual look. Happy to answer any questions.

Fixes applied

Rule Severity File Description
RGS-002 high .github/workflows/build_command.yml Extracted 1 unsafe expression(s) to env vars
RGS-007 high .github/workflows/commit_lint.yml Pinned 1 third-party action(s) to commit SHA
RGS-007 high .github/workflows/docker_ci.yml Pinned 2 third-party action(s) to commit SHA
RGS-007 high .github/workflows/flutter_ci.yaml Pinned 6 third-party action(s) to commit SHA
RGS-007 high .github/workflows/ios_ci.yaml Pinned 4 third-party action(s) to commit SHA
RGS-007 high .github/workflows/mobile_ci.yml Pinned 1 third-party action(s) to commit SHA
RGS-007 high .github/workflows/ninja_i18n.yml Pinned 1 third-party action(s) to commit SHA
RGS-007 high .github/workflows/release.yml Pinned 13 third-party action(s) to commit SHA
RGS-002 high .github/workflows/release.yml Extracted 21 unsafe expression(s) to env vars
RGS-007 high .github/workflows/rust_ci.yaml Pinned 3 third-party action(s) to commit SHA
RGS-007 high .github/workflows/rust_coverage.yml Pinned 3 third-party action(s) to commit SHA
RGS-007 high .github/workflows/translation_notify.yml Pinned 1 third-party action(s) to commit SHA

Additional findings (manual review recommended)

| Rule | Severity | File | Description |
| RGS-002 | critical | .github/workflows/release.yml | Expression Injection via Branch Name or Untrusted Input |
| RGS-012 | high | .github/workflows/mobile_ci.yml | Secret Exfiltration via Outbound HTTP Request |
| RGS-014 | high | .github/workflows/mobile_ci.yml | Expression Injection via workflow_dispatch Input |
| RGS-012 | high | .github/workflows/mobile_ci.yml | Secret Exfiltration via Outbound HTTP Request |
| RGS-012 | high | .github/workflows/release.yml | Secret Exfiltration via Outbound HTTP Request |
| RGS-012 | high | .github/workflows/release.yml | Secret Exfiltration via Outbound HTTP Request |

Why this matters

GitHub Actions workflows that use untrusted input in run: blocks or reference unpinned third-party actions are vulnerable to code injection and supply chain attacks. These are the same vulnerability classes exploited in the tj-actions/changed-files incident which compromised CI secrets across thousands of repositories.

How to verify

Review the diff, each change is mechanical and preserves workflow behavior:

  • Expression extraction: Moves ${{ }} expressions from run: blocks into env: mappings, preventing shell injection
  • SHA pinning: Pins third-party actions to immutable commit SHAs (original version tag preserved as comment)

If this PR is not welcome, just close it and I won't send another.

Summary by Sourcery

Harden GitHub Actions workflows by pinning third-party actions to specific SHAs and isolating dynamic expressions and secrets via environment variables.

CI:

  • Pin multiple third-party actions across release, Flutter, iOS, Rust, Docker, commit lint, mobile, i18n, and translation workflows to immutable commit SHAs.
  • Extract GitHub context expressions and secrets from shell commands into env mappings in release and build_command workflows to reduce injection risk.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 26, 2026

CLA assistant check
All committers have signed the CLA.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Mar 26, 2026

Reviewer's Guide

Pins previously unpinned third‑party GitHub Actions to specific commit SHAs and extracts interpolated GitHub/secret expressions out of shell command strings into env vars to harden CI workflows against supply‑chain and expression‑injection attacks, without changing intended behavior.

Sequence diagram for secure secret usage in release workflow run steps

sequenceDiagram
  participant GitHubSecrets
  participant Workflow as ReleaseWorkflow
  participant JobEnv as JobEnvironment
  participant Shell as RunStepShell
  participant External as ExternalService

  GitHubSecrets->>Workflow: Provide DISCORD, MACOS_CERTIFICATE, MACOS_NOTARY_USER, etc.
  Workflow->>JobEnv: Map secrets to env vars (DISCORD, MACOS_CERTIFICATE, REF_NAME, ...)
  JobEnv->>Shell: Expose env vars as ${VAR} during run step
  Shell->>External: Use env vars in commands (curl, xcrun notarytool, security, etc.)

  note over Workflow,Shell: Interpolation inside shell commands changed from ${{ secrets.* }} and ${{ github.ref_name }} to ${VAR} sourced from env
Loading

Flow diagram for extracting unsafe expressions to env variables

flowchart TD
  Start["Start: Existing workflow run step"] --> Detect["Detect ${{ github.* }} or ${{ secrets.* }} inside run script"]
  Detect -->|Found| Extract["Create env variable mapping for each expression"]
  Extract --> Replace["Replace occurrences in run script with ${VAR} references"]
  Replace --> Result["Result: Shell sees only ${VAR}, GitHub interpolates values into env"]
  Detect -->|None| Keep["Keep run step unchanged"]
Loading

File-Level Changes

Change Details Files
Harden release workflow by pinning actions and moving GitHub/secrets expressions from shell scripts into environment variables.
  • Replace use of version tags for third-party actions (Flutter, Rust toolchain, Docker, Slack, etc.) with immutable commit SHA references while preserving original versions as comments.
  • Introduce job-level env variables (e.g., REF_NAME, MACOS_* secrets, DISCORD) and reference them in run: scripts instead of inlined ${{ }} expressions to mitigate shell injection risk.
  • Refactor release build scripts (Windows, macOS, Linux) to use env variables for GitHub ref name and secrets in dart, cargo, shell, codesign, notarytool, and curl commands.
  • Maintain existing release behavior (paths, flags, matrix usage) while changing only how variables and secrets are interpolated.
.github/workflows/release.yml
Pin Flutter/Rust and related cache/install actions to SHAs in Flutter CI workflow.
  • Pin actions-rs/toolchain, subosito/flutter-action, Swatinem/rust-cache, and taiki-e/install-action to specific commit SHAs.
  • Keep workflow logic, inputs, and matrices unchanged; only the action references are updated.
.github/workflows/flutter_ci.yaml
Pin toolchain/cache actions to SHAs in iOS CI workflow.
  • Pin actions-rs/toolchain, subosito/flutter-action, Swatinem/rust-cache, and davidB/rust-cargo-make to specific commit SHAs.
  • Preserve all existing iOS build logic and environment usage.
.github/workflows/ios_ci.yaml
Pin timezone, Rust toolchain, and cache actions to SHAs in Rust CI workflow.
  • Pin szenius/set-timezone, actions-rs/toolchain, and Swatinem/rust-cache to specific commit SHAs.
  • Leave job configuration and Rust tooling settings unchanged.
.github/workflows/rust_ci.yaml
Pin Rust toolchain, Flutter, and cache actions to SHAs in Rust coverage workflow.
  • Pin actions-rs/toolchain, subosito/flutter-action, and Swatinem/rust-cache to specific commit SHAs.
  • Do not alter coverage matrix, env, or installation commands apart from action references.
.github/workflows/rust_coverage.yml
Extract secret token from inline curl header into an env var in build command workflow.
  • Replace direct ${{ secrets.TOKEN }} interpolation in curl Authorization header with ${TOKEN} and add corresponding TOKEN env mapping.
  • Keep request URL, headers, and payload structure intact.
.github/workflows/build_command.yml
Pin Docker setup and build actions to SHAs in Docker CI workflow.
  • Pin docker/setup-buildx-action and docker/build-push-action to specific commit SHAs.
  • Retain all Docker build configuration and caching comments unchanged.
.github/workflows/docker_ci.yml
Pin commit linting GitHub Action to a specific SHA.
  • Update wagoid/commitlint-github-action from version tag to a commit SHA while keeping config and usage the same.
.github/workflows/commit_lint.yml
Pin Slack notification action to a specific SHA in mobile CI workflow.
  • Update 8398a7/action-slack from version tag to commit SHA, leaving notification logic and inputs unchanged.
.github/workflows/mobile_ci.yml
Pin Ninja i18n action to a specific SHA in i18n workflow.
  • Update opral/ninja-i18n-action from branch reference to commit SHA while preserving env and step usage.
.github/workflows/ninja_i18n.yml
Pin Discord notification action to a specific SHA in translation notify workflow.
  • Update Ilshidur/action-discord from branch reference to commit SHA, keeping inputs and env unchanged.
.github/workflows/translation_notify.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path=".github/workflows/release.yml" line_range="86-95" />
<code_context>
-          dart ./scripts/flutter_release_build/build_flowy.dart exclude-directives . ${{ github.ref_name }}
-          cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-windows-x86 appflowy
-          dart ./scripts/flutter_release_build/build_flowy.dart include-directives . ${{ github.ref_name }}
+          dart ./scripts/flutter_release_build/build_flowy.dart exclude-directives . ${REF_NAME}
+          cargo make --env APP_VERSION=${REF_NAME} --profile production-windows-x86 appflowy
+          dart ./scripts/flutter_release_build/build_flowy.dart include-directives . ${REF_NAME}
</code_context>
<issue_to_address>
**issue (bug_risk):** Environment variable expansion with `${REF_NAME}` will not work on Windows runners using the default PowerShell shell.

Given the Windows-specific paths and tools, these steps are running under the default PowerShell shell. In PowerShell, environment variables must be referenced as `$Env:REF_NAME`, so `${REF_NAME}` will be passed literally to `dart`, `cargo make`, and `iscc` instead of the tag value.

To fix this, either:
- switch back to GitHub expression interpolation (e.g. `... ${{ github.ref_name }}` / `... ${{ env.REF_NAME }}`), or
- keep `env: REF_NAME: ...` and use PowerShell syntax (`$Env:REF_NAME`), or
- set `shell: bash` on these steps if you want bash-style `${REF_NAME}`.

Otherwise Windows builds will use an incorrect or empty version string.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@dagecko
Copy link
Copy Markdown
Author

dagecko commented Mar 27, 2026

CLA signed.

  • Chris

@dagecko dagecko force-pushed the runner-guard/fix-ci-security branch from c1ae6bf to 48222be Compare March 29, 2026 01:34
@dagecko
Copy link
Copy Markdown
Author

dagecko commented Mar 29, 2026

Pushed an update. Saw the feedback on the PowerShell env var expansion and fixed it. The Windows build steps now have shell: bash so ${REF_NAME} expands correctly.

We found some additional hardening opportunities post the original PR, so the scope of changes is larger than before. Here's what's in the updated commit:

  • 9 critical: github.ref_name extracted from run blocks to env mappings across the release workflow
  • 13 high: inline secrets (macOS certificates, notary credentials, Discord webhook) extracted from run blocks to env mappings
  • 35 action pins across all 11 workflow files
  • Added shell: bash to 2 Windows build steps so env var references expand correctly
  • Manually fixed the Discord notification step where github.ref_name was embedded inside a JSON string

All changes are mechanical and preserve existing workflow behavior. Happy to walk through any of it if you have questions.

I also posted on Twitter about a lot of these hardening techniques if you want to read through the research, and if you like it wouldn't mind a repost.

- Chris (dagecko)

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.

2 participants