Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions .github/workflows/deploy-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Deploy Preview

on:
workflow_run:
workflows: ['Preview Site']
types: [completed]

concurrency:
group: deploy-preview-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: true

permissions:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: cancel-in-progress: true on a deploy workflow can leave a broken Vercel preview if a second push arrives while vercel deploy is mid-flight -- the runner is killed after the upload has started but before it completes, and Vercel may surface a stale or errored deployment. deploy-site.yml uses cancel-in-progress: false for exactly this reason. Deploy workflows should queue rather than cancel.

contents: read
actions: read
pull-requests: write

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: (non-blocking) contents: read is listed but this workflow has no actions/checkout step and reads no repo contents. actions/download-artifact with run-id uses actions: read (already present). The existing workflow_run workflows in this repo (spellcheck-comment.yml, link-check-comment.yml) omit contents: read and work correctly.


jobs:
deploy:
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Download prebuilt output
uses: actions/download-artifact@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -e yml -e yaml .github/workflows -x rg -n 'uses:\s+[^#]+@(?:v[0-9]+|main|master|latest)\b' {}

Repository: Comfy-Org/workflow_templates

Length of output: 540


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
fd --type f --hidden --glob '.github/workflows/*' . | sed 's#^\./##' | sort

printf '\n== deploy-preview.yml ==\n'
nl -ba .github/workflows/deploy-preview.yml | sed -n '1,220p'

printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re
for path in sorted(Path('.github/workflows').rglob('*.[yY][aA][mM][lL]')) + sorted(Path('.github/workflows').rglob('*.yml')):
    text = path.read_text()
    for i, line in enumerate(text.splitlines(), 1):
        m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
        if m:
            ref = m.group(2)
            if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
                print(f"{path}:{i}:{m.group(1)}@{ref}")
PY

Repository: Comfy-Org/workflow_templates

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
fd --type f --hidden --glob '.github/workflows/*' . | sed 's#^\./##' | sort

printf '\n== deploy-preview.yml ==\n'
nl -ba .github/workflows/deploy-preview.yml | sed -n '1,220p'

printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re
for path in sorted(Path('.github/workflows').rglob('*.[yY][aA][mM][lL]')) + sorted(Path('.github/workflows').rglob('*.yml')):
    text = path.read_text()
    for i, line in enumerate(text.splitlines(), 1):
        m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
        if m:
            ref = m.group(2)
            if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
                print(f"{path}:{i}:{m.group(1)}@{ref}")
PY

Repository: Comfy-Org/workflow_templates

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== deploy-preview.yml ==\n'
sed -n '1,220p' .github/workflows/deploy-preview.yml | cat -n

printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re

workflow_dir = Path('.github/workflows')
for path in sorted([p for p in workflow_dir.rglob('*') if p.suffix.lower() in {'.yml', '.yaml'}]):
    for i, line in enumerate(path.read_text().splitlines(), 1):
        m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
        if m:
            ref = m.group(2)
            if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
                print(f"{path}:{i}:{m.group(1)}@{ref}")
PY

Repository: Comfy-Org/workflow_templates

Length of output: 10443


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== deploy-preview.yml ==\n'
sed -n '1,220p' .github/workflows/deploy-preview.yml | cat -n

printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re

workflow_dir = Path('.github/workflows')
for path in sorted([p for p in workflow_dir.rglob('*') if p.suffix.lower() in {'.yml', '.yaml'}]):
    for i, line in enumerate(path.read_text().splitlines(), 1):
        m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
        if m:
            ref = m.group(2)
            if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
                print(f"{path}:{i}:{m.group(1)}@{ref}")
PY

Repository: Comfy-Org/workflow_templates

Length of output: 10443


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== deploy-preview.yml ==\n'
sed -n '1,220p' .github/workflows/deploy-preview.yml | cat -n

printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re

workflow_dir = Path('.github/workflows')
for path in sorted([p for p in workflow_dir.rglob('*') if p.suffix.lower() in {'.yml', '.yaml'}]):
    for i, line in enumerate(path.read_text().splitlines(), 1):
        m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
        if m:
            ref = m.group(2)
            if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
                print(f"{path}:{i}:{m.group(1)}@{ref}")
PY

Repository: Comfy-Org/workflow_templates

Length of output: 10443


Pin the workflow actions

