Back to skill

Security audit

Vercel Staging Workflow

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but its workflow can automatically point a trusted staging URL at the wrong Vercel deployment if the intended commit is not found.

Review before installing. Use a least-privilege Vercel token scoped to the intended project, remove the fallback to latest READY deployment, and make the workflow fail closed unless it can verify the deployment belongs to the triggering commit and main branch.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
references/github-action-template.yml:59
Finding

Unrelated Deployment Can Be Assigned to the Trusted Staging Alias

Content
View full analysis

Vulnerability Details

File Location: references/github-action-template.yml, lines 59–70
Vulnerability Type: Unsafe deployment fallback
Risk Level: High

Vulnerable Code

yaml
        # Fallback to latest READY deployment
        if [ -z "$DEPLOYMENT_ID" ] || [ "$DEPLOYMENT_ID" = "null" ]; then
          echo "Falling back to latest READY deployment..."
          RESPONSE=$(curl -s \
            -H "Authorization: Bearer $VERCEL_TOKEN" \
            "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&limit=5${TEAM_PARAM}")
          DEPLOYMENT_ID=$(echo "$RESPONSE" \
            | jq -r '[(.deployments // [])[] | select(.state=="READY")] | first | .uid // ""')
          DEPLOYMENT_URL=$(echo "$RESPONSE" \
            | jq -r '[(.deployments // [])[] | select(.state=="READY")] | first | .url // ""')
        fi

Technical Analysis

The workflow initially searches for a READY deployment associated with the triggering commit. If no matching deployment is found within the polling period, however, it falls back to the first READY deployment returned for the project.

The fallback does not verify the deployment's commit SHA, source branch, environment, creator, or relationship to the triggering GitHub Actions run. Consequently, it violates the documented guarantee that the stable staging alias points to the latest main deployment. The selected deployment could instead originate from another branch, an older commit, or a contributor-controlled preview deployment.

Attack Path

  1. An attacker or contributor causes a preview deployment from an untrusted branch or commit to reach the READY state.
  2. A push to main triggers the staging-alias workflow.
  3. The corresponding main deployment is delayed, fails, or is not returned among the ten deployments queried during polling.
  4. After the polling period expires, the fallback requests recent project deployments.

...[truncated 916 chars]

Remediation
View remediation

Remediation Suggestions

Remove the unverified fallback and fail closed when the expected deployment cannot be found. The workflow must not update the alias unless it verifies that the deployment belongs to the triggering event.

Recommended hardening measures:

  1. Require an exact match against the complete ${{ github.sha }} rather than accepting an arbitrary READY deployment.
  2. Verify the source branch is main and, where available, validate the repository and deployment environment metadata.
  3. If the expected deployment remains unavailable after the timeout, exit with a nonzero status and leave the existing alias unchanged.
  4. Increase the polling duration if normal deployments can exceed three minutes, rather than weakening deployment identity checks.
  5. Check HTTP status codes and Vercel API error responses by using options such as curl --fail-with-body.
  6. Use a repository-scoped, least-privilege Vercel credential capable only of managing the intended project and alias.
  7. Consider triggering alias assignment from a verified deployment-completion event instead of inferring completion by selecting from recent deployments.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (4)

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/github-action-template.yml (reported line 49)May include surrounding context.

yaml
echo "Check $i/18..."
            RESPONSE=$(curl -s \
              -H "Authorization: Bearer $VERCEL_TOKEN" \
              "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&limit=10${TEAM_PARAM}")

            DEPLOYMENT_ID=$(echo "$RESPONSE" \
              | jq -r --arg sha "$COMMIT_SHA" \

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/github-action-template.yml (reported line 70)May include surrounding context.

yaml
echo "Check $i/18..."
            RESPONSE=$(curl -s \
              -H "Authorization: Bearer $VERCEL_TOKEN" \
              "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&limit=10${TEAM_PARAM}")

            DEPLOYMENT_ID=$(echo "$RESPONSE" \
              | jq -r --arg sha "$COMMIT_SHA" \

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/github-action-template.yml (reported line 84)May include surrounding context.

yaml
echo "Check $i/18..."
            RESPONSE=$(curl -s \
              -H "Authorization: Bearer $VERCEL_TOKEN" \
              "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&limit=10${TEAM_PARAM}")

            DEPLOYMENT_ID=$(echo "$RESPONSE" \
              | jq -r --arg sha "$COMMIT_SHA" \

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/github-action-template.yml (reported line 89)May include surrounding context.

yaml
ALIAS_URL="${ALIAS_URL}?teamId=${VERCEL_TEAM_ID}"
          fi

          curl -s -X POST \
            -H "Authorization: Bearer $VERCEL_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"alias\":\"$STAGING_ALIAS\"}" \

Static analysis

No suspicious patterns detected.