Skip to content

Harden workflow credentials#3095

Open
koxudaxi wants to merge 4 commits intomainfrom
harden-workflow-credentials
Open

Harden workflow credentials#3095
koxudaxi wants to merge 4 commits intomainfrom
harden-workflow-credentials

Conversation

@koxudaxi
Copy link
Copy Markdown
Owner

@koxudaxi koxudaxi commented Apr 21, 2026

Summary by CodeRabbit

  • New Features

    • Automatically apply generated fixes from CI for forked pull requests via short-lived patch artifacts.
  • Chores

    • Hardened workflow security by restricting token permissions and avoiding persisted runner credentials.
    • Standardized and upgraded CI actions (CodeQL, Docker, GitHub Script) and tightened push/commit handling for safer automated updates.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 003bdb74-5c45-4422-a5e5-d5941cd4d331

📥 Commits

Reviewing files that changed from the base of the PR and between 15a9487 and 13b218a.

📒 Files selected for processing (1)
  • .github/workflows/docs.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/docs.yaml

📝 Walkthrough

Walkthrough

Adds fork-aware autofix artifact generation to multiple generator workflows and a new autofix-apply workflow that downloads, validates, and applies fork-produced patch artifacts; standardizes checkout to persist-credentials: false, tightens permissions, upgrades several actions, and changes push mechanics to token-authenticated HTTPS across many workflows.

Changes

Cohort / File(s) Summary
Autofix apply workflow
.github/workflows/autofix-apply.yaml
New workflow that listens to upstream workflow_run completions, downloads autofix-patch artifacts from successful fork runs, validates metadata.json, verifies HEAD SHA, applies autofix.patch, commits, and pushes to the fork using secrets.PAT when appropriate.
Generator workflows producing artifacts
.github/workflows/lint.yaml, .github/workflows/cli-docs.yaml, .github/workflows/readme.yaml, .github/workflows/schema-docs.yaml, .github/workflows/llms-txt.yaml
Removed pull_request_target flows; for forked PRs check out head SHA read-only, generate autofix.patch + metadata.json when changes detected, and upload artifact. Same-repo runs/pushes commit and push via PAT-authenticated HTTPS.
Docs/preview and changelog workflows
.github/workflows/docs.yaml, .github/workflows/changelog.yaml
Refactored docs job split (separate preview/comment job), upgraded github-script, and changed changelog push to use PUSH_TOKEN/PAT in HTTPS push URL instead of persisted checkout token.
Permissions & checkout hardening
.github/workflows/codeql.yaml, .github/workflows/codespell.yaml, .github/workflows/codspeed.yaml, .github/workflows/config-types.yaml, .github/workflows/publish.yaml, .github/workflows/release-draft.yaml, .github/workflows/release-notify.yaml, .github/workflows/test.yaml
Set persist-credentials: false on checkouts and introduced/reduced workflow-level job permissions (commonly contents: read); some steps upgraded action versions and tightened job permission scopes.
Publish & Docker action upgrades
.github/workflows/publish.yaml
Upgraded docker-related actions (metadata, login, qemu, buildx, build-push) and checkout usage; adjusted docker metadata tagging pattern.
Other workflow updates
.github/workflows/cli-docs.yaml, .github/workflows/llms-txt.yaml, .github/workflows/readme.yaml, .github/workflows/schema-docs.yaml, .github/workflows/lint.yaml
Consistent removal of pull_request_target triggers/guards, unified fork vs same-repo checkout logic, added early-exit when no staged changes, and standardized authenticated push via PAT for same-repo commits.

Sequence Diagram(s)

sequenceDiagram
    participant Fork as Forked PR Runner
    participant Generator as Generator Workflow (lint/cli/docs/...)
    participant Artifact as Artifact Storage (upstream run)
    participant ApplyWf as autofix-apply Workflow
    participant ForkRepo as Fork Repo (PR head)

    Fork->>Generator: Run workflow on fork PR (checkout head SHA, read-only)
    Generator->>Generator: Generate files, diff against repo
    alt Changes detected
        Generator->>Generator: Create `autofix.patch` and `metadata.json`
        Generator->>Artifact: Upload `autofix-patch` artifact
    end

    Artifact->>ApplyWf: Trigger on upstream workflow_run success (PR event)
    ApplyWf->>Artifact: Download `autofix-patch` artifact
    ApplyWf->>ApplyWf: Parse `metadata.json`, validate workflow name & head_sha
    ApplyWf->>ForkRepo: Checkout PR head branch from fork (using repo info from metadata)
    ApplyWf->>ApplyWf: Verify current HEAD == recorded head_sha
    ApplyWf->>ApplyWf: Apply `autofix.patch`, stage, commit with provided message
    ApplyWf->>ForkRepo: Push commit to fork branch via HTTPS using `secrets.PAT`
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I stitched a patch beneath moonlit logs,

Metadata tucked with careful hops and clogs,
From fork to upstream the carrot-trail winds,
A tiny commit where correctness finds,
Hooray — the rabbit fixed what the code left behind!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Harden workflow credentials' directly and specifically describes the main change across all modified workflows—implementing security hardening through credential persistence controls, token scoping, and authentication mechanisms.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch harden-workflow-credentials

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 21, 2026

Merging this PR will improve performance by 46.64%

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 11 improved benchmarks
⏩ 98 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
WallTime test_perf_stripe_style_pydantic_v2 2 s 1.4 s +42.26%
WallTime test_perf_multiple_files_input 3.7 s 2.6 s +41.13%
WallTime test_perf_all_options_enabled 6.5 s 4.6 s +43.18%
WallTime test_perf_openapi_large 3 s 2 s +46.64%
WallTime test_perf_graphql_style_pydantic_v2 827.6 ms 576 ms +43.68%
WallTime test_perf_large_models_pydantic_v2 3.7 s 2.6 s +44.33%
WallTime test_perf_duplicate_names 1,060.4 ms 762 ms +39.17%
WallTime test_perf_kubernetes_style_pydantic_v2 2.6 s 1.8 s +43.26%
WallTime test_perf_aws_style_openapi_pydantic_v2 1.9 s 1.3 s +43.97%
WallTime test_perf_complex_refs 2.2 s 1.6 s +35.01%
WallTime test_perf_deep_nested 6.1 s 4.4 s +37.64%

Comparing harden-workflow-credentials (13b218a) with main (e91b601)

Open in CodSpeed

Footnotes

  1. 98 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (e91b601) to head (13b218a).

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #3095   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           87        87           
  Lines        18306     18306           
  Branches      2091      2091           