actions/download-artifact@v4 and marocchino/sticky-pull-request-comment@v2 are mutable tags. Pin both to full commit SHAs before this privileged deploy runs—no tag-drift gremlins.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 25-25: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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/deploy-preview.yml at line 25, The deploy preview workflow
currently references mutable action tags, so pin the actions used in this job to
full commit SHAs instead of version tags. Update the `actions/download-artifact`
and `marocchino/sticky-pull-request-comment` references in the workflow to fixed
commit hashes so the privileged deploy flow always runs against immutable action
versions.

Source: Linters/SAST tools

with:
name: vercel-preview-output
path: artifact
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Resolve PR number
id: pr
run: |
number="$(tr -d '[:space:]' < artifact/preview-meta/pr-number)"
if [ -z "$number" ]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: (non-blocking) tr -d '[:space:]' strips whitespace but does not validate the result is a non-negative integer before writing to GITHUB_OUTPUT and passing to gh pr view. In practice github.event.pull_request.number is always a positive integer, but a defensive check costs nothing: if [[ ! "$number" =~ ^[0-9]+$ ]]; then echo "Invalid PR number" >&2; exit 1; fi

echo "No PR number in artifact; nothing to deploy."
echo "found=false" >> "$GITHUB_OUTPUT"
else
echo "found=true" >> "$GITHUB_OUTPUT"
echo "number=$number" >> "$GITHUB_OUTPUT"
Comment on lines +35 to +41

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Validate the artifact PR number before using it in shell templates.

artifact/preview-meta/pr-number crosses from the untrusted build artifact into a privileged workflow_run. Without numeric validation, a crafted value can be emitted as an output and expanded into the shell at Lines 50 and 55, bypassing the label gate or running unintended commands.

Suggested change
       - name: Resolve PR number
         id: pr
         run: |
           number="$(tr -d '[:space:]' < artifact/preview-meta/pr-number)"
-          if [ -z "$number" ]; then
-            echo "No PR number in artifact; nothing to deploy."
+          if [[ ! "$number" =~ ^[0-9]+$ ]]; then
+            echo "Invalid PR number in artifact; nothing to deploy." >&2
             echo "found=false" >> "$GITHUB_OUTPUT"
           else
             echo "found=true" >> "$GITHUB_OUTPUT"
             echo "number=$number" >> "$GITHUB_OUTPUT"
           fi
@@
         env:
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          PR_NUMBER: ${{ steps.pr.outputs.number }}
+          REPOSITORY: ${{ github.repository }}
         run: |
-          labels="$(gh pr view "${{ steps.pr.outputs.number }}" --repo "${{ github.repository }}" --json labels --jq '.labels[].name')"
+          labels="$(gh pr view "$PR_NUMBER" --repo "$REPOSITORY" --json labels --jq '.labels[].name')"
           if printf '%s\n' "$labels" | grep -qx 'preview'; then
             echo "approved=true" >> "$GITHUB_OUTPUT"
           else
             echo "approved=false" >> "$GITHUB_OUTPUT"
-            echo "PR #${{ steps.pr.outputs.number }} has no 'preview' label — skipping deploy."
+            echo "PR #$PR_NUMBER has no 'preview' label — skipping deploy."
           fi

Also applies to: 50-55

🤖 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/deploy-preview.yml around lines 35 - 41, The preview
deployment workflow is passing an untrusted artifact value from
artifact/preview-meta/pr-number directly into GITHUB_OUTPUT, which can later be
expanded in shell templates; validate the value in the artifact-handling step
before setting number/found. In the workflow job that reads the PR number,
ensure the value is strictly numeric (for example, using a pattern check) and
only then emit it; otherwise mark the artifact invalid and stop before the later
shell usage in the deployment steps that reference the PR number.

Source: Linters/SAST tools

fi

- name: Check for preview label
id: gate
if: steps.pr.outputs.found == 'true'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: label approval can be consumed by a later, unreviewed commit. Sequence: (1) maintainer adds preview label to commit A; (2) fork author immediately pushes commit B; (3) Preview Site runs on commit B and uploads a new artifact; (4) this workflow fires, checks the label -- still present -- and deploys commit B's content. The label granted for commit A now covers commit B without maintainer re-review.

Two options: (a) add a pull_request: [synchronize] workflow that strips the preview label on every new push, forcing re-approval per commit; or (b) record the approved SHA when labeling and verify github.event.workflow_run.head_sha matches at deploy time.

env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
labels="$(gh pr view "${{ steps.pr.outputs.number }}" --repo "${{ github.repository }}" --json labels --jq '.labels[].name')"
if printf '%s\n' "$labels" | grep -qx 'preview'; then
echo "approved=true" >> "$GITHUB_OUTPUT"
else
echo "approved=false" >> "$GITHUB_OUTPUT"
echo "PR #${{ steps.pr.outputs.number }} has no 'preview' label — skipping deploy."
fi

