From 33a996b187dbf531199a214b25ad82b71be73a74 Mon Sep 17 00:00:00 2001 From: Daniel Blaecker Date: Thu, 16 Jul 2026 07:34:11 +0700 Subject: [PATCH 1/3] docs(rules): add GHA template-injection convention (CONV:ACTIONS-NO-INJECTION) --- .agents/rules/500-github-actions.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.agents/rules/500-github-actions.md b/.agents/rules/500-github-actions.md index 019196ed3..a05e55b48 100644 --- a/.agents/rules/500-github-actions.md +++ b/.agents/rules/500-github-actions.md @@ -36,6 +36,30 @@ paths: - uses: some-action@main ``` +### No template injection in `run:` scripts ([CONV:ACTIONS-NO-INJECTION]) + +- **Never interpolate `${{ github.* }}` (or any other `${{ ... }}` expression carrying attacker-influenced data) directly into a `run:` shell script.** The expression is substituted into the script body **before** the shell runs, so a crafted value (e.g. a branch name, PR title, commit message, or actor login containing shell metacharacters) executes as code on the runner. +- **Highest-risk contexts** (attacker-controllable on `pull_request`/`issue`/`issue_comment` triggers): `github.actor`, `github.event.*` (`.pull_request.title`, `.pull_request.body`, `.pull_request.head.ref`, `.issue.title`, `.comment.body`, `.label.name`, `.action`), `github.head_ref`. Treat every `github.event.*` field as untrusted. +- **Fix**: bind the expression to an `env:` entry on the step, then reference the shell variable (quoted) inside `run:`. Values passed via `env:` reach the script as data, never as code. +- **Example (correct)**: + + ```yaml + - name: Check for authorized actor + env: + GITHUB_ACTOR: ${{ github.actor }} + run: | + if [[ "$GITHUB_ACTOR" == "lifi-action-bot" ]]; then echo "authorized"; fi + ``` + +- **Anti-pattern (forbidden)**: + + ```yaml + - run: | + if [[ "${{ github.actor }}" == "lifi-action-bot" ]]; then echo "authorized"; fi + ``` + +- `${{ secrets.* }}` and `${{ github.* }}` inside `if:`/`with:`/`env:` values are fine — only direct interpolation into the `run:` script body is the injection sink. + ### Foundry installation ([CONV:FOUNDRY-SETUP]) - **Always install foundry via the project's `./.github/actions/setup-foundry` composite action.** Do not call `foundry-rs/foundry-toolchain` directly from a workflow. From 4656a8a1e128d6fa415686b31cd61c7b053e8af2 Mon Sep 17 00:00:00 2001 From: Daniel Blaecker Date: Thu, 16 Jul 2026 07:40:23 +0700 Subject: [PATCH 2/3] =?UTF-8?q?docs(rules):=20address=20CodeRabbit=20?= =?UTF-8?q?=E2=80=94=20custom=20env=20name,=20scope=20safety=20claim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/rules/500-github-actions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.agents/rules/500-github-actions.md b/.agents/rules/500-github-actions.md index a05e55b48..381060515 100644 --- a/.agents/rules/500-github-actions.md +++ b/.agents/rules/500-github-actions.md @@ -40,15 +40,15 @@ paths: - **Never interpolate `${{ github.* }}` (or any other `${{ ... }}` expression carrying attacker-influenced data) directly into a `run:` shell script.** The expression is substituted into the script body **before** the shell runs, so a crafted value (e.g. a branch name, PR title, commit message, or actor login containing shell metacharacters) executes as code on the runner. - **Highest-risk contexts** (attacker-controllable on `pull_request`/`issue`/`issue_comment` triggers): `github.actor`, `github.event.*` (`.pull_request.title`, `.pull_request.body`, `.pull_request.head.ref`, `.issue.title`, `.comment.body`, `.label.name`, `.action`), `github.head_ref`. Treat every `github.event.*` field as untrusted. -- **Fix**: bind the expression to an `env:` entry on the step, then reference the shell variable (quoted) inside `run:`. Values passed via `env:` reach the script as data, never as code. +- **Fix**: bind the expression to an `env:` entry on the step, then reference the shell variable (quoted) inside `run:`. Values passed via `env:` reach the script as data, never as code. Use a **custom** env-var name — GitHub silently ignores assignments to names it reserves (`GITHUB_*`, `RUNNER_*`), so `env: GITHUB_ACTOR: ...` is dropped and `$GITHUB_ACTOR` falls back to the runner default (works only by coincidence when the values match). - **Example (correct)**: ```yaml - name: Check for authorized actor env: - GITHUB_ACTOR: ${{ github.actor }} + ACTOR_LOGIN: ${{ github.actor }} run: | - if [[ "$GITHUB_ACTOR" == "lifi-action-bot" ]]; then echo "authorized"; fi + if [[ "$ACTOR_LOGIN" == "lifi-action-bot" ]]; then echo "authorized"; fi ``` - **Anti-pattern (forbidden)**: @@ -58,7 +58,7 @@ paths: if [[ "${{ github.actor }}" == "lifi-action-bot" ]]; then echo "authorized"; fi ``` -- `${{ secrets.* }}` and `${{ github.* }}` inside `if:`/`with:`/`env:` values are fine — only direct interpolation into the `run:` script body is the injection sink. +- The `run:` script body is the direct injection sink. `${{ ... }}` in `if:`/`with:`/`env:` is not that sink, but it is not automatically safe: `if:` cannot reference `secrets.*` at all (map to `env:` first), and a value handed to `with:` is only as safe as what the receiving action does with it. Always bind untrusted expressions through `env:` and reference the quoted shell variable in `run:`. ### Foundry installation ([CONV:FOUNDRY-SETUP]) From c576c468adbcdd0083c4d7acf5dbef21e8d3fe40 Mon Sep 17 00:00:00 2001 From: Daniel Blaecker Date: Thu, 16 Jul 2026 07:44:03 +0700 Subject: [PATCH 3/3] docs(rules): set shell: bash in workflow example (CodeRabbit) --- .agents/rules/500-github-actions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.agents/rules/500-github-actions.md b/.agents/rules/500-github-actions.md index 381060515..4f81ed274 100644 --- a/.agents/rules/500-github-actions.md +++ b/.agents/rules/500-github-actions.md @@ -47,6 +47,7 @@ paths: - name: Check for authorized actor env: ACTOR_LOGIN: ${{ github.actor }} + shell: bash run: | if [[ "$ACTOR_LOGIN" == "lifi-action-bot" ]]; then echo "authorized"; fi ```