=========================================
  Hits         18306     18306           
Flag Coverage Δ
unittests 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/changelog.yaml (1)

18-71: ⚠️ Potential issue | 🟠 Major

Avoid inline release-tag interpolation while the PAT is available.

Line 70 injects github.event.release.tag_name directly into shell. A crafted tag containing shell syntax can execute in the PAT-bearing step. Pass the tag through env, quote it, fail fast, and skip the credentialed push when there is no commit.

🛡️ Proposed hardening
       - name: Get release info and update CHANGELOG
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          RELEASE_TAG: ${{ github.event.release.tag_name }}
         run: |
           set -euo pipefail
 
-          TAG="${{ github.event.release.tag_name }}"
+          TAG="$RELEASE_TAG"
           DATE=$(gh release view "$TAG" --json publishedAt --jq '.publishedAt | split("T")[0]')
           BODY=$(gh release view "$TAG" --json body --jq '.body // ""')
 
@@
       - name: Commit and push
         env:
           PUSH_TOKEN: ${{ secrets.PAT }}
+          RELEASE_TAG: ${{ github.event.release.tag_name }}
         run: |
+          set -euo pipefail
+          : "${PUSH_TOKEN:?PUSH_TOKEN is required}"
+
           git config user.name "github-actions[bot]"
           git config user.email "github-actions[bot]@users.noreply.github.com"
           git add CHANGELOG.md