- name: Deploy preview to Vercel
id: deploy
if: steps.gate.outputs.approved == 'true'
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
run: |
stdout_file="$RUNNER_TEMP/vercel-preview.stdout"
stderr_file="$RUNNER_TEMP/vercel-preview.stderr"
output_dir="$GITHUB_WORKSPACE/.vercel/output"

if [ ! -d "artifact/site/.vercel/output" ]; then
echo "Prebuilt output missing from artifact" >&2
exit 1
fi

mkdir -p "$GITHUB_WORKSPACE/.vercel"
rm -rf "$output_dir"
cp -R "artifact/site/.vercel/output" "$output_dir"

if npx vercel@latest deploy --cwd "$GITHUB_WORKSPACE" --prebuilt --yes \
--token="$VERCEL_TOKEN" >"$stdout_file" 2>"$stderr_file"; then
Comment on lines +79 to +80

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -e json -e lock -e yml -e yaml -e toml -e js -e ts | xargs rg -n '"vercel"|vercel@|npx vercel'

Repository: Comfy-Org/workflow_templates

Length of output: 480


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== deploy-preview workflow context =="
nl -ba .github/workflows/deploy-preview.yml | sed -n '60,95p'

echo
echo "== repo search for Vercel CLI references =="
rg -n -C 2 'npx vercel|vercel@latest|vercel@' . --glob '!site/pnpm-lock.yaml'

echo
echo "== package manifests mentioning vercel =="
rg -n -C 2 '"vercel"|vercel@' package.json site/package.json pnpm-lock.yaml site/pnpm-lock.yaml .github/workflows/*.yml .github/workflows/**/*.yml

Repository: Comfy-Org/workflow_templates

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== deploy-preview workflow context =="
nl -ba .github/workflows/deploy-preview.yml | sed -n '60,95p'

echo
echo "== repo search for Vercel CLI references =="
rg -n -C 2 'npx vercel|vercel@latest|vercel@' . --glob '!site/pnpm-lock.yaml'

echo
echo "== package manifests mentioning vercel =="
rg -n -C 2 '"vercel"|vercel@' package.json site/package.json pnpm-lock.yaml site/pnpm-lock.yaml .github/workflows/*.yml .github/workflows/**/*.yml

Repository: Comfy-Org/workflow_templates

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== deploy-preview workflow context =="
awk 'NR>=60 && NR<=95 { printf "%4d %s\n", NR, $0 }' .github/workflows/deploy-preview.yml

echo
echo "== repo search for Vercel CLI references =="
rg -n -C 2 'npx vercel|vercel@latest|vercel@' . --glob '!site/pnpm-lock.yaml'

echo
echo "== files that mention vercel =="
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|.*\.yml|.*\.yaml|.*\.json|.*\.js|.*\.ts)$' | xargs rg -n -C 1 '"vercel"|vercel@|npx vercel' --glob '!site/pnpm-lock.yaml'

Repository: Comfy-Org/workflow_templates

Length of output: 1783


Pin the Vercel CLI in .github/workflows/deploy-preview.yml:79

npx vercel@latest can pull mutable code while VERCEL_TOKEN is in scope. Replace it with a pinned version or a reviewed lockfile-backed install; there’s no existing CLI pin in the repo to reuse.

🤖 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/deploy-preview.yml around lines 79 - 80, The
deploy-preview workflow currently uses a mutable Vercel CLI invocation via npx
vercel@latest while VERCEL_TOKEN is available, so update the command in the
deploy step to use a pinned, reviewed Vercel CLI version or a lockfile-backed
installation instead. Keep the change localized to the workflow job that runs
the deploy command, and make sure the pinned reference is explicit so the CLI
version cannot change unexpectedly.

cat "$stderr_file" >&2
cat "$stdout_file"

preview_url="$(tail -n 1 "$stdout_file" | tr -d '\r')"
if [[ ! "$preview_url" =~ ^https?:// ]]; then
echo "Could not parse preview URL from Vercel CLI output" >&2
exit 1
fi

echo "preview-url=$preview_url" >> "$GITHUB_OUTPUT"
else
cat "$stderr_file" >&2
cat "$stdout_file"
exit 1
fi

- name: Comment preview URL
if: steps.deploy.outputs.preview-url != ''
uses: marocchino/sticky-pull-request-comment@v2
with:
number: ${{ steps.pr.outputs.number }}
header: vercel-preview
message: |
🚀 **Preview deployed:** ${{ steps.deploy.outputs.preview-url }}
58 changes: 13 additions & 45 deletions .github/workflows/preview-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: Preview Site

on:
pull_request:
types: [opened, synchronize, reopened, labeled]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: labeled event type is functionally dead when combined with the paths: ['site/**'] filter. GitHub evaluates event types and paths as a conjunction, so adding the preview label to a fork PR that has no site/** changes will not trigger this workflow. The "remove + re-add the label to redeploy" workaround described in the PR description also fails for the same reason -- the only working path for a first deploy is a new push (synchronize) after the label is present.

Since the label gate is correctly enforced at deploy-time in deploy-preview.yml, the labeled type here is not needed. Suggest removing it, or moving label-triggered builds to a separate workflow file without a paths filter.

paths:
- 'site/**'
workflow_dispatch:
Expand All @@ -12,7 +13,6 @@ concurrency:

permissions:
contents: read
pull-requests: write

jobs:
preview:
Expand Down Expand Up @@ -45,50 +45,18 @@ jobs:
PUBLIC_HUB_API_URL: ${{ secrets.HUB_API_URL_PREVIEW }}
PUBLIC_COMFY_CLOUD_URL: ${{ secrets.COMFY_CLOUD_URL_PREVIEW }}

- name: Deploy preview to Vercel
id: deploy
working-directory: site
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
- name: Save PR metadata
run: |
stdout_file="$RUNNER_TEMP/vercel-preview.stdout"
stderr_file="$RUNNER_TEMP/vercel-preview.stderr"
site_output_dir="$GITHUB_WORKSPACE/site/.vercel/output"
root_output_dir="$GITHUB_WORKSPACE/.vercel/output"

if [ ! -d "$site_output_dir" ]; then
echo "Expected prebuilt output in $site_output_dir" >&2
exit 1
fi

mkdir -p "$GITHUB_WORKSPACE/.vercel"
rm -rf "$root_output_dir"
cp -R "$site_output_dir" "$root_output_dir"

if npx vercel@latest deploy --cwd "$GITHUB_WORKSPACE" --prebuilt --yes \
--token="$VERCEL_TOKEN" >"$stdout_file" 2>"$stderr_file"; then
cat "$stderr_file" >&2
cat "$stdout_file"

preview_url="$(tail -n 1 "$stdout_file" | tr -d '\r')"
if [[ ! "$preview_url" =~ ^https?:// ]]; then
echo "Could not parse preview URL from Vercel CLI output" >&2
exit 1
fi

echo "preview-url=$preview_url" >> "$GITHUB_OUTPUT"
else
cat "$stderr_file" >&2
cat "$stdout_file"
exit 1
fi
mkdir -p preview-meta
echo "${{ github.event.pull_request.number }}" > preview-meta/pr-number

- name: Comment preview URL
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
- name: Upload prebuilt output
uses: actions/upload-artifact@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -e yml -e yaml .github/workflows -x rg -n 'uses:\s+[^#]+@(?:v[0-9]+|main|master|latest)\b' {}

Repository: Comfy-Org/workflow_templates

Length of output: 540


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the workflow around the referenced line and list all action refs.
echo '--- preview-site.yml (relevant section) ---'
sed -n '1,220p' .github/workflows/preview-site.yml | cat -n

echo
echo '--- all uses: entries in .github/workflows ---'
rg -n --glob '.github/workflows/**/*.yml' --glob '.github/workflows/**/*.yaml' '^\s*uses:\s*[^#]+' .github/workflows

Repository: Comfy-Org/workflow_templates

Length of output: 11801


Pin actions/upload-artifact to a commit SHA. actions/upload-artifact@v4 is mutable; use an immutable SHA here to avoid supply-chain drift and keep the workflow from going off-key.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 54-54: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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/preview-site.yml at line 54, The workflow step using
actions/upload-artifact is still referenced by a floating version tag, so pin it
to an immutable commit SHA instead. Update the upload-artifact usage in the
preview-site workflow to the SHA-backed reference for the same v4 release,
keeping the rest of the job unchanged.

Source: Linters/SAST tools

with:
header: vercel-preview
message: |
🚀 **Preview deployed:** ${{ steps.deploy.outputs.preview-url }}
name: vercel-preview-output
path: |
site/.vercel/output
preview-meta/pr-number
include-hidden-files: true
if-no-files-found: error
retention-days: 1
Loading