From 7898b15d8a67bff224a0da5a2b473f62e1889216 Mon Sep 17 00:00:00 2001 From: Mykhailo Shevchuk Date: Tue, 28 Jul 2026 21:56:21 +0300 Subject: [PATCH] CI: require the AI usage disclosure and finish the shell-injection hardening (#1054) PR template: port the "No AI assistance" option and the "tick exactly one" note from all-the-plugins (xMasterX/all-the-plugins#249) so the two repos agree, and add a check that fails the PR unless exactly one of the three boxes is ticked. The check needs no token and checks out nothing (`permissions: {}`), so plain `pull_request` is safe for forks; `edited` is in the trigger list so fixing the description re-runs it. pr-build.yml: #1052 bound github.head_ref/ref_name to env, but the branch name still reached a `run:` block through steps.vars.outputs.ref/.dist, which the "Assemble PR report" step interpolated. A branch named `x$(id)` still executed there. Bind those outputs to env as well, and reduce SAFE_REF to [A-Za-z0-9._-] instead of only replacing '/', so the outputs are inert for every consumer. Artifact names are unchanged for ordinary branch names. Pin the two third-party actions to commits: dessant/lock-threads runs with issues+pull-requests write, and a mutable tag can be repointed at any time. --- .github/pull_request_template.md | 3 ++ .github/workflows/codeql.yml | 3 +- .github/workflows/lock-threads.yml | 4 +- .github/workflows/pr-build.yml | 16 ++++++-- .github/workflows/pr-template-check.yml | 52 +++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/pr-template-check.yml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 234fbe7c5..ce264adba 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,6 +14,9 @@ # AI usage disclosure (Fill this out): +Tick exactly one - leaving all three blank reads as "not filled out" rather than "no AI". + +- [ ] No AI assistance. - [ ] Partially AI assisted (clarify below which code was AI assisted and briefly explain what it does). - [ ] Fully AI generated (explain what all the generated code does in moderate detail). diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0fbafc002..3a5706a73 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -91,7 +91,8 @@ jobs: upload: "failure-only" # disable the upload here - we will upload in a different action - name: Filter dirs for SARIF - uses: advanced-security/filter-sarif@v1 + # Third-party action pinned to a commit; mutable tags can be repointed. + uses: advanced-security/filter-sarif@f3b8118a9349d88f7b1c0c488476411145b6270d # v1 if: ${{ success() }} with: # filter out all test files unless they contain a sql-injection vulnerability diff --git a/.github/workflows/lock-threads.yml b/.github/workflows/lock-threads.yml index bee83abc2..303f3a73d 100644 --- a/.github/workflows/lock-threads.yml +++ b/.github/workflows/lock-threads.yml @@ -30,7 +30,9 @@ jobs: name: Lock inactive closed threads runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v6 + # Third-party action pinned to a commit: this job holds issues+PRs write, and a + # mutable tag can be repointed at any time. Bump deliberately, not implicitly. + - uses: dessant/lock-threads@89ae32b08ed1a541efecbab17912962a5e38981c # v6 with: issue-inactive-days: "14" pr-inactive-days: "14" diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index e65a2aa0d..e5e1b17b9 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -70,7 +70,10 @@ jobs: REF="$REF_NAME" fi SHORT="${SHA:0:8}" - SAFE_REF="${REF//\//-}" # branch name, '/' -> '-' for artifact names + # Branch names may contain '$', '(', ')' and backticks, and these outputs are + # consumed by later steps, so reduce to a charset that is inert everywhere + # rather than only replacing '/' for artifact names. + SAFE_REF="$(printf '%s' "$REF" | tr -c 'A-Za-z0-9._-' '-')" echo "sha8=$SHORT" >> "$GITHUB_OUTPUT" echo "ref=$SAFE_REF" >> "$GITHUB_OUTPUT" echo "dist=${SAFE_REF}-${SHORT}" >> "$GITHUB_OUTPUT" @@ -135,13 +138,18 @@ jobs: shell: bash env: RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + # Derived from the branch name, so bind rather than interpolate (see #1052). + REF: ${{ steps.vars.outputs.ref }} + SHA8: ${{ steps.vars.outputs.sha8 }} + DIST: ${{ steps.vars.outputs.dist }} + BUILD_OUTCOME: ${{ steps.build.outcome }} run: | { echo "" - echo "## 🤖 Build & analyze — \`${{ steps.vars.outputs.ref }}\` @ \`${{ steps.vars.outputs.sha8 }}\`" + echo "## 🤖 Build & analyze — \`$REF\` @ \`$SHA8\`" echo "" - if [ "${{ steps.build.outcome }}" = "success" ]; then - echo "✅ **Firmware built** — artifact \`unleashed-fw-${{ steps.vars.outputs.dist }}\` ([run]($RUN_URL))" + if [ "$BUILD_OUTCOME" = "success" ]; then + echo "✅ **Firmware built** — artifact \`unleashed-fw-$DIST\` ([run]($RUN_URL))" else echo "❌ **Firmware build failed** — see the [run log]($RUN_URL)" fi diff --git a/.github/workflows/pr-template-check.yml b/.github/workflows/pr-template-check.yml new file mode 100644 index 000000000..0bc468c59 --- /dev/null +++ b/.github/workflows/pr-template-check.yml @@ -0,0 +1,52 @@ +# Enforce the AI usage disclosure from .github/pull_request_template.md: exactly one of +# the three boxes must be ticked. Leaving all three blank reads as "not filled out" +# rather than "no AI", which is the whole point of having a "No AI assistance" option. +# +# Security: the PR body is attacker-controlled free text, so it is bound to an env var +# and never interpolated into the script — the same mistake fixed in #1052. This job +# checks out nothing and needs no token: `permissions: {}` denies every scope, which is +# what makes plain `pull_request` (rather than `pull_request_target`) safe for fork PRs. +# +# `edited` is in the trigger list so a contributor only has to fix the description for +# the check to re-run — no push, no re-run button. + +name: PR template check + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +permissions: {} + +concurrency: + group: pr-template-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + ai-disclosure: + name: AI usage disclosure + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Exactly one disclosure box ticked + shell: bash + env: + BODY: ${{ github.event.pull_request.body }} + run: | + # Tolerates '-' or '*' bullets, [x] or [X], and the CRLF line endings the web + # editor produces. An empty body yields 0 matches and fails with the same message. + ticked=$(printf '%s\n' "$BODY" | + grep -ciE '^[[:space:]]*[-*][[:space:]]*\[x\][[:space:]]*(No AI assistance|Partially AI assisted|Fully AI generated)' || + true) + + if [ "$ticked" = "1" ]; then + echo "AI usage disclosure: OK." + exit 0 + fi + + if [ "$ticked" = "0" ]; then + echo "::error title=AI usage disclosure not filled out::Tick exactly one box under 'AI usage disclosure' in the PR description - 'No AI assistance', 'Partially AI assisted' or 'Fully AI generated'. Leaving all three blank is treated as not filled out, not as 'no AI'. Editing the description re-runs this check." + else + echo "::error title=AI usage disclosure ambiguous::${ticked} boxes are ticked under 'AI usage disclosure' - tick exactly one. Editing the description re-runs this check." + fi + exit 1