-          git diff --cached --quiet || git commit -m "docs: update CHANGELOG.md for ${{ github.event.release.tag_name }}"
+          if git diff --cached --quiet; then
+            exit 0
+          fi
+          git commit -m "docs: update CHANGELOG.md for ${RELEASE_TAG}"
           git push "https://x-access-token:${PUSH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:main
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/changelog.yaml around lines 18 - 71, The workflow
currently interpolates github.event.release.tag_name directly into shell
commands, which risks shell injection when the PAT is available; move the tag
into the step env (set TAG: ${{ github.event.release.tag_name }} on the "Get
release info and update CHANGELOG" and "Commit and push" steps), always
reference it as "$TAG" (quoting expansions like in the commit message and any
URLs) to avoid word-splitting, and harden the push step by making the push
conditional: after git add and git diff --cached --quiet, if there are no staged
changes exit successfully (skip the credentialed git push) rather than
interpolating the tag into the push command; keep set -euo pipefail to fail
fast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/autofix-apply.yaml:
- Around line 28-31: The workflow extracts the autofix.patch into the repo
workspace before actions/checkout@v4 which by default runs git clean and deletes
that patch; update the job so the patch is saved to the runner temp directory
(use $RUNNER_TEMP) or set the checkout step (actions/checkout@v4) to clean:
false so the file survives; locate the step that uploads/downloads the artifact
and change its destination to $RUNNER_TEMP or modify the checkout step
configuration to include clean: false (references: GH_TOKEN, RUN_ID,
WORKFLOW_NAME, and the actions/checkout@v4 step where the patch is applied).
- Around line 56-69: The artifact metadata (data dict and keys
data["head_repo"], data["head_ref"], data["head_sha"], data["commit_message"])
must be treated as untrusted: verify that data["workflow"] matches
os.environ["WORKFLOW_NAME"] and additionally validate
head_repo/head_ref/head_sha against the authoritative workflow_run/PR details
(fetch the workflow_run or PR API and compare) before using them to drive
checkout/push; do not inject raw commit_message or refs into shell source—export
validated values into the environment and write only safe, validated values
(e.g., sanitized head_repo, head_ref, head_sha, PR_NUMBER) to GITHUB_OUTPUT, and
deliver multi-line commit_message via a guarded mechanism (e.g., set as an env
var or store in a tempfile) rather than writing unescaped content directly into
the shell-interpolated output.

In @.github/workflows/cli-docs.yaml:
- Around line 105-123: The script currently interpolates
github.event.pull_request.head.ref and
github.event.pull_request.head.repo.full_name directly into the run: body (used
to set TARGET_REPO and TARGET_REF), which allows shell injection; move these
pull-request-derived values into the job env (e.g., PR_HEAD_REF and
PR_HEAD_REPO) alongside PUSH_TOKEN, then in the run block assign
TARGET_REPO="$PR_HEAD_REPO" and TARGET_REF="$PR_HEAD_REF" (use double quotes
around the variable expansions everywhere, including the git push URL and ref)
so the untrusted values are treated as data rather than executed code and the
git push still uses the existing PUSH_TOKEN.

In @.github/workflows/lint.yaml:
- Around line 78-96: Move all interpolated GitHub context values into the
workflow env block and reference them as quoted shell variables to prevent
branch-name injection: define env entries for TARGET_REPO and TARGET_REF (using
github.repository, github.ref_name,
github.event.pull_request.head.repo.full_name and
github.event.pull_request.head.ref as appropriate) alongside PUSH_TOKEN, then
update the shell script to use the quoted variables (e.g., "$TARGET_REPO" and
"$TARGET_REF") when constructing the git push URL and ref; ensure no direct
${...} GitHub context is expanded inside the run script and always quote the
variables to avoid shell metacharacter injection.

In @.github/workflows/llms-txt.yaml:
- Around line 95-113: Move all GitHub context interpolations out of the inline
run script and into the job env so secrets are not exposed in the script scope;
specifically, add env entries for the values currently interpolated in the run
block (github.event_name, github.ref_name,
github.event.pull_request.head.repo.full_name,
github.event.pull_request.head.ref) and then update the run logic that sets
TARGET_REPO and TARGET_REF (the variables used before the git push command) to
reference those env variables instead of using "${{ ... }}" inline; keep using
PUSH_TOKEN from env for the push URL but ensure the git push uses the env-based
TARGET_REPO and TARGET_REF variables.

In @.github/workflows/readme.yaml:
- Around line 91-109: The inline shell uses GitHub contexts directly to set
TARGET_REPO and TARGET_REF which allows shell injection; move the context values
into workflow env variables (e.g., add env entries for PUSH_TOKEN,
PR_TARGET_REPO and PR_TARGET_REF or similar) and then reference those env vars
inside the run script to assign TARGET_REPO and TARGET_REF (replace uses of
github.event.pull_request.head.repo.full_name and
github.event.pull_request.head.ref with the new env vars). Ensure all context
expansions used in the run block are only read from the environment (not
interpolated into the script), and use the existing PUSH_TOKEN env var for the
git push URL.

In @.github/workflows/schema-docs.yaml:
- Around line 95-113: The workflow currently expands github context values
directly inside the shell run block (using github.event.pull_request.head.ref /
.head.repo.full_name) which can lead to shell injection; instead, add
environment variables (e.g., TARGET_REPO and TARGET_REF) at the step level or
export them at the start of the run by assigning the github context to env vars
(using the workflow env mapping) and then reference those shell-safe variables
(TARGET_REPO, TARGET_REF) in the script; update the step so PUSH_TOKEN,
TARGET_REPO and TARGET_REF are provided via env and replace any direct
github.context expansions in the run script with the corresponding shell
variables (TARGET_REPO, TARGET_REF) when building the git push URL.

---

Outside diff comments:
In @.github/workflows/changelog.yaml:
- Around line 18-71: The workflow currently interpolates
github.event.release.tag_name directly into shell commands, which risks shell
injection when the PAT is available; move the tag into the step env (set TAG:
${{ github.event.release.tag_name }} on the "Get release info and update
CHANGELOG" and "Commit and push" steps), always reference it as "$TAG" (quoting
expansions like in the commit message and any URLs) to avoid word-splitting, and
harden the push step by making the push conditional: after git add and git diff
--cached --quiet, if there are no staged changes exit successfully (skip the
credentialed git push) rather than interpolating the tag into the push command;
keep set -euo pipefail to fail fast.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f3d1eb6f-dc23-4d92-ae52-a675ebaea26c

📥 Commits

Reviewing files that changed from the base of the PR and between e91b601 and 15a9487.

📒 Files selected for processing (16)
  • .github/workflows/autofix-apply.yaml
  • .github/workflows/changelog.yaml
  • .github/workflows/cli-docs.yaml
  • .github/workflows/codeql.yaml
  • .github/workflows/codespell.yaml
  • .github/workflows/codspeed.yaml
  • .github/workflows/config-types.yaml
  • .github/workflows/docs.yaml
  • .github/workflows/lint.yaml
  • .github/workflows/llms-txt.yaml
  • .github/workflows/publish.yaml
  • .github/workflows/readme.yaml
  • .github/workflows/release-draft.yaml
  • .github/workflows/release-notify.yaml
  • .github/workflows/schema-docs.yaml
  • .github/workflows/test.yaml

Comment on lines +28 to +31
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, find the workflow file
fd -t f "autofix-apply.yaml"

Repository: koxudaxi/datamodel-code-generator

Length of output: 59


🏁 Script executed:

# Read the entire workflow file to understand the sequence
cat -n ".github/workflows/autofix-apply.yaml"

Repository: koxudaxi/datamodel-code-generator

Length of output: 4864


Store the patch in $RUNNER_TEMP to persist it across the workspace cleanup.

autofix.patch is extracted to the workspace before actions/checkout@v4, which by default runs git clean -ffdx that removes all files. Line 94 will fail when attempting to apply the deleted patch. Move the artifact extraction to $RUNNER_TEMP or add clean: false to the checkout action.

Proposed fix
       - name: Download approved patch artifact
         id: artifact
         env:
           GH_TOKEN: ${{ github.token }}
           RUN_ID: ${{ github.event.workflow_run.id }}
           WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
+          PATCH_DIR: ${{ runner.temp }}/autofix
         run: |
           set -euo pipefail
@@
-          gh api "repos/${{ github.repository }}/actions/artifacts/${ARTIFACT_ID}/zip" > autofix.zip
-          unzip -o autofix.zip
+          mkdir -p "$PATCH_DIR"
+          gh api "repos/${{ github.repository }}/actions/artifacts/${ARTIFACT_ID}/zip" > "$PATCH_DIR/autofix.zip"
+          unzip -o "$PATCH_DIR/autofix.zip" -d "$PATCH_DIR"
           python3 <<'PY'
@@
-          data = json.loads(Path("metadata.json").read_text(encoding="utf-8"))
+          data = json.loads((Path(os.environ["PATCH_DIR"]) / "metadata.json").read_text(encoding="utf-8"))
@@
       - name: Check out PR head branch
         if: steps.artifact.outputs.should_apply == 'true'
         uses: actions/checkout@v4
         with:
           fetch-depth: 0
           ref: ${{ steps.artifact.outputs.head_ref }}
           repository: ${{ steps.artifact.outputs.head_repo }}
           persist-credentials: false
       - name: Apply patch and push
         if: steps.current_head.outputs.is_current == 'true'
         env:
           PUSH_TOKEN: ${{ secrets.PAT }}
+          PATCH_DIR: ${{ runner.temp }}/autofix
         run: |
           set -euo pipefail
-          git apply --index --whitespace=nowarn autofix.patch
+          git apply --index --whitespace=nowarn "$PATCH_DIR/autofix.patch"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/autofix-apply.yaml around lines 28 - 31, The workflow
extracts the autofix.patch into the repo workspace before actions/checkout@v4
which by default runs git clean and deletes that patch; update the job so the
patch is saved to the runner temp directory (use $RUNNER_TEMP) or set the
checkout step (actions/checkout@v4) to clean: false so the file survives; locate
the step that uploads/downloads the artifact and change its destination to
$RUNNER_TEMP or modify the checkout step configuration to include clean: false
(references: GH_TOKEN, RUN_ID, WORKFLOW_NAME, and the actions/checkout@v4 step
where the patch is applied).

Comment on lines +56 to +69
data = json.loads(Path("metadata.json").read_text(encoding="utf-8"))
workflow_name = os.environ["WORKFLOW_NAME"]
if data["workflow"] != workflow_name:
raise SystemExit(f"artifact workflow mismatch: {data['workflow']} != {workflow_name}")
output = Path(os.environ["GITHUB_OUTPUT"])
with output.open("a", encoding="utf-8") as fh:
fh.write("should_apply=true\n")
fh.write(f"pr_number={os.environ['PR_NUMBER']}\n")
fh.write(f"head_repo={data['head_repo']}\n")
fh.write(f"head_ref={data['head_ref']}\n")
fh.write(f"head_sha={data['head_sha']}\n")
fh.write("commit_message<<EOF\n")
fh.write(data["commit_message"])
fh.write("\nEOF\n")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Treat artifact metadata as untrusted before checkout and push.

The artifact comes from a fork-triggered run, but head_repo, head_ref, head_sha, and commit_message are used to drive checkout/push and are also interpolated into shell source. Validate the metadata against workflow_run/PR data, then pass outputs into shell via env.

🛡️ Proposed hardening
       - name: Download approved patch artifact
         id: artifact
         env:
           GH_TOKEN: ${{ github.token }}
           RUN_ID: ${{ github.event.workflow_run.id }}
           WORKFLOW_NAME: ${{ github.event.workflow_run.name }}
+          EXPECTED_HEAD_REPO: ${{ github.event.workflow_run.head_repository.full_name }}
+          EXPECTED_HEAD_REF: ${{ github.event.workflow_run.head_branch }}
+          EXPECTED_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
         run: |
@@
           data = json.loads(Path("metadata.json").read_text(encoding="utf-8"))
           workflow_name = os.environ["WORKFLOW_NAME"]
           if data["workflow"] != workflow_name:
               raise SystemExit(f"artifact workflow mismatch: {data['workflow']} != {workflow_name}")
+          expected = {
+              "head_repo": os.environ["EXPECTED_HEAD_REPO"],
+              "head_ref": os.environ["EXPECTED_HEAD_REF"],
+              "head_sha": os.environ["EXPECTED_HEAD_SHA"],
+          }
+          for key, value in expected.items():
+              if data.get(key) != value:
+                  raise SystemExit(f"artifact {key} mismatch: {data.get(key)} != {value}")
           output = Path(os.environ["GITHUB_OUTPUT"])
           with output.open("a", encoding="utf-8") as fh:
               fh.write("should_apply=true\n")
               fh.write(f"pr_number={os.environ['PR_NUMBER']}\n")
-              fh.write(f"head_repo={data['head_repo']}\n")
-              fh.write(f"head_ref={data['head_ref']}\n")
-              fh.write(f"head_sha={data['head_sha']}\n")
+              fh.write(f"head_repo={expected['head_repo']}\n")
+              fh.write(f"head_ref={expected['head_ref']}\n")
+              fh.write(f"head_sha={expected['head_sha']}\n")
               fh.write("commit_message<<EOF\n")
               fh.write(data["commit_message"])
               fh.write("\nEOF\n")
@@
       - name: Skip stale patch
         id: current_head
         if: steps.artifact.outputs.should_apply == 'true'
+        env:
+          EXPECTED_HEAD_SHA: ${{ steps.artifact.outputs.head_sha }}
         run: |
-          if [ "$(git rev-parse HEAD)" != "${{ steps.artifact.outputs.head_sha }}" ]; then
+          if [ "$(git rev-parse HEAD)" != "$EXPECTED_HEAD_SHA" ]; then
             echo "is_current=false" >> "$GITHUB_OUTPUT"
             exit 0
           fi
@@
       - name: Apply patch and push
         if: steps.current_head.outputs.is_current == 'true'
         env:
           PUSH_TOKEN: ${{ secrets.PAT }}
+          COMMIT_MESSAGE: ${{ steps.artifact.outputs.commit_message }}
+          HEAD_REPO: ${{ steps.artifact.outputs.head_repo }}
+          HEAD_REF: ${{ steps.artifact.outputs.head_ref }}
         run: |
@@
-          git commit -m "${{ steps.artifact.outputs.commit_message }}"
-          git push "https://x-access-token:${PUSH_TOKEN}@github.com/${{ steps.artifact.outputs.head_repo }}.git" "HEAD:${{ steps.artifact.outputs.head_ref }}"
+          git commit -m "$COMMIT_MESSAGE"
+          git push "https://x-access-token:${PUSH_TOKEN}@github.com/${HEAD_REPO}.git" "HEAD:${HEAD_REF}"

Also applies to: 83-83, 100-101

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/autofix-apply.yaml around lines 56 - 69, The artifact
metadata (data dict and keys data["head_repo"], data["head_ref"],
data["head_sha"], data["commit_message"]) must be treated as untrusted: verify
that data["workflow"] matches os.environ["WORKFLOW_NAME"] and additionally
validate head_repo/head_ref/head_sha against the authoritative workflow_run/PR
details (fetch the workflow_run or PR API and compare) before using them to
drive checkout/push; do not inject raw commit_message or refs into shell
source—export validated values into the environment and write only safe,
validated values (e.g., sanitized head_repo, head_ref, head_sha, PR_NUMBER) to
GITHUB_OUTPUT, and deliver multi-line commit_message via a guarded mechanism
(e.g., set as an env var or store in a tempfile) rather than writing unescaped
content directly into the shell-interpolated output.

Comment on lines +105 to +123
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/cli-reference/ src/datamodel_code_generator/prompt_data.py
git diff --staged --quiet || git commit -m "docs: update CLI reference documentation and prompt data

🤖 Generated by GitHub Actions"
git push
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update CLI reference documentation and prompt data"
if [ "${{ github.event_name }}" = "push" ]; then
TARGET_REPO="${{ github.repository }}"
TARGET_REF="${{ github.ref_name }}"
else
TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
TARGET_REF="${{ github.event.pull_request.head.ref }}"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's locate and examine the cli-docs.yaml file
fd -t f "cli-docs.yaml" .github/workflows/

Repository: koxudaxi/datamodel-code-generator

Length of output: 108


🏁 Script executed:

# Read the file around the flagged lines
head -n 130 .github/workflows/cli-docs.yaml | tail -n 40

Repository: koxudaxi/datamodel-code-generator

Length of output: 1816


🏁 Script executed:

# Let's also check if schema-docs.yaml exists and has similar code
fd -t f "schema-docs.yaml" .github/workflows/

Repository: koxudaxi/datamodel-code-generator

Length of output: 111


🏁 Script executed:

# Let's read the exact lines around 105-123 in cli-docs.yaml
sed -n '100,130p' .github/workflows/cli-docs.yaml | cat -n

Repository: koxudaxi/datamodel-code-generator

Length of output: 1377


🏁 Script executed:

# Let's also check the schema-docs.yaml to see if it has similar code
cat .github/workflows/schema-docs.yaml | grep -A 20 "Commit and push if changed"

Repository: koxudaxi/datamodel-code-generator

Length of output: 1058


🏁 Script executed:

# Let's understand GitHub Actions security: check if there are any recent security advisories
# about context variable interpolation in shell scripts
web_search
GitHub Actions shell injection context variables security risk

Repository: koxudaxi/datamodel-code-generator

Length of output: 172


🌐 Web query:

GitHub Actions shell injection context variable untrusted input security

💡 Result:

GitHub Actions workflows are vulnerable to shell injection when untrusted input from GitHub context variables (like github.event.issue.title, github.event.pull_request.title, branch names) is directly interpolated into 'run' steps using ${{ }} expressions. GitHub evaluates these expressions before the shell runs, substituting values into the script, allowing attackers to inject commands (e.g., PR title: a"; ls $GITHUB_WORKSPACE). Untrusted context fields include those ending in body, title, ref, head_ref, label, name, email, message (e.g., github.event.issue.body, github.head_ref). Branch names like ";echo hello;# can also inject code. This risks secret exfiltration, GITHUB_TOKEN abuse, or runner compromise. Mitigation: Assign untrusted values to env variables first, then reference with shell syntax ($VAR). Vulnerable: run: echo "${{ github.event.issue.title }}" Secure: env: TITLE: ${{ github.event.issue.title }} run: echo "$TITLE" Use actions instead of inline scripts for processing. Set minimal GITHUB_TOKEN permissions (read-only default). Enable code scanning for detection. Avoid pull_request_target with write perms on untrusted code.

Citations:


🌐 Web query:

GitHub Actions github.event.pull_request.head.ref security injection risk

💡 Result:

Yes, there is a significant security injection risk when using github.event.pull_request.head.ref in GitHub Actions workflows. This value represents the source branch name in a pull request, which is fully attacker-controlled, especially in forks. Attackers can craft malicious branch names containing shell metacharacters (e.g., "; curl https://evil.com/script.sh | bash; #) to inject and execute arbitrary commands when interpolated into shell scripts via ${{ github.event.pull_request.head.ref }}. This risk is amplified in pull_request_target workflows, which run with base repository privileges (access to secrets and write permissions), unlike standard pull_request triggers. Official GitHub documentation explicitly lists github.event.pull_request.head.ref (and head_ref) as untrusted input that must not flow directly into executable contexts like run: steps, API calls, or commands. Multiple security advisories and research confirm real-world exploits leading to RCE, secret exfiltration, and repository compromise. Mitigations: - Treat as untrusted: Sanitize or validate before use. - Use environment variables: env: BRANCH: ${{ github.event.pull_request.head.ref }} then reference $BRANCH (prevents shell expansion). - Prefer actions over inline run: scripts for processing. - Avoid pull_request_target unless necessary; use pull_request for untrusted code. - Set minimal permissions: permissions: { contents: read }. - Disable credential persistence in checkouts: persist-credentials: false. - Separate jobs for checkout and sensitive operations.

Citations:


Route pull request branch name through environment variables to prevent shell injection.

Lines 117–122 interpolate github.event.pull_request.head.ref and github.event.pull_request.head.repo.full_name directly into a run: script that has access to PUSH_TOKEN. Both fields are attacker-controlled in pull requests and can contain shell metacharacters (e.g., "; malicious-command; #") to inject arbitrary code.

Move these untrusted values to the env section and reference them as quoted shell variables:

🛡️ Proposed fix
       - name: Commit and push if changed
         if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
         env:
           PUSH_TOKEN: ${{ secrets.PAT }}
+          EVENT_NAME: ${{ github.event_name }}
+          PUSH_REPO: ${{ github.repository }}
+          PUSH_REF: ${{ github.ref_name }}
+          PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+          PR_REF: ${{ github.event.pull_request.head.ref }}
         run: |
@@
-          if [ "${{ github.event_name }}" = "push" ]; then
-            TARGET_REPO="${{ github.repository }}"
-            TARGET_REF="${{ github.ref_name }}"
+          if [ "$EVENT_NAME" = "push" ]; then
+            TARGET_REPO="$PUSH_REPO"
+            TARGET_REF="$PUSH_REF"
           else
-            TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
-            TARGET_REF="${{ github.event.pull_request.head.ref }}"
+            TARGET_REPO="$PR_REPO"
+            TARGET_REF="$PR_REF"
           fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/cli-reference/ src/datamodel_code_generator/prompt_data.py
git diff --staged --quiet || git commit -m "docs: update CLI reference documentation and prompt data
🤖 Generated by GitHub Actions"
git push
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update CLI reference documentation and prompt data"
if [ "${{ github.event_name }}" = "push" ]; then
TARGET_REPO="${{ github.repository }}"
TARGET_REF="${{ github.ref_name }}"
else
TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
TARGET_REF="${{ github.event.pull_request.head.ref }}"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
EVENT_NAME: ${{ github.event_name }}
PUSH_REPO: ${{ github.repository }}
PUSH_REF: ${{ github.ref_name }}
PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
PR_REF: ${{ github.event.pull_request.head.ref }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/cli-reference/ src/datamodel_code_generator/prompt_data.py
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update CLI reference documentation and prompt data"
if [ "$EVENT_NAME" = "push" ]; then
TARGET_REPO="$PUSH_REPO"
TARGET_REF="$PUSH_REF"
else
TARGET_REPO="$PR_REPO"
TARGET_REF="$PR_REF"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
🧰 Tools
🪛 actionlint (1.7.12)

[error] 108-108: "github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/cli-docs.yaml around lines 105 - 123, The script currently
interpolates github.event.pull_request.head.ref and
github.event.pull_request.head.repo.full_name directly into the run: body (used
to set TARGET_REPO and TARGET_REF), which allows shell injection; move these
pull-request-derived values into the job env (e.g., PR_HEAD_REF and
PR_HEAD_REPO) alongside PUSH_TOKEN, then in the run block assign
TARGET_REPO="$PR_HEAD_REPO" and TARGET_REF="$PR_HEAD_REF" (use double quotes
around the variable expansions everywhere, including the git push URL and ref)
so the untrusted values are treated as data rather than executed code and the
git push still uses the existing PUSH_TOKEN.

Comment on lines +78 to +96
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
if git diff --staged --quiet; then
exit 0
fi
git commit -m "style: auto-fix by prek"
if [ "${{ github.event_name }}" = "push" ]; then
TARGET_REPO="${{ github.repository }}"
TARGET_REF="${{ github.ref_name }}"
else
TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
TARGET_REF="${{ github.event.pull_request.head.ref }}"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/lint.yaml | sed -n '78,96p'

Repository: koxudaxi/datamodel-code-generator

Length of output: 1109


🌐 Web query:

GitHub Actions secrets security inline script variable interpolation best practices

💡 Result:

Best practices for securing GitHub Actions secrets in inline scripts with variable interpolation: 1. Pass secrets as environment variables instead of direct interpolation in run steps. Direct use of ${{ secrets.MY_SECRET }} in inline scripts allows shell command injection if the secret value is compromised or malicious, as GitHub expressions expand before shell execution. Example (vulnerable): - run: echo "${{ secrets.SSH_KEY }}" > keyfile Safe: - env: SSH_KEY: ${{ secrets.SSH_KEY }} run: echo "$SSH_KEY" > keyfile 2. Avoid passing secrets on command lines; use environment variables, STDIN, or tool-specific secure methods to prevent exposure via ps output or logs. 3. Never echo, log, or print secrets; GitHub redacts known secrets from logs but fails for transformed/structured data, generated values, or obfuscated output. 4. Use ::add-mask::VALUE for non-secret sensitive data to enable redaction. 5. Avoid structured data (JSON, etc.) as secrets; redaction requires exact matches. 6. Register generated sensitive values (e.g., JWTs) as masked secrets. 7. Use GitHub Environments for production secrets with required approvals. 8. Set GITHUB_TOKEN to read-only by default and use least-privilege secrets. 9. Pin third-party actions to commits/SHAs, not tags. 10. Audit workflows: review logs after runs, enable code scanning for injection risks. These prevent OICUITSF (Octopus Inject Command Unless Interpolated To Shell First) attacks where untrusted input or poisoned secrets lead to RCE and secret exfiltration.

Citations:


🌐 Web query:

GitHub Actions context variables shell script injection risk

💡 Result:

Yes, there is a significant shell script injection risk when using GitHub Actions context variables, particularly those from the github context that contain user-controlled data, directly in run steps or inline scripts. Context variables like github.event.pull_request.title, github.event.issue.body, github.event.pull_request.body, branch names (github.ref_name), and others ending in .body, .title, .message, .name, .ref, .head_ref, .default_branch, or .email are potentially untrusted because attackers can control them (e.g., via malicious PR titles or branch names). The risk arises because GitHub evaluates ${{ }} expressions before the shell script executes, substituting the values directly into the script. If the value contains shell metacharacters (e.g., ", ;, $), it can break out of the intended command and execute arbitrary code on the runner. Example vulnerable workflow from official docs: - name: Check PR title run: | title="${{ github.event.pull_request.title }}" if [[ $title =~ ^octocat ]]; then echo "PR title starts with 'octocat'" An attacker sets PR title to a"; ls $GITHUB_WORKSPACE;#, resulting in execution of ls. Mitigations (prioritized from official GitHub docs): 1. Use a JavaScript/TypeScript action to process untrusted input instead of inline shell scripts. 2. For shell scripts: Assign context values to env variables first, then reference them with $VAR (prevents injection as shell evaluates after substitution): - name: Safe process env: TITLE: ${{ github.event.pull_request.title }} run: echo "$TITLE" 3. Validate/sanitize inputs (e.g., regex checks, whitelists) after assigning to env. 4. Avoid direct interpolation in run: steps. 5. Use least-privilege GITHUB_TOKEN permissions. 6. For self-hosted runners, higher risk due to potential access to internal resources. This is a workflow author responsibility; GitHub treats these contexts as untrusted input by design.

Citations:


Move GitHub context values to environment variables before shell execution to prevent branch-name injection attacks.

Lines 89–94 interpolate untrusted GitHub context values directly into the shell script. Attackers can control branch names (line 94: github.event.pull_request.head.ref) and inject shell metacharacters (e.g., main"; echo $PUSH_TOKEN; #) to exfiltrate the PAT token. Assign context values to env variables in the env block first, then reference them as quoted shell variables.

🛡️ Proposed hardening
       - name: Commit and push auto-fixes
         if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
         env:
           PUSH_TOKEN: ${{ secrets.PAT }}
+          EVENT_NAME: ${{ github.event_name }}
+          PUSH_REPO: ${{ github.repository }}
+          PUSH_REF: ${{ github.ref_name }}
+          PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+          PR_REF: ${{ github.event.pull_request.head.ref }}
         run: |
@@
-          if [ "${{ github.event_name }}" = "push" ]; then
-            TARGET_REPO="${{ github.repository }}"
-            TARGET_REF="${{ github.ref_name }}"
+          if [ "$EVENT_NAME" = "push" ]; then
+            TARGET_REPO="$PUSH_REPO"
+            TARGET_REF="$PUSH_REF"
           else
-            TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
-            TARGET_REF="${{ github.event.pull_request.head.ref }}"
+            TARGET_REPO="$PR_REPO"
+            TARGET_REF="$PR_REF"
           fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
if git diff --staged --quiet; then
exit 0
fi
git commit -m "style: auto-fix by prek"
if [ "${{ github.event_name }}" = "push" ]; then
TARGET_REPO="${{ github.repository }}"
TARGET_REF="${{ github.ref_name }}"
else
TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
TARGET_REF="${{ github.event.pull_request.head.ref }}"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
EVENT_NAME: ${{ github.event_name }}
PUSH_REPO: ${{ github.repository }}
PUSH_REF: ${{ github.ref_name }}
PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
PR_REF: ${{ github.event.pull_request.head.ref }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
if git diff --staged --quiet; then
exit 0
fi
git commit -m "style: auto-fix by prek"
if [ "$EVENT_NAME" = "push" ]; then
TARGET_REPO="$PUSH_REPO"
TARGET_REF="$PUSH_REF"
else
TARGET_REPO="$PR_REPO"
TARGET_REF="$PR_REF"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
🧰 Tools
🪛 actionlint (1.7.12)

[error] 81-81: "github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/lint.yaml around lines 78 - 96, Move all interpolated
GitHub context values into the workflow env block and reference them as quoted
shell variables to prevent branch-name injection: define env entries for
TARGET_REPO and TARGET_REF (using github.repository, github.ref_name,
github.event.pull_request.head.repo.full_name and
github.event.pull_request.head.ref as appropriate) alongside PUSH_TOKEN, then
update the shell script to use the quoted variables (e.g., "$TARGET_REPO" and
"$TARGET_REF") when constructing the git push URL and ref; ensure no direct
${...} GitHub context is expanded inside the run script and always quote the
variables to avoid shell metacharacter injection.

Comment on lines +95 to +113
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/llms.txt docs/llms-full.txt
git diff --staged --quiet || git commit -m "docs: update llms.txt files

Generated by GitHub Actions"
git push
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update llms.txt files"
if [ "${{ github.event_name }}" = "push" ]; then
TARGET_REPO="${{ github.repository }}"
TARGET_REF="${{ github.ref_name }}"
else
TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
TARGET_REF="${{ github.event.pull_request.head.ref }}"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read the relevant lines
cd /tmp && git ls-files | grep -i "llms-txt.yaml"

Repository: koxudaxi/datamodel-code-generator

Length of output: 145


🏁 Script executed:

# Read the file to verify the exact content and context
if [ -f ".github/workflows/llms-txt.yaml" ]; then
  cat -n .github/workflows/llms-txt.yaml | sed -n '90,120p'
fi

Repository: koxudaxi/datamodel-code-generator

Length of output: 1317


🏁 Script executed:

# Also check the full step to understand the complete context
if [ -f ".github/workflows/llms-txt.yaml" ]; then
  cat -n .github/workflows/llms-txt.yaml | sed -n '85,125p'
fi

Repository: koxudaxi/datamodel-code-generator

Length of output: 1537


Move GitHub context values to env to reduce injection surface when secrets are in scope.

Lines 106–111 interpolate untrusted GitHub context values (PR branch name, repo) directly into the inline shell script while PUSH_TOKEN containing secrets.PAT is in scope. Although current quoting limits immediate risk, this pattern should be hardened by moving all GitHub context to the env: section and referencing them as shell variables.

🛡️ Proposed hardening
       - name: Commit and push if changed
         if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
         env:
           PUSH_TOKEN: ${{ secrets.PAT }}
+          EVENT_NAME: ${{ github.event_name }}
+          PUSH_REPO: ${{ github.repository }}
+          PUSH_REF: ${{ github.ref_name }}
+          PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+          PR_REF: ${{ github.event.pull_request.head.ref }}
         run: |
@@
-          if [ "${{ github.event_name }}" = "push" ]; then
-            TARGET_REPO="${{ github.repository }}"
-            TARGET_REF="${{ github.ref_name }}"
+          if [ "$EVENT_NAME" = "push" ]; then
+            TARGET_REPO="$PUSH_REPO"
+            TARGET_REF="$PUSH_REF"
           else
-            TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
-            TARGET_REF="${{ github.event.pull_request.head.ref }}"
+            TARGET_REPO="$PR_REPO"
+            TARGET_REF="$PR_REF"
           fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/llms.txt docs/llms-full.txt
git diff --staged --quiet || git commit -m "docs: update llms.txt files
Generated by GitHub Actions"
git push
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update llms.txt files"
if [ "${{ github.event_name }}" = "push" ]; then
TARGET_REPO="${{ github.repository }}"
TARGET_REF="${{ github.ref_name }}"
else
TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
TARGET_REF="${{ github.event.pull_request.head.ref }}"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
EVENT_NAME: ${{ github.event_name }}
PUSH_REPO: ${{ github.repository }}
PUSH_REF: ${{ github.ref_name }}
PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
PR_REF: ${{ github.event.pull_request.head.ref }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/llms.txt docs/llms-full.txt
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update llms.txt files"
if [ "$EVENT_NAME" = "push" ]; then
TARGET_REPO="$PUSH_REPO"
TARGET_REF="$PUSH_REF"
else
TARGET_REPO="$PR_REPO"
TARGET_REF="$PR_REF"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
🧰 Tools
🪛 actionlint (1.7.12)

[error] 98-98: "github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/llms-txt.yaml around lines 95 - 113, Move all GitHub
context interpolations out of the inline run script and into the job env so
secrets are not exposed in the script scope; specifically, add env entries for
the values currently interpolated in the run block (github.event_name,
github.ref_name, github.event.pull_request.head.repo.full_name,
github.event.pull_request.head.ref) and then update the run logic that sets
TARGET_REPO and TARGET_REF (the variables used before the git push command) to
reference those env variables instead of using "${{ ... }}" inline; keep using
PUSH_TOKEN from env for the push URL but ensure the git push uses the env-based
TARGET_REPO and TARGET_REF variables.

Comment on lines +91 to +109
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add README.md docs/index.md
git diff --staged --quiet || git commit -m "docs: update command help in README

🤖 Generated by GitHub Actions"
git push
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update command help in README"
if [ "${{ github.event_name }}" = "push" ]; then
TARGET_REPO="${{ github.repository }}"
TARGET_REF="${{ github.ref_name }}"
else
TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
TARGET_REF="${{ github.event.pull_request.head.ref }}"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "\.github/workflows"

Repository: koxudaxi/datamodel-code-generator

Length of output: 596


🏁 Script executed:

cat -n .github/workflows/readme.yaml | sed -n '85,115p'

Repository: koxudaxi/datamodel-code-generator

Length of output: 1353


🏁 Script executed:

wc -l .github/workflows/readme.yaml

Repository: koxudaxi/datamodel-code-generator

Length of output: 110


🏁 Script executed:

rg "github.event.pull_request" .github/workflows/ -A 2 -B 2

Repository: koxudaxi/datamodel-code-generator

Length of output: 20287


🏁 Script executed:

rg '\$\{\{' .github/workflows/readme.yaml | head -20

Repository: koxudaxi/datamodel-code-generator

Length of output: 966


Move GitHub context values to environment variables to prevent shell injection.

Lines 102–107 expand PR event context directly into an inline shell script. A malicious branch name containing shell metacharacters (e.g., backticks or $()) would execute arbitrary commands. Pass all context values via env instead and reference them as shell variables.

🛡️ Proposed hardening
       - name: Commit and push if changed
         if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
         env:
           PUSH_TOKEN: ${{ secrets.PAT }}
+          EVENT_NAME: ${{ github.event_name }}
+          PUSH_REPO: ${{ github.repository }}
+          PUSH_REF: ${{ github.ref_name }}
+          PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+          PR_REF: ${{ github.event.pull_request.head.ref }}
         run: |
@@
-          if [ "${{ github.event_name }}" = "push" ]; then
-            TARGET_REPO="${{ github.repository }}"
-            TARGET_REF="${{ github.ref_name }}"
+          if [ "$EVENT_NAME" = "push" ]; then
+            TARGET_REPO="$PUSH_REPO"
+            TARGET_REF="$PUSH_REF"
           else
-            TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
-            TARGET_REF="${{ github.event.pull_request.head.ref }}"
+            TARGET_REPO="$PR_REPO"
+            TARGET_REF="$PR_REF"
           fi
🧰 Tools
🪛 actionlint (1.7.12)

[error] 94-94: "github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/readme.yaml around lines 91 - 109, The inline shell uses
GitHub contexts directly to set TARGET_REPO and TARGET_REF which allows shell
injection; move the context values into workflow env variables (e.g., add env
entries for PUSH_TOKEN, PR_TARGET_REPO and PR_TARGET_REF or similar) and then
reference those env vars inside the run script to assign TARGET_REPO and
TARGET_REF (replace uses of github.event.pull_request.head.repo.full_name and
github.event.pull_request.head.ref with the new env vars). Ensure all context
expansions used in the run block are only read from the environment (not
interpolated into the script), and use the existing PUSH_TOKEN env var for the
git push URL.

Comment on lines +95 to +113
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/supported_formats.md
if ! git diff --staged --quiet; then
git commit -m "docs: update schema feature documentation

🤖 Generated by GitHub Actions"
git push
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update schema feature documentation"
if [ "${{ github.event_name }}" = "push" ]; then
TARGET_REPO="${{ github.repository }}"
TARGET_REF="${{ github.ref_name }}"
else
TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
TARGET_REF="${{ github.event.pull_request.head.ref }}"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/schema-docs.yaml | sed -n '90,120p'

Repository: koxudaxi/datamodel-code-generator

Length of output: 1324


🏁 Script executed:

# Check GitHub's branch name restrictions
# Branch names can contain special characters that may break shell quoting
python3 << 'PYTHON_SCRIPT'
import re

# GitHub allows a broad range of characters in branch names
# Special characters that could break shell quoting:
dangerous_chars = [
    '"',     # double quote - breaks double-quoted strings
    "'",     # single quote - breaks single-quoted strings  
    '$',     # dollar sign - allows variable expansion
    '`',     # backtick - allows command substitution
    '\\',    # backslash - escape character
    '!',     # exclamation - history expansion in bash
    '\n',    # newline - command injection
]

print("Characters that can break shell quoting in branch names:")
print("- Double quotes, single quotes, backticks")
print("- Dollar signs (variable expansion)")
print("- Backslashes (escape sequences)")
print("- Newlines (multi-line injection)")
print("\nExamples of dangerous branch names:")
examples = [
    'feature/$(echo pwned)',
    'bugfix/`whoami`',
    'test/"; echo injected; echo "',
    "feature/'; malicious_cmd; echo '",
]
for ex in examples:
    print(f"  {ex}")

print("\n--- Proposed fix analysis ---")
print("Moving to env vars: PR_REF=${{ github.event.pull_request.head.ref }}")
print("Then using: TARGET_REF=\"$PR_REF\"")
print("In command: git push \"...@github.com/${TARGET_REPO}.git\" \"HEAD:${TARGET_REF}\"")
print("\nThis contains the variable in double quotes, but:")
print("- $ expansion still happens inside \"...\"")
print("- Backticks and $() still execute if present")
print("- Proper fix would need: \"HEAD:${TARGET_REF//\"/}\" or similar escaping")
PYTHON_SCRIPT

Repository: koxudaxi/datamodel-code-generator

Length of output: 764


🏁 Script executed:

git show HEAD:.github/workflows/schema-docs.yaml 2>/dev/null | sed -n '95,113p' || echo "File not in HEAD, checking working directory"

Repository: koxudaxi/datamodel-code-generator

Length of output: 1017


🏁 Script executed:

# Verify the actual git push command structure and how variables are used
sed -n '95,113p' .github/workflows/schema-docs.yaml

Repository: koxudaxi/datamodel-code-generator

Length of output: 1017


Pass PR refs through environment variables before shell use.

Line 111 expands github.event.pull_request.head.ref directly into the shell script with PUSH_TOKEN available. Although the step guards against fork PRs, same-repo branch names remain user-controlled and can contain shell metacharacters. Move context values to environment variables and reference them safely as shell variables.

🛡️ Proposed hardening
       - name: Commit and push if changed
         if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
         env:
           PUSH_TOKEN: ${{ secrets.PAT }}
+          EVENT_NAME: ${{ github.event_name }}
+          PUSH_REPO: ${{ github.repository }}
+          PUSH_REF: ${{ github.ref_name }}
+          PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
+          PR_REF: ${{ github.event.pull_request.head.ref }}
         run: |
@@
-          if [ "${{ github.event_name }}" = "push" ]; then
-            TARGET_REPO="${{ github.repository }}"
-            TARGET_REF="${{ github.ref_name }}"
+          if [ "$EVENT_NAME" = "push" ]; then
+            TARGET_REPO="$PUSH_REPO"
+            TARGET_REF="$PUSH_REF"
           else
-            TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
-            TARGET_REF="${{ github.event.pull_request.head.ref }}"
+            TARGET_REPO="$PR_REPO"
+            TARGET_REF="$PR_REF"
           fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/supported_formats.md
if ! git diff --staged --quiet; then
git commit -m "docs: update schema feature documentation
🤖 Generated by GitHub Actions"
git push
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update schema feature documentation"
if [ "${{ github.event_name }}" = "push" ]; then
TARGET_REPO="${{ github.repository }}"
TARGET_REF="${{ github.ref_name }}"
else
TARGET_REPO="${{ github.event.pull_request.head.repo.full_name }}"
TARGET_REF="${{ github.event.pull_request.head.ref }}"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
env:
PUSH_TOKEN: ${{ secrets.PAT }}
EVENT_NAME: ${{ github.event_name }}
PUSH_REPO: ${{ github.repository }}
PUSH_REF: ${{ github.ref_name }}
PR_REPO: ${{ github.event.pull_request.head.repo.full_name }}
PR_REF: ${{ github.event.pull_request.head.ref }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/supported_formats.md
if git diff --staged --quiet; then
exit 0
fi
git commit -m "docs: update schema feature documentation"
if [ "$EVENT_NAME" = "push" ]; then
TARGET_REPO="$PUSH_REPO"
TARGET_REF="$PUSH_REF"
else
TARGET_REPO="$PR_REPO"
TARGET_REF="$PR_REF"
fi
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${TARGET_REPO}.git" "HEAD:${TARGET_REF}"
🧰 Tools
🪛 actionlint (1.7.12)

[error] 98-98: "github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details

(expression)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/schema-docs.yaml around lines 95 - 113, The workflow
currently expands github context values directly inside the shell run block
(using github.event.pull_request.head.ref / .head.repo.full_name) which can lead
to shell injection; instead, add environment variables (e.g., TARGET_REPO and
TARGET_REF) at the step level or export them at the start of the run by
assigning the github context to env vars (using the workflow env mapping) and
then reference those shell-safe variables (TARGET_REPO, TARGET_REF) in the
script; update the step so PUSH_TOKEN, TARGET_REPO and TARGET_REF are provided
via env and replace any direct github.context expansions in the run script with
the corresponding shell variables (TARGET_REPO, TARGET_REF) when building the
git push URL.

@github-actions
Copy link
Copy Markdown
Contributor

📚 Docs Preview: https://pr-3095.datamodel-code-generator.pages.dev

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.

1 participant