ci(vercel): comment the webapp preview URL on the PR#227
Conversation
Capture the Vercel preview URL from the deploy step output and post it as a sticky PR comment (updated in place on each redeploy), so reviewers don't have to dig it out of the Deploy-Preview job log. Gated to PR / preview context only — no comment on production/main deploys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe workflow gains ChangesVercel PR Preview Comment
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Reviewed cafcfee: adds a sticky preview-URL comment step to the rainix-vercel reusable — captures the vercel CLI preview URL and posts/updates ONE sticky comment per PR, gated to PR/preview context (no comment on production), new sticky-comment action SHA-pinned. YAML-valid, purely additive. (Callers must grant pull-requests: write to see the comment — small raindex follow-up.) LGTM. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/rainix-vercel.yaml:
- Around line 127-133: The workflow step injects user inputs directly into the
shell command using GitHub template expression syntax (${{ inputs.production &&
'--prod' || '' }} and ${{ inputs.deploy-path }}), which allows malicious inputs
to break out of quotes and inject arbitrary commands. Add an env section to the
step that captures these inputs as environment variables, then replace the
dangerous template expressions in the shell command with safe shell variable
references using native syntax (e.g., $PRODUCTION_FLAG and $DEPLOY_PATH). This
ensures inputs are safely passed through environment variables rather than being
evaluated as part of the command string construction.
🪄 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: 6072ba34-c2fc-4a4e-a4f3-b6a206d41b26
📒 Files selected for processing (1)
.github/workflows/rainix-vercel.yaml
| run: | | ||
| "$VERCEL_BIN" deploy --prebuilt ${{ inputs.production && '--prod' || '' }} --token="$VERCEL_TOKEN" "${{ inputs.deploy-path }}" | ||
| # `vercel deploy` prints the bare deployment URL to stdout and the | ||
| # human-readable "Preview:"/"Inspect:" lines to stderr. Capture all of | ||
| # it (tee back to the log so the URLs stay visible there) and parse the | ||
| # URLs robustly from the combined output. | ||
| deploy_log="$(mktemp)" | ||
| "$VERCEL_BIN" deploy --prebuilt ${{ inputs.production && '--prod' || '' }} --token="$VERCEL_TOKEN" "${{ inputs.deploy-path }}" 2>&1 | tee "$deploy_log" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd /tmp && find . -name "rainix-vercel.yaml" 2>/dev/null || echo "Not in /tmp, checking root"Repository: rainlanguage/rainix
Length of output: 45
🏁 Script executed:
git ls-files | grep -E "rainix-vercel|workflows.*yaml"Repository: rainlanguage/rainix
Length of output: 665
🏁 Script executed:
cat -n .github/workflows/rainix-vercel.yaml 2>/dev/null | sed -n '120,140p'Repository: rainlanguage/rainix
Length of output: 1447
🌐 Web query:
GitHub Actions template interpolation security ${{ }} in run shell script command injection
💡 Result:
Using the ${{ }} template interpolation syntax directly inside a GitHub Actions run shell script command is a critical security risk that can lead to arbitrary code execution [1][2][3]. The vulnerability occurs because GitHub Actions resolves expressions inside ${{ }} before the workflow step executes [4][5][6]. When these expressions contain untrusted, user-controlled input (such as issue titles, pull request bodies, or labels), the content is substituted directly into the shell script being generated [1][4][7]. An attacker can craft malicious input that breaks out of any surrounding quotes or command structures to execute arbitrary shell commands on the runner [1][5][6]. Key Security Findings: - Untrusted Contexts: Any data originating from an external source (e.g., github.event.issue.title, github.event.pull_request.head_ref) should be treated as untrusted and potentially malicious [1][8]. - Preprocessing Behavior: The shell script is generated with the interpolated values already present. Consequently, standard shell-level escaping or quoting within the run block is often insufficient to prevent injection because the malicious payload is already part of the command structure before the shell even begins execution [4][5]. - Impact: Successfully exploiting this vulnerability allows an attacker to execute code with the permissions of the workflow runner, potentially exfiltrating sensitive secrets, repository tokens, or modifying the repository [5][3][7]. Recommended Mitigation: The industry-standard best practice to prevent this vulnerability is to never use ${{ }} directly inside run commands [2][4]. Instead, map the untrusted input to an intermediate environment variable and reference that variable using the native syntax of the shell (e.g.,
Citations:
- 1: https://github.com/github/docs/blob/main/content/actions/concepts/security/script-injections.md
- 2: https://github.blog/security/supply-chain-security/four-tips-to-keep-your-github-actions-workflows-secure/
- 3: https://codeql.github.com/codeql-query-help/actions/actions-code-injection-critical/
- 4: https://cloud.hacktricks.wiki/en/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.html
- 5: https://sebastion.dev/intelligence/2026-03-21-poc-github-actions-shell-injection-via-unsanitized-issue-metadata
- 6: https://docs.github.com/en/actions/concepts/security/script-injections
- 7: https://github.blog/security/vulnerability-research/how-to-catch-github-actions-workflow-injections-before-attackers-do/
- 8: https://docs.github.com/en/enterprise-server@2.22/actions/security-guides/security-hardening-for-github-actions
- 9: https://github.com/github/docs/blob/962a1c8dccb8c0f66548b324e5b921b5e4fbc3d6/content/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions.md
- 10: https://docs.github.com/enterprise-server@3.1/actions/security-guides/security-hardening-for-github-actions
🌐 Web query:
GitHub Actions inputs property escape quoting in shell run block security
💡 Result:
Using GitHub Actions inputs or other user-controlled context in a shell run block creates a severe risk of shell command injection [1][2][3]. This occurs because GitHub Actions evaluates expressions inside ${{ }} syntax and substitutes them directly into the shell script before it is executed [2][4]. An attacker providing malicious input can escape the intended command string and execute arbitrary code with the workflow's permissions [2][4][3]. To secure your workflows, follow these defensive practices: 1. Use Environment Variables for Indirection: Never use the ${{ }} expression syntax directly in a run block for untrusted input [1][5]. Instead, assign the input to an environment variable in the env section of the step, and reference that variable inside the shell script using native shell syntax (e.g., "$VAR" in Bash) [1][5][4][3]. Because the expression engine never touches the shell command string, the input is treated as a literal value rather than executable code [5][3]. Example of safe usage: - env: USER_INPUT: ${{ github.event.inputs.user_input }} run: | echo "$USER_INPUT" 2. Quote Variables and Use Delimiters: Always wrap environment variables in double quotes to prevent word splitting [1][6]. Additionally, when passing inputs as arguments to commands, use the end-of-options marker (--) where supported (e.g., git diff -- "$VAR") to prevent the input from being misinterpreted as a command-line option (argument injection) [6]. 3. Validate and Sanitize Inputs: Treat all user-controlled data (e.g., pull request titles, issue bodies, workflow inputs) as untrusted [1][2]. Whenever possible, validate inputs against an allowlist (e.g., regex pattern matching) before using them [4][3][6]. 4. Limit Token Permissions: Reduce the blast radius of a potential compromise by using granular permissions for the GITHUB_TOKEN [5][4]. Avoid granting broad write or administrative access if the job only requires read-only operations [5]. By adopting these patterns, you treat workflow inputs as data rather than code, which is the standard defensive posture for preventing injection vulnerabilities [2][5].
Citations:
- 1: https://github.blog/security/supply-chain-security/four-tips-to-keep-your-github-actions-workflows-secure/
- 2: https://github.com/github/docs/blob/main/content/actions/concepts/security/script-injections.md
- 3: https://actsense.dev/vulnerabilities/shell_injection/
- 4: GHSA-3g6g-gq4r-xjm9
- 5: https://codeql.github.com/codeql-query-help/actions/actions-code-injection-critical/
- 6: https://sisaku-security.github.io/lint/docs/rules/argumentinjection/
Avoid ${{ }} interpolation directly in shell command construction (Line 133).
Line 133 injects workflow inputs directly into the shell command via ${{ inputs.production && '--prod' || '' }} and ${{ inputs.deploy-path }}. GitHub evaluates template expressions before shell execution; a malicious input can break out of quotes and inject arbitrary commands. Move caller inputs to the env section and reference them with native shell syntax ($VAR) instead.
Suggested hardening diff
- name: Deploy to Vercel
id: deploy
shell: bash --noprofile --norc -euo pipefail {0}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
+ DEPLOY_PATH: ${{ inputs.deploy-path }}
+ IS_PRODUCTION: ${{ inputs.production }}
run: |
# `vercel deploy` prints the bare deployment URL to stdout and the
# human-readable "Preview:"/"Inspect:" lines to stderr. Capture all of
# it (tee back to the log so the URLs stay visible there) and parse the
# URLs robustly from the combined output.
deploy_log="$(mktemp)"
- "$VERCEL_BIN" deploy --prebuilt ${{ inputs.production && '--prod' || '' }} --token="$VERCEL_TOKEN" "${{ inputs.deploy-path }}" 2>&1 | tee "$deploy_log"
+ prod_arg=()
+ if [ "$IS_PRODUCTION" = "true" ]; then
+ prod_arg+=(--prod)
+ fi
+ "$VERCEL_BIN" deploy --prebuilt "${prod_arg[@]}" --token="$VERCEL_TOKEN" "$DEPLOY_PATH" 2>&1 | tee "$deploy_log"🧰 Tools
🪛 zizmor (1.25.2)
[error] 133-133: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/rainix-vercel.yaml around lines 127 - 133, The workflow
step injects user inputs directly into the shell command using GitHub template
expression syntax (${{ inputs.production && '--prod' || '' }} and ${{
inputs.deploy-path }}), which allows malicious inputs to break out of quotes and
inject arbitrary commands. Add an env section to the step that captures these
inputs as environment variables, then replace the dangerous template expressions
in the shell command with safe shell variable references using native syntax
(e.g., $PRODUCTION_FLAG and $DEPLOY_PATH). This ensures inputs are safely passed
through environment variables rather than being evaluated as part of the command
string construction.
Source: Linters/SAST tools
|
@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment: S/M/L PR Classification Guidelines:This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed. Small (S)Characteristics:
Review Effort: Would have taken 5-10 minutes Examples:
Medium (M)Characteristics:
Review Effort: Would have taken 15-30 minutes Examples:
Large (L)Characteristics:
Review Effort: Would have taken 45+ minutes Examples:
Additional Factors to ConsiderWhen deciding between sizes, also consider:
Notes:
|
|
Tip For best results, initiate chat on the files or code changes. SIZE=S |
Reviewers currently have to dig the Vercel preview URL out of the Deploy to Vercel job log of the
rainix-vercelreusable workflow. Because that reusable uses the Vercel CLI directly (not the Vercel GitHub App), nothing surfaces the URL onto the PR.This change:
id: deploy). The Vercel CLI printsPreview: https://…vercel.app/Inspect: https://vercel.com/…lines plus the bare deployment URL; weteethe output (so the URLs stay in the log too) and parse the preview URL robustly, preferring thePreview:line and falling back to the last bare*.vercel.appURL. The Inspect URL is captured too.marocchino/sticky-pull-request-comment(pinned to a commit SHA). It uses a stableheaderkeyed on the deploy path, so re-deploys update the same comment in place instead of spamming a new one each run. Body:🔗 Webapp preview: <url>, the Inspect URL, and the deployed commit SHA.pull-requests: writeto the job (with a note that callers of this reusable must also grant it). The comment step is gated to PR/preview context (github.event_name == 'pull_request'and!inputs.production) and skipped when no URL was captured — so no comment on production / main pushes, only previews.End-to-end behavior fully confirms only when this runs on a real consumer PR (raindex etc.), since the reusable isn't exercised by rainix's own CI; the URL-capture and sticky-comment logic is gated and idempotent by construction.
🤖 Generated with Claude Code
Summary by CodeRabbit