Skip to content
1 change: 1 addition & 0 deletions .github/workflows/agent-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ permissions: {}
on:
# Use `pull_request_target` so we can run this workflow on PRs from forks, as its goal is to assess
# if PR authors are trustworthy. Only reasons on the PR author and does not check out the fork code.
# zizmor: ignore[dangerous-triggers] # required for fork PRs; no fork code is checked out
pull_request_target:
types:
- opened
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Setup Node.js and Install Dependencies
uses: ./.github/actions/setup-node-and-install
Expand Down
13 changes: 8 additions & 5 deletions .github/workflows/danger-js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@
# #
###################################################################################################

name: Danger JS

on:
# We need `pull_request_target` to check external contributor PRs.
# zizmor: ignore[dangerous-triggers] # job checks out base.sha (trusted code), not the PR head; see security warning above
pull_request_target:
types:
- opened
Expand All @@ -47,16 +50,16 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event.number }}
cancel-in-progress: true

permissions:
contents: read
issues: read
pull-requests: write
permissions: {}

name: Danger JS
jobs:
dangerJS:
name: Danger JS
runs-on: ubuntu-latest
permissions:
contents: read
issues: read
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/fork-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ on:
env:
NODE_OPTIONS: '--max_old_space_size=4096'

permissions: {}

jobs:
check:
name: Core Type Checking
if: github.repository_owner != 'storybookjs'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand All @@ -30,6 +34,8 @@ jobs:
name: Core Formatting
if: github.repository_owner != 'storybookjs'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand All @@ -51,6 +57,8 @@ jobs:
runs-on: ${{ matrix.os }}
name: Core Unit Tests, ${{ matrix.os }}
if: github.repository_owner != 'storybookjs'
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/generate-sandboxes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ env:
CLEANUP_SANDBOX_NODE_MODULES: 'true'
NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

permissions: {}

defaults:
run:
working-directory: ./code
Expand All @@ -23,6 +25,7 @@ jobs:
name: Resolve target branches
if: github.repository_owner == 'storybookjs'
runs-on: ubuntu-latest
permissions: {}
outputs:
branches: ${{ steps.set.outputs.branches }}
steps:
Expand All @@ -44,6 +47,8 @@ jobs:
needs: set-branches
if: github.repository_owner == 'storybookjs'
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
Expand All @@ -69,6 +74,7 @@ jobs:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ matrix.branch }}
persist-credentials: false

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e #6.4.0
with:
Expand Down Expand Up @@ -103,7 +109,10 @@ jobs:
# publish sandboxes even if the generation fails, as some sandboxes might have been generated successfully
# when triggered manually, always publish to the `next` branch on the sandboxes repo
if: ${{ !cancelled() }}
run: yarn publish-sandboxes --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT }}@github.com/storybookjs/sandboxes.git --push --branch=${{ github.event_name == 'workflow_dispatch' && 'next' || matrix.branch }}
env:
PAT: ${{ secrets.PAT_STORYBOOK_BOT }}
BRANCH: ${{ github.event_name == 'workflow_dispatch' && 'next' || matrix.branch }}
run: yarn publish-sandboxes --remote="https://storybook-bot:${PAT}@github.com/storybookjs/sandboxes.git" --push --branch="$BRANCH"

- name: Report failure to Discord
if: failure()
Expand Down
46 changes: 35 additions & 11 deletions .github/workflows/handle-release-branches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ name: Handle Release Branches
on:
push:

permissions: {}

jobs:
branch-checks:
if: github.repository_owner == 'storybookjs'
runs-on: ubuntu-latest
permissions: {}
steps:
- id: get-branch
env:
REF: ${{ github.ref }}
run: |
BRANCH=($(echo ${{ github.ref }} | sed -E 's/refs\/heads\///'))
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
BRANCH="${REF#refs/heads/}"
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
outputs:
branch: ${{ steps.get-branch.outputs.branch }}
is-latest-branch: ${{ steps.get-branch.outputs.branch == 'main' }}
Expand All @@ -23,6 +28,8 @@ jobs:
needs: branch-checks
if: ${{ needs.branch-checks.outputs.is-latest-branch == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand All @@ -35,6 +42,8 @@ jobs:
needs: branch-checks
if: ${{ needs.branch-checks.outputs.is-next-branch == 'true' || needs.branch-checks.outputs.is-release-branch == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand All @@ -53,24 +62,32 @@ jobs:
needs: [branch-checks, get-next-release-branch]
if: ${{ needs.branch-checks.outputs.is-next-branch == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Checkout (with creds for later git push)
# zizmor: ignore[artipacked] # git push origin requires persisted credentials
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

- run: |
- env:
TARGET_BRANCH: ${{ needs.get-next-release-branch.outputs.branch }}
SOURCE_BRANCH: ${{ needs.branch-checks.outputs.branch }}
run: |
set +e
REMOTE_BRANCH=$(git branch -r | grep origin/${{ needs.get-next-release-branch.outputs.branch }})
if [[ ! -z $REMOTE_BRANCH ]]; then git push origin --delete ${{ needs.get-next-release-branch.outputs.branch }}; fi
echo 'Pushing branch ${{ needs.get-next-release-branch.outputs.branch }}...'
git push -f origin ${{ needs.branch-checks.outputs.branch }}:${{ needs.get-next-release-branch.outputs.branch }}
REMOTE_BRANCH=$(git branch -r | grep "origin/${TARGET_BRANCH}")
if [[ -n "$REMOTE_BRANCH" ]]; then git push origin --delete "$TARGET_BRANCH"; fi
echo "Pushing branch ${TARGET_BRANCH}..."
git push -f origin "${SOURCE_BRANCH}:${TARGET_BRANCH}"
outputs:
branch: ${{ needs.get-next-release-branch.outputs.branch }}

next-release-branch-check:
if: ${{ always() && github.repository_owner == 'storybookjs' }}
needs: [branch-checks, get-next-release-branch]
runs-on: ubuntu-latest
permissions: {}
steps:
- id: is-next-release-branch
run: |
Expand All @@ -93,10 +110,17 @@ jobs:
needs:
[branch-checks, next-release-branch-check, create-next-release-branch]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- if: ${{ needs.branch-checks.outputs.is-actionable-branch == 'true' && needs.branch-checks.outputs.is-latest-branch == 'false' && needs.next-release-branch-check.outputs.check == 'false' }}
env:
BRANCH: ${{ needs.create-next-release-branch.outputs.branch || needs.branch-checks.outputs.branch }}
FRONTPAGE_TOKEN: ${{ secrets.FRONTPAGE_ACCESS_TOKEN }}
run: |
DISPATCH_PAYLOAD=$(jq -n --arg branch "$BRANCH" \
'{event_type: "request-create-frontpage-branch", client_payload: {branch: $branch}}')
curl -X POST https://api.github.com/repos/storybookjs/frontpage/dispatches \
-H 'Accept: application/vnd.github.v3+json' \
-u ${{ secrets.FRONTPAGE_ACCESS_TOKEN }} \
--data '{"event_type": "request-create-frontpage-branch", "client_payload": { "branch": "${{ needs.create-next-release-branch.outputs.branch || needs.branch-checks.outputs.branch }}" }}'
-H 'Accept: application/vnd.github.v3+json' \
-u "$FRONTPAGE_TOKEN" \
--data "$DISPATCH_PAYLOAD"
Comment on lines +121 to +126
Comment on lines 123 to +126

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

GitHub API authentication curl PAT token format 2025

💡 Result:

For GitHub REST API authentication with curl using a Personal Access Token (PAT), the PAT value is sent in the HTTP Authorization header, typically as either: 1) Authorization: Bearer Example (GitHub docs): curl --request GET --url "https://api.github.com/octocat" --header "Authorization: Bearer YOUR-TOKEN" [1] 2) Authorization: token GitHub docs note that in most cases you can use Authorization: Bearer or Authorization: token to pass a token (but for JWT you must use Bearer). [1] PAT token “format” (the characters/prefix you’ll see) - GitHub personal access tokens use prefixes: ghp_, gho_, ghu_, ghs_, or ghr_ (case-sensitive prefix). [2] - Those prefixes are followed by the token body characters; a Microsoft security definition describes the overall entity as a prefix plus 36 characters composed of a-z/a-z (case-insensitive) and 0-9. [3] Common curl pattern (concise) curl -H "Authorization: Bearer $GITHUB_TOKEN_OR_PAT" \ -H "X-GitHub-Api-Version: " \ "https://api.github.com/" [1] Important related note (avoid quoting token syntax in curl) GitHub’s docs show the token is supplied via the Authorization header; you typically should not put it in the URL path/query. [1] Answering your “2025” point The token mechanism described in GitHub’s REST authentication docs (Authorization header with Bearer/token) is the current supported method, and the docs show an example using Authorization: Bearer. [1] The token prefixes (ghp_/gho_/ghu_/ghs_/ghr_) and added underscore separator are described in GitHub’s engineering blog. [2]

Citations:


Fix GitHub API curl authentication to use an Authorization header.

-u "$FRONTPAGE_TOKEN" passes the PAT as the basic-auth username (with empty password). For GitHub REST API PAT authentication, the token should be sent via the Authorization header (e.g., Authorization: token $FRONTPAGE_TOKEN).

🔧 Proposed fix using Authorization header
          curl -X POST https://api.github.com/repos/storybookjs/frontpage/dispatches \
            -H 'Accept: application/vnd.github.v3+json' \
-           -u "$FRONTPAGE_TOKEN" \
+           -H "Authorization: token $FRONTPAGE_TOKEN" \
            --data "$DISPATCH_PAYLOAD"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
curl -X POST https://api.github.com/repos/storybookjs/frontpage/dispatches \
-H 'Accept: application/vnd.github.v3+json' \
-u ${{ secrets.FRONTPAGE_ACCESS_TOKEN }} \
--data '{"event_type": "request-create-frontpage-branch", "client_payload": { "branch": "${{ needs.create-next-release-branch.outputs.branch || needs.branch-checks.outputs.branch }}" }}'
-H 'Accept: application/vnd.github.v3+json' \
-u "$FRONTPAGE_TOKEN" \
--data "$DISPATCH_PAYLOAD"
curl -X POST https://api.github.com/repos/storybookjs/frontpage/dispatches \
-H 'Accept: application/vnd.github.v3+json' \
-H "Authorization: token $FRONTPAGE_TOKEN" \
--data "$DISPATCH_PAYLOAD"
🤖 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/handle-release-branches.yml around lines 123 - 126, The
curl call currently uses -u "$FRONTPAGE_TOKEN" (basic auth) which is wrong for
PATs; update the command that posts DISPATCH_PAYLOAD to use an Authorization
header instead by adding a header like "Authorization: token $FRONTPAGE_TOKEN"
(keep the existing Accept header and --data "$DISPATCH_PAYLOAD"), and remove the
-u "$FRONTPAGE_TOKEN" flag so the request authenticates via the Authorization
header.

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.

@copilot fix this

61 changes: 33 additions & 28 deletions .github/workflows/nx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,41 +34,44 @@ jobs:
with:
filter: tree:0
fetch-depth: 0
persist-credentials: false
- name: Set Nx tag(s)
id: tag
env:
EVENT_NAME: ${{ github.event_name }}
IS_MERGED: ${{ contains(github.event.pull_request.labels.*.name, 'ci:merged') }}
IS_DAILY: ${{ contains(github.event.pull_request.labels.*.name, 'ci:daily') }}
REF: ${{ github.ref }}
run: |
tags="normal"
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
if [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci:merged') }}" == "true" ]]; then
tags="merged"
fi
if [[ "${{ contains(github.event.pull_request.labels.*.name, 'ci:daily') }}" == "true" ]]; then
tags="daily"
fi
if [[ "$EVENT_NAME" == "pull_request" ]]; then
if [[ "$IS_MERGED" == "true" ]]; then tags="merged"; fi
if [[ "$IS_DAILY" == "true" ]]; then tags="daily"; fi
fi

if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/next" ]]; then
if [[ "$EVENT_NAME" == "push" && "$REF" == "refs/heads/next" ]]; then
tags="merged"
fi

if [[ "${{ github.event_name }}" == "schedule" ]]; then
if [[ "$EVENT_NAME" == "schedule" ]]; then
tags="daily"
fi

echo "tag=$tags" >> "$GITHUB_OUTPUT"
- name: Select distribution config
id: dist
env:
TAG: ${{ steps.tag.outputs.tag }}
run: |
if [[ "${{ steps.tag.outputs.tag }}" == "daily" ]]; then
if [[ "$TAG" == "daily" ]]; then
echo "config=./.nx/workflows/distribution-config-daily.yaml" >> "$GITHUB_OUTPUT"
else
echo "config=./.nx/workflows/distribution-config.yaml" >> "$GITHUB_OUTPUT"
fi
- run: npx nx-cloud@latest start-ci-run --distribute-on="${{ steps.dist.outputs.config }}" --stop-agents-after="$ALL_TASKS"
- env:
DIST_CONFIG: ${{ steps.dist.outputs.config }}
run: npx nx-cloud@19.1.3 start-ci-run --distribute-on="$DIST_CONFIG" --stop-agents-after="$ALL_TASKS"
- name: Create Nx Cloud Status (pending)
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b #7.1.0
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
script: | # zizmor: ignore[template-injection] safe toJson context expansion in github-script
const tag = ${{ toJson(steps.tag.outputs.tag) }} || 'normal';

await github.rest.repos.createCommitStatus({
Expand All @@ -82,49 +85,51 @@ jobs:
description: 'NX Cloud is running your tests',
context: `nx: ${tag}`,
});
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e #6.4.0
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install --immutable
- uses: nrwl/nx-set-shas@afb73a62d26e41464e9254689e1fd6122ee683c1 # v5.0.1
- id: nx
name: 'Run nx'
env:
TAG: ${{ steps.tag.outputs.tag }}
run: |
echo 'nx_output<<EOF' >> "$GITHUB_OUTPUT"
yarn nx run-many -t $ALL_TASKS -c production -p="tag:library,tag:ci:${{ steps.tag.outputs.tag }}" | tee -a "$GITHUB_OUTPUT"
yarn nx run-many -t $ALL_TASKS -c production -p="tag:library,tag:ci:${TAG}" | tee -a "$GITHUB_OUTPUT"
status=${PIPESTATUS[0]}
echo 'EOF' >> "$GITHUB_OUTPUT"
exit $status

- name: Create per-task Nx statuses
if: always()
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b #7.1.0
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
script: | # zizmor: ignore[template-injection] safe toJson context expansion in github-script
const raw = ${{ toJson(steps.nx.outputs.nx_output) }} || '';
const tag = ${{ toJson(steps.tag.outputs.tag) }} || '';
const lines = raw.split('\n');
const failures = [];

for (const [i, line] of lines.entries()) {
if (!line.includes('✖')) continue;

const task =
line.match(/✖\s+([^│]+?)\s{2,}/)?.[1].trim() ||
'Unknown Nx task';

const url = lines
.slice(i + 1, i + 6)
.find(l => l.includes('Task logs:'))
?.match(/Task logs:\s*(https:\/\/cloud\.nx\.app\/logs\/\S+)/)?.[1];

failures.push({ task, url });
}

const sha = context.payload.pull_request?.head?.sha ?? context.sha;

// Per-task statuses (max 5)
for (const { task, url } of failures.slice(0, 5)) {
await github.rest.repos.createCommitStatus({
Expand All @@ -137,7 +142,7 @@ jobs:
description: 'Your test failed on NX Cloud',
});
}

const runMatches = raw.match(/https:\/\/cloud\.nx\.app\/runs\/\S+/g);
const nxCloudUrl = runMatches ? runMatches[runMatches.length - 1] : undefined;

Expand All @@ -153,4 +158,4 @@ jobs:
? `Nx Cloud run failed (${failedCount} tasks failed)`
: 'Nx Cloud run finished successfully',
context: `nx: ${tag}`,
});
});
Loading
Loading