Skip to content

Harden workflows against script injection#210

Merged
jeremy merged 4 commits intomainfrom
prompt-injection
Mar 7, 2026
Merged

Harden workflows against script injection#210
jeremy merged 4 commits intomainfrom
prompt-injection

Conversation

@jeremy
Copy link
Copy Markdown
Member

@jeremy jeremy commented Mar 7, 2026

Summary

  • Fix script injection via AI model output in labeler workflow (use response-file, structured JSON output, env vars for PR numbers)
  • Fix $GITHUB_ENV injection in release changelog step (use file-based approach)
  • Replace secrets:inherit with explicit secret passthrough
  • Move git config tokens and ref names out of shell interpolation
  • Narrow permissions from workflow-level to per-job
  • Add secrets declaration to security.yml for workflow_call

Context

Security audit of GitHub Actions workflows identified script injection vectors where ${{ }} expressions are interpolated by the Actions runner before bash executes. This PR eliminates all identified injection paths.


Summary by cubic

Hardens GitHub Actions to prevent script and token injection across labeler, release, test, and security workflows. Uses response files with strict JSON, env vars, and least-privilege permissions.

  • Bug Fixes

    • Labeler: parse strict JSON (json_schema) from a response file with higher token headroom; pass PR via env; normalize the label before applying.
    • Release: replace $GITHUB_ENV heredoc with file/output changelog; default RELEASE_CHANGELOG to "" to avoid template errors.
    • Remove inline shell interpolation of tokens, ref_name, and run_id in all workflows.
  • Refactors

    • Permissions: scope per job; keep top-level security-events: write for the reusable security workflow.
    • Secrets: replace inherit with explicit passthrough; declare workflow_call secrets in security.yml.
    • GoReleaser: split install/run; standardize git config to use an env TOKEN.

Written for commit 79f1b1e. Summary will update on new commits.

jeremy added 3 commits March 7, 2026 07:54
Move PR number from ${{ }} interpolation to env vars, switch from
inline response to response-file for model output, and add structured
JSON output schema to the classifier prompt so label extraction no
longer depends on parsing free-text model responses.
- Replace $GITHUB_ENV heredoc injection vector with file-based changelog
  passed through step outputs and env vars
- Replace secrets:inherit with explicit secret passthrough to security.yml
- Move ref_name, run_id, and git config tokens out of shell interpolation
  into env vars
- Narrow permissions from workflow-level to per-job
- Add secrets declaration to security.yml for workflow_call
Replace ${{ steps.app-token.outputs.token }} in git config run commands
with env var references across all 7 jobs to prevent token exposure
through shell interpolation.
Copilot AI review requested due to automatic review settings March 7, 2026 15:55
@github-actions github-actions bot added ci CI/CD workflows bug Something isn't working labels Mar 7, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-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.

3 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/release.yml">

<violation number="1" location=".github/workflows/release.yml:280">
P1: `RELEASE_CHANGELOG` is no longer set when the AI changelog step fails or produces no file. GoReleaser templates use `missingkey=error`, so accessing `.Env.RELEASE_CHANGELOG` when the variable is unset will fail the release. The old code had an `else` branch that set the variable to empty — add an unconditional default export before the conditional.</violation>
</file>

<file name=".github/workflows/ai-labeler.yml">

<violation number="1" location=".github/workflows/ai-labeler.yml:85">
P1: When jq succeeds (structured JSON path), the label is not lowercased or whitespace-stripped, so a model response like `{"label": "Bug"}` won't match the `case` branches and the label is silently skipped. The `tr` normalization only runs in the fallback (jq-failure) path due to `||` precedence.</violation>
</file>

<file name=".github/prompts/classify-pr.prompt.yml">

<violation number="1" location=".github/prompts/classify-pr.prompt.yml:38">
P1: `maxCompletionTokens: 10` is too tight for the new JSON response format. A single-word response like `bug` needed ~2 tokens; `{"label": "documentation"}` needs ~7-10. If the budget is exhausted mid-object, the structured-output response is truncated and the downstream step will fail to parse it. Bump to ~25 to leave headroom.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread .github/workflows/release.yml
Comment thread .github/workflows/ai-labeler.yml Outdated
Comment thread .github/prompts/classify-pr.prompt.yml
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Hardens GitHub Actions workflows against script/workflow-command injection by reducing ${{ }} interpolation inside shell, switching to response-file/JSON parsing for AI outputs, and tightening secrets/permissions handling.

Changes:

  • Move sensitive values (tokens, PR numbers, ref/run IDs) into env: and reference them in shell to avoid runner-time interpolation.
  • Switch AI labeler to structured JSON output via response files + JSON schema.
  • Replace $GITHUB_ENV changelog injection with a file/output-based flow and split GoReleaser install vs run.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
.github/workflows/test.yml Avoid direct ${{ }} interpolation in git config command by using env vars.
.github/workflows/security.yml Declare workflow_call secret input and avoid interpolation in git config.
.github/workflows/release.yml Remove workflow-level permissions, pass secrets explicitly, harden changelog + ref/run interpolation, and adjust GoReleaser invocation.
.github/workflows/ai-labeler.yml Use PR number via env, parse model output from response-file (JSON-first), avoid direct ${{ }} in shell.
.github/prompts/classify-pr.prompt.yml Require JSON output and add a strict JSON schema response format for classification.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/security.yml
Comment thread .github/workflows/release.yml
Comment thread .github/workflows/release.yml
…ssions, and changelog default

- Split label extraction into two lines so tr normalization always runs
- Standardize responseFormat to match detect-breaking.prompt.yml shape
- Bump maxCompletionTokens from 10 to 25 for JSON output headroom
- Restore top-level permissions so reusable security workflow gets security-events:write
- Default RELEASE_CHANGELOG to empty string so GoReleaser template doesn't fail on missing key
@jeremy jeremy merged commit 977f93e into main Mar 7, 2026
22 checks passed
@jeremy jeremy deleted the prompt-injection branch March 7, 2026 19:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ci CI/CD workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants