diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..b822848f30d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,41 @@ +# Dependabot config — keeps SHA-pinned GitHub Actions current. +# +# Without this, every action pinned to a commit SHA in .github/workflows +# silently rots: vulnerabilities discovered upstream don't reach us, and +# our pinned SHAs drift further from the action's release tags every week. +# +# Scope: GitHub Actions only. Node/Python/pnpm dependency updates are +# already handled by Renovate / manual upgrades and don't belong here. + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + cooldown: + default-days: 1 + commit-message: + prefix: "chore(ci)" + include: "scope" + labels: + - "dependencies" + - "github-actions" + - "security" + groups: + minor-and-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + # Workaround for dependabot/dependabot-core#14202: without an explicit + # major group, major updates matching the minor-and-patch pattern are + # silently suppressed. Remove this group when #14202 is fixed to get + # individual (ungrouped) PRs per major bump instead. + major: + patterns: + - "*" + update-types: + - "major" diff --git a/.github/workflows/auto_merge_showcases.yml b/.github/workflows/auto_merge_showcases.yml index 9760f497753..f74c338fe9e 100644 --- a/.github/workflows/auto_merge_showcases.yml +++ b/.github/workflows/auto_merge_showcases.yml @@ -16,25 +16,31 @@ jobs: steps: - name: Check author is on Demo team id: check-team - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: + # Authorize on the PR AUTHOR (immutable for the lifetime of the PR), + # never `context.actor` — `actor` is whoever triggered the most + # recent event, so a team member synchronizing or reopening an + # outsider's PR would otherwise green-light auto-merge of code + # they didn't author. script: | + const prAuthor = context.payload.pull_request.user.login; try { await github.rest.teams.getMembershipForUserInOrg({ org: 'CopilotKit', team_slug: 'demo', - username: context.actor, + username: prAuthor, }); core.setOutput('is_demo', 'true'); } catch { - core.info(`${context.actor} is not a member of CopilotKit/demo`); + core.info(`${prAuthor} is not a member of CopilotKit/demo`); core.setOutput('is_demo', 'false'); } - name: Check PR only touches showcases if: steps.check-team.outputs.is_demo == 'true' id: check-files - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const { data: files } = await github.rest.pulls.listFiles({ @@ -59,7 +65,7 @@ jobs: - name: Approve PR if: steps.check-team.outputs.is_demo == 'true' && steps.check-files.outputs.only_showcases == 'true' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | await github.rest.pulls.createReview({ @@ -72,7 +78,7 @@ jobs: - name: Enable auto-merge if: steps.check-team.outputs.is_demo == 'true' && steps.check-files.outputs.only_showcases == 'true' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | await github.graphql(` diff --git a/.github/workflows/cleanup_pr-caches.yml b/.github/workflows/cleanup_pr-caches.yml index 793bf2df1d5..d06a953e66a 100644 --- a/.github/workflows/cleanup_pr-caches.yml +++ b/.github/workflows/cleanup_pr-caches.yml @@ -4,9 +4,16 @@ on: types: - closed +permissions: + contents: read + jobs: pr-caches: runs-on: ubuntu-latest + permissions: + contents: read + # Needed to delete repository actions caches via `gh cache delete` + actions: write steps: - name: Cleanup run: | diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 00000000000..b7a6e555951 --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,32 @@ +name: Dependabot Auto-Merge (Minor/Patch) + +on: + pull_request_target: + types: [opened, synchronize] + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + runs-on: ubuntu-latest + if: github.event.pull_request.user.login == 'dependabot[bot]' + steps: + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Auto-approve and merge minor/patch github-actions updates + if: >- + steps.metadata.outputs.package-ecosystem == 'github_actions' && + (steps.metadata.outputs.update-type == 'version-update:semver-minor' || + steps.metadata.outputs.update-type == 'version-update:semver-patch') + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr review "$PR_URL" --approve + gh pr merge "$PR_URL" --auto --merge diff --git a/.github/workflows/dependabot-major-analysis.yml b/.github/workflows/dependabot-major-analysis.yml new file mode 100644 index 00000000000..0e44e756dde --- /dev/null +++ b/.github/workflows/dependabot-major-analysis.yml @@ -0,0 +1,144 @@ +name: Dependabot Major Version Analysis + +on: + pull_request_target: + types: [opened] + +permissions: + contents: read + pull-requests: write + +jobs: + analyze-major: + runs-on: ubuntu-latest + if: github.event.pull_request.user.login == 'dependabot[bot]' + steps: + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Analyze major version bump + if: >- + steps.metadata.outputs.package-ecosystem == 'github_actions' && + steps.metadata.outputs.update-type == 'version-update:semver-major' + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + env: + DEP_NAME: ${{ steps.metadata.outputs.dependency-names }} + PREV_VERSION: ${{ steps.metadata.outputs.previous-version }} + NEW_VERSION: ${{ steps.metadata.outputs.new-version }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const depName = process.env.DEP_NAME; + const prevVersion = process.env.PREV_VERSION; + const newVersion = process.env.NEW_VERSION; + const parts = depName.split('/'); + const owner = parts[0]; + const repo = parts[1]; + const repoSlug = `${owner}/${repo}`; + + let releases = []; + try { + const { data } = await github.rest.repos.listReleases({ owner, repo, per_page: 50 }); + releases = data; + } catch (err) { + core.warning(`Could not fetch releases for ${repoSlug}: ${err.message}`); + } + + const prevMajor = parseInt(prevVersion.replace(/^v/, ''), 10); + const newMajor = parseInt(newVersion.replace(/^v/, ''), 10); + + const relevantReleases = releases.filter(r => { + const major = parseInt(r.tag_name.replace(/^v/, ''), 10); + return major > prevMajor && major <= newMajor; + }); + + let releaseNotesSummary = ''; + let breakingChanges = ''; + + if (relevantReleases.length === 0) { + releaseNotesSummary = '_No releases found between these versions._'; + breakingChanges = `_Unable to determine breaking changes automatically. Please review the [full changelog](https://github.com/${repoSlug}/releases)._`; + } else { + for (const release of relevantReleases.slice(0, 10)) { + const body = (release.body || '_No release notes._').replace(/(?<=^|\s)@(?=[a-zA-Z0-9])(?![a-zA-Z0-9-]*\/)/gm, ''); + releaseNotesSummary += `### ${release.tag_name}${release.name && release.name !== release.tag_name ? ' — ' + release.name : ''}\n\n`; + releaseNotesSummary += body.substring(0, 2000); + if (body.length > 2000) releaseNotesSummary += '\n\n_...truncated_'; + releaseNotesSummary += '\n\n---\n\n'; + const lines = body.split('\n'); + for (const line of lines) { + if (/breaking|BREAKING|removed|deprecated|incompatible|migration/i.test(line)) { + breakingChanges += `- ${line.trim()}\n`; + } + } + } + } + + if (!breakingChanges) { + breakingChanges = '_No explicit breaking changes detected in release notes. Manual review recommended._'; + } + + let commentBody = `## :warning: Major Version Update — Manual Review Required + + | Field | Value | + |-------|-------| + | **Action** | [\`${depName}\`](https://github.com/${repoSlug}) | + | **Previous** | \`v${prevVersion}\` | + | **New** | \`v${newVersion}\` | + | **Type** | Major (\`v${prevMajor}\` → \`v${newMajor}\`) | + + ### Breaking Changes + + ${breakingChanges} + + ### Release Notes (v${prevMajor + 1} → v${newMajor}) + + ${releaseNotesSummary} + + ### Next Steps + + 1. Review breaking changes above + 2. Check if workflow inputs/outputs changed + 3. Verify compatibility with your CI/CD configuration + + > Full changelog: https://github.com/${repoSlug}/releases + + --- + _Generated automatically for Dependabot major version PRs._`.replace(/^ /gm, ''); + + if (commentBody.length > 64000) { + commentBody = commentBody.substring(0, 63900) + '\n\n_...comment truncated due to size limit._'; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: commentBody, + }); + + try { + const labelsToAdd = ['major-update', 'needs-review']; + for (const label of labelsToAdd) { + try { + await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label }); + } catch { + const colors = { 'major-update': 'B60205', 'needs-review': 'FBCA04' }; + await github.rest.issues.createLabel({ + owner: context.repo.owner, repo: context.repo.repo, + name: label, color: colors[label] || 'EDEDED', + }); + } + } + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + labels: labelsToAdd, + }); + } catch (err) { + core.warning(`Could not add labels: ${err.message}`); + } diff --git a/.github/workflows/ghcr_unlinked_packages.yml b/.github/workflows/ghcr_unlinked_packages.yml index fd934f80971..5c04c4c47a7 100644 --- a/.github/workflows/ghcr_unlinked_packages.yml +++ b/.github/workflows/ghcr_unlinked_packages.yml @@ -185,7 +185,7 @@ jobs: - name: Notify Slack (drift detected) if: steps.audit.outputs.unlinked_count != '0' && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_GHCR_DRIFT }} webhook-type: incoming-webhook diff --git a/.github/workflows/integrations_parity.yml b/.github/workflows/integrations_parity.yml index 609505c3783..a57b52002ef 100644 --- a/.github/workflows/integrations_parity.yml +++ b/.github/workflows/integrations_parity.yml @@ -23,17 +23,20 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "22" - name: Install pnpm - uses: pnpm/action-setup@v4 + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 with: - version: 10.13.1 run_install: false - name: Install root dev dependencies (tsx) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index b2dc5f4d129..fe2124e9f35 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -20,6 +20,9 @@ on: default: false type: boolean +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: false @@ -34,22 +37,37 @@ jobs: permissions: contents: read steps: + # No credential persistence needed: the build job only reads source. + # Keeping credentials out of the artifact avoids leaking tokens. - name: Checkout Repo - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20.x - name: Install Dependencies run: pnpm install --frozen-lockfile + - name: Bump prerelease versions + env: + INPUT_SCOPE: ${{ inputs.scope }} + INPUT_SUFFIX: ${{ inputs.suffix }} + run: | + ARGS="--scope $INPUT_SCOPE" + if [ -n "$INPUT_SUFFIX" ]; then + ARGS="$ARGS --suffix $INPUT_SUFFIX" + fi + pnpm tsx scripts/release/bump-prerelease.ts $ARGS + - name: Build packages run: pnpm run build @@ -57,7 +75,7 @@ jobs: run: pnpm run test - name: Upload workspace - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: workspace path: . @@ -68,21 +86,22 @@ jobs: needs: build runs-on: ubuntu-latest timeout-minutes: 20 + environment: npm permissions: contents: read steps: - name: Download workspace - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: workspace - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20.x registry-url: https://registry.npmjs.org @@ -96,15 +115,18 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Publish prerelease + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + INPUT_SCOPE: ${{ inputs.scope }} + INPUT_SUFFIX: ${{ inputs.suffix }} + INPUT_DRY_RUN: ${{ inputs.dry_run }} run: | - ARGS="--scope ${{ inputs.scope }}" - if [ -n "${{ inputs.suffix }}" ]; then - ARGS="$ARGS --suffix ${{ inputs.suffix }}" + ARGS="--scope $INPUT_SCOPE" + if [ -n "$INPUT_SUFFIX" ]; then + ARGS="$ARGS --suffix $INPUT_SUFFIX" fi - if [ "${{ inputs.dry_run }}" == "true" ]; then + if [ "$INPUT_DRY_RUN" == "true" ]; then ARGS="$ARGS --dry-run" fi pnpm tsx scripts/release/prerelease.ts $ARGS - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish-commit.yml b/.github/workflows/publish-commit.yml index d19cdeaf571..08573578449 100644 --- a/.github/workflows/publish-commit.yml +++ b/.github/workflows/publish-commit.yml @@ -7,6 +7,9 @@ on: paths: - "packages/**" +permissions: + contents: read + concurrency: group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -20,16 +23,22 @@ jobs: build: runs-on: ubuntu-latest timeout-minutes: 15 + environment: npm + permissions: + contents: read + # pkg-pr-new posts snapshot comments on the PR using the workflow token + pull-requests: write steps: + # persist-credentials required: pkg-pr-new uses repo token to post snapshot comments on the PR - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - run: corepack enable - - uses: actions/setup-node@v4 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version-file: "package.json" # setup-node built-in cache is fork-safe (fork PRs can't write to base repo cache) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 07919cfdbef..0b17b03443d 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -5,6 +5,9 @@ on: types: [closed] branches: [main] +permissions: + contents: read + concurrency: group: publish-release cancel-in-progress: false @@ -34,19 +37,25 @@ jobs: echo "scope=$SCOPE" >> $GITHUB_OUTPUT echo "Detected scope: $SCOPE" + # No token/credential persistence: the publish job sets up its own + # `git config insteadOf` with secrets.GITHUB_TOKEN before pushing tags, + # so this checkout doesn't need write access. Critically, the + # subsequent `Upload workspace` step packs the entire checkout + # (including .git/config) into an artifact — persisting credentials + # here would leak a workflow-scoped token to anyone with actions:read. - name: Checkout Repo - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20.x @@ -57,7 +66,7 @@ jobs: run: pnpm run build - name: Upload workspace - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: workspace path: . @@ -71,11 +80,12 @@ jobs: needs: build runs-on: ubuntu-latest timeout-minutes: 20 + environment: npm permissions: contents: write steps: - name: Download workspace - uses: actions/download-artifact@v4 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: workspace @@ -84,12 +94,12 @@ jobs: git config --local url."https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/".insteadOf "https://github.com/" - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20.x registry-url: https://registry.npmjs.org @@ -112,8 +122,8 @@ jobs: - name: Configure git user run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" - name: Check for pre-existing tags run: | @@ -144,7 +154,7 @@ jobs: id: tag - name: Create GitHub Release - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: RELEASE_TAG: ${{ steps.tag.outputs.tag }} RELEASE_SCOPE: ${{ needs.build.outputs.scope }} diff --git a/.github/workflows/security_fork-pr-alert.yml b/.github/workflows/security_fork-pr-alert.yml index 50ad9e1599a..fef1625d1b6 100644 --- a/.github/workflows/security_fork-pr-alert.yml +++ b/.github/workflows/security_fork-pr-alert.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check for suspicious patterns - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const pr = context.payload.pull_request; diff --git a/.github/workflows/security_zizmor.yml b/.github/workflows/security_zizmor.yml new file mode 100644 index 00000000000..e4f59dc7011 --- /dev/null +++ b/.github/workflows/security_zizmor.yml @@ -0,0 +1,62 @@ +name: security / zizmor + +# zizmor runs static analysis over every workflow under .github/workflows +# looking for the well-known classes of GitHub Actions footguns: template +# injection from untrusted input, dangerous triggers like +# `pull_request_target`, unpinned `uses:` refs, excessive token scopes, +# secret exfil via job outputs, and a long tail of others. +# +# Findings at `low` confidence and above fail the job, so this acts as a +# blocking PR check. To deliberately allow a finding, suppress it in +# `.github/zizmor.yml` with a justification — never silently ignore. + +on: + push: + branches: [main] + paths: + - ".github/workflows/**" + - ".github/actions/**" + - ".github/zizmor.yml" + - ".github/dependabot.yml" + pull_request: + paths: + - ".github/workflows/**" + - ".github/actions/**" + - ".github/zizmor.yml" + - ".github/dependabot.yml" + schedule: + # Catch findings introduced by newly-published advisories even when no + # workflow file changed this week. + - cron: "0 9 * * 1" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + zizmor: + name: Static analysis (zizmor) + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + # SARIF upload requires `security-events: write`, but we currently + # rely on the action's own annotations. Keep contents:read only. + contents: read + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Run zizmor + # `min-severity: low` blocks PRs on the broadest set of findings + # without flagging hypothetical-only `informational` notes. Drop + # to `medium` if low-severity churn becomes a problem. + uses: zizmorcore/zizmor-action@b572f7b1a1c2d41efaab43d504f68d215c3cd727 # v0.5.4 + with: + min-severity: low + advanced-security: false + config: .github/zizmor.yml diff --git a/.github/workflows/showcase_build.yml b/.github/workflows/showcase_build.yml index fc27494972c..53fd5804e6b 100644 --- a/.github/workflows/showcase_build.yml +++ b/.github/workflows/showcase_build.yml @@ -56,18 +56,25 @@ on: env: RAILWAY_ENV_ID: "b14919f4-6417-429f-848d-c6ae2201e04f" +permissions: + contents: read + jobs: detect-changes: runs-on: ubuntu-latest timeout-minutes: 5 + permissions: + contents: read outputs: matrix: ${{ steps.build-matrix.outputs.matrix }} has_changes: ${{ steps.build-matrix.outputs.has_changes }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Detect changed paths - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: filter with: # All filter values use list form for consistency. `shell` @@ -141,6 +148,11 @@ jobs: - name: Build service matrix id: build-matrix + env: + DISPATCH_SERVICE: ${{ github.event.inputs.service }} + GITHUB_SHA_ENV: ${{ github.sha }} + GITHUB_REF_NAME_ENV: ${{ github.ref_name }} + FILTER_CHANGES: ${{ steps.filter.outputs.changes }} run: | # Full service config as JSON # Fields: dispatch_name, filter_key, context, image, railway_id, timeout, lfs, build_args, build_args_sha, build_args_branch, dockerfile, health_path @@ -150,7 +162,7 @@ jobs: # to 200 at the other path. Misconfigured paths are a config bug to # fix in the matrix, not runtime behavior to hide. ALL_SERVICES='[ - {"dispatch_name":"shell","filter_key":"shell","context":".","image":"showcase-shell","railway_id":"40eea0da-6071-4ea8-bdb9-39afb19225ec","timeout":10,"lfs":true,"build_args_sha":"${{ github.sha }}","build_args_branch":"${{ github.ref_name }}","dockerfile":"showcase/shell/Dockerfile","health_path":"/"}, + {"dispatch_name":"shell","filter_key":"shell","context":".","image":"showcase-shell","railway_id":"40eea0da-6071-4ea8-bdb9-39afb19225ec","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell/Dockerfile","health_path":"/"}, {"dispatch_name":"langgraph-python","filter_key":"langgraph_python","context":"showcase/integrations/langgraph-python","image":"showcase-langgraph-python","railway_id":"90d03214-4569-41b0-b4c1-6438a8a7b203","timeout":15,"lfs":false,"build_args":"","dockerfile":"","health_path":"/api/health"}, {"dispatch_name":"mastra","filter_key":"mastra","context":"showcase/integrations/mastra","image":"showcase-mastra","railway_id":"d7979eb7-2405-4aab-ad21-438f4a1b08af","timeout":15,"lfs":false,"build_args":"","dockerfile":"","health_path":"/api/health"}, {"dispatch_name":"crewai-crews","filter_key":"crewai_crews","context":"showcase/integrations/crewai-crews","image":"showcase-crewai-crews","railway_id":"0e9c284d-8d87-4fcf-9f82-6b704d7e4bd4","timeout":15,"lfs":false,"build_args":"","dockerfile":"","health_path":"/api/health"}, @@ -170,15 +182,19 @@ jobs: {"dispatch_name":"claude-sdk-python","filter_key":"claude_sdk_python","context":"showcase/integrations/claude-sdk-python","image":"showcase-claude-sdk-python","railway_id":"b122ab65-9854-4cb2-a68e-b50ff13f7481","timeout":15,"lfs":false,"build_args":"","dockerfile":"","health_path":"/api/health"}, {"dispatch_name":"built-in-agent","filter_key":"built_in_agent","context":"showcase/integrations/built-in-agent","image":"showcase-built-in-agent","railway_id":"f4f8371a-bc46-45b2-b6d4-9c9af608bdbf","timeout":15,"lfs":false,"build_args":"","dockerfile":"","health_path":"/api/health"}, {"dispatch_name":"shell-dojo","filter_key":"shell_dojo","context":".","image":"showcase-shell-dojo","railway_id":"7ad1ece7-2228-49cd-8a78-bddf30322907","timeout":10,"lfs":false,"build_args":"","dockerfile":"showcase/shell-dojo/Dockerfile","health_path":"/"}, - {"dispatch_name":"shell-dashboard","filter_key":"shell_dashboard","context":".","image":"showcase-shell-dashboard","railway_id":"4d5dfd74-be61-40b2-8564-b53b7dd4c15b","timeout":10,"lfs":true,"build_args_sha":"${{ github.sha }}","build_args_branch":"${{ github.ref_name }}","build_args_pb_url":"https://showcase-pocketbase-production.up.railway.app","build_args_shell_url":"https://showcase.copilotkit.ai","build_args_ops_url":"https://showcase-harness-production.up.railway.app","dockerfile":"showcase/shell-dashboard/Dockerfile","health_path":"/"}, - {"dispatch_name":"shell-docs","filter_key":"shell_docs","context":".","image":"showcase-shell-docs","railway_id":"7badfb8d-4228-414c-9145-b4026803714f","timeout":10,"lfs":true,"build_args_sha":"${{ github.sha }}","build_args_branch":"${{ github.ref_name }}","build_args_analytics":"yes","dockerfile":"showcase/shell-docs/Dockerfile","health_path":"/"}, + {"dispatch_name":"shell-dashboard","filter_key":"shell_dashboard","context":".","image":"showcase-shell-dashboard","railway_id":"4d5dfd74-be61-40b2-8564-b53b7dd4c15b","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","build_args_pb_url":"https://showcase-pocketbase-production.up.railway.app","build_args_shell_url":"https://showcase.copilotkit.ai","build_args_ops_url":"https://showcase-harness-production.up.railway.app","dockerfile":"showcase/shell-dashboard/Dockerfile","health_path":"/"}, + {"dispatch_name":"shell-docs","filter_key":"shell_docs","context":".","image":"showcase-shell-docs","railway_id":"7badfb8d-4228-414c-9145-b4026803714f","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","build_args_analytics":"yes","dockerfile":"showcase/shell-docs/Dockerfile","health_path":"/"}, {"dispatch_name":"showcase-harness","filter_key":"showcase_harness","context":".","image":"showcase-harness","railway_id":"3a14bfed-0537-4d71-897b-7c593dca161d","timeout":20,"lfs":false,"build_args":"","dockerfile":"showcase/harness/Dockerfile","health_path":"/health"}, {"dispatch_name":"showcase-aimock","filter_key":"showcase_aimock","context":"showcase/aimock","image":"showcase-aimock","railway_id":"0fa0435d-8a66-46f0-84fd-e4250b580013","timeout":5,"lfs":false,"build_args":"","dockerfile":"showcase/aimock/Dockerfile","health_path":"/health"} ]' - DISPATCH="${{ github.event.inputs.service }}" - CHANGES='${{ steps.filter.outputs.changes }}' - CHANGES="${CHANGES:-[]}" + # Substitute trusted git context (sha, ref_name) into JSON placeholders. + # Routed through env: above to satisfy zizmor template-injection check. + ALL_SERVICES="${ALL_SERVICES//__GH_SHA__/$GITHUB_SHA_ENV}" + ALL_SERVICES="${ALL_SERVICES//__GH_REF_NAME__/$GITHUB_REF_NAME_ENV}" + + DISPATCH="$DISPATCH_SERVICE" + CHANGES="${FILTER_CHANGES:-[]}" # Filter services based on three dispatch modes: # dispatch == "all": manual "deploy all" — include every service unconditionally. @@ -220,16 +236,20 @@ jobs: check-lockfile: runs-on: ubuntu-latest timeout-minutes: 5 + permissions: + contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false # Omit `version:` so pnpm/action-setup inherits from the repo's # `packageManager` field in package.json (enforced via corepack). # Earlier revisions hard-pinned `version: 10.13.1` which silently # drifted from package.json whenever the repo bumped pnpm — # resulting in lockfile-vs-engine mismatches that only surfaced on # the slow `--frozen-lockfile` path. - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22.x - run: pnpm install --frozen-lockfile --ignore-scripts @@ -239,9 +259,13 @@ jobs: if: needs.detect-changes.outputs.has_changes == 'true' runs-on: ubuntu-latest timeout-minutes: 3 + permissions: + contents: read steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22.x - name: Verify Railway image refs @@ -265,15 +289,16 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: lfs: ${{ matrix.service.lfs }} + persist-credentials: false - name: Setup Depot - uses: depot/setup-action@v1 + uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -350,7 +375,7 @@ jobs: done - name: Build and push - uses: depot/build-push-action@v1 + uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1 with: project: m2kw2wmmcp context: ${{ matrix.service.context }} @@ -407,7 +432,7 @@ jobs: steps: - name: Slack alert if: env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook @@ -415,7 +440,7 @@ jobs: { "text": ${{ toJSON(format(':x: *Showcase Build Failed*\nCommit: `{0}` by {1}\n', github.sha, github.actor, github.repository, github.run_id)) }} } - name: Comment on PR - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ diff --git a/.github/workflows/showcase_build_check.yml b/.github/workflows/showcase_build_check.yml index d82be1dc20f..6fbdfcfa02a 100644 --- a/.github/workflows/showcase_build_check.yml +++ b/.github/workflows/showcase_build_check.yml @@ -14,18 +14,25 @@ concurrency: group: showcase-build-check-${{ github.head_ref }} cancel-in-progress: true +permissions: + contents: read + jobs: detect-changes: runs-on: ubuntu-latest timeout-minutes: 5 + permissions: + contents: read outputs: matrix: ${{ steps.build-matrix.outputs.matrix }} has_changes: ${{ steps.build-matrix.outputs.has_changes }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Detect changed paths - uses: dorny/paths-filter@v3 + uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: filter with: filters: | @@ -95,6 +102,10 @@ jobs: - name: Build service matrix id: build-matrix + env: + PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + PR_HEAD_REF: ${{ github.head_ref }} + FILTER_CHANGES: ${{ steps.filter.outputs.changes }} run: | # Mirror of the ALL_SERVICES definition from showcase_build.yml. # Keep in sync — the matrix here must match the production build @@ -105,7 +116,7 @@ jobs: # timeout, lfs, build_args_*, dockerfile. # Fields omitted (not needed for build-only): railway_id, health_path. ALL_SERVICES='[ - {"dispatch_name":"shell","filter_key":"shell","context":".","image":"showcase-shell","timeout":10,"lfs":true,"build_args_sha":"${{ github.event.pull_request.head.sha }}","build_args_branch":"${{ github.head_ref }}","dockerfile":"showcase/shell/Dockerfile"}, + {"dispatch_name":"shell","filter_key":"shell","context":".","image":"showcase-shell","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell/Dockerfile"}, {"dispatch_name":"langgraph-python","filter_key":"langgraph_python","context":"showcase/integrations/langgraph-python","image":"showcase-langgraph-python","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"mastra","filter_key":"mastra","context":"showcase/integrations/mastra","image":"showcase-mastra","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"crewai-crews","filter_key":"crewai_crews","context":"showcase/integrations/crewai-crews","image":"showcase-crewai-crews","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, @@ -125,14 +136,20 @@ jobs: {"dispatch_name":"claude-sdk-python","filter_key":"claude_sdk_python","context":"showcase/integrations/claude-sdk-python","image":"showcase-claude-sdk-python","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"built-in-agent","filter_key":"built_in_agent","context":"showcase/integrations/built-in-agent","image":"showcase-built-in-agent","timeout":15,"lfs":false,"build_args":"","dockerfile":""}, {"dispatch_name":"shell-dojo","filter_key":"shell_dojo","context":".","image":"showcase-shell-dojo","timeout":10,"lfs":false,"build_args":"","dockerfile":"showcase/shell-dojo/Dockerfile"}, - {"dispatch_name":"shell-dashboard","filter_key":"shell_dashboard","context":".","image":"showcase-shell-dashboard","timeout":10,"lfs":true,"build_args_sha":"${{ github.event.pull_request.head.sha }}","build_args_branch":"${{ github.head_ref }}","build_args_pb_url":"https://showcase-pocketbase-production.up.railway.app","build_args_shell_url":"https://showcase.copilotkit.ai","build_args_ops_url":"https://showcase-harness-production.up.railway.app","dockerfile":"showcase/shell-dashboard/Dockerfile"}, - {"dispatch_name":"shell-docs","filter_key":"shell_docs","context":".","image":"showcase-shell-docs","timeout":10,"lfs":true,"build_args_sha":"${{ github.event.pull_request.head.sha }}","build_args_branch":"${{ github.head_ref }}","dockerfile":"showcase/shell-docs/Dockerfile"}, + {"dispatch_name":"shell-dashboard","filter_key":"shell_dashboard","context":".","image":"showcase-shell-dashboard","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","build_args_pb_url":"https://showcase-pocketbase-production.up.railway.app","build_args_shell_url":"https://showcase.copilotkit.ai","build_args_ops_url":"https://showcase-harness-production.up.railway.app","dockerfile":"showcase/shell-dashboard/Dockerfile"}, + {"dispatch_name":"shell-docs","filter_key":"shell_docs","context":".","image":"showcase-shell-docs","timeout":10,"lfs":true,"build_args_sha":"__GH_SHA__","build_args_branch":"__GH_REF_NAME__","dockerfile":"showcase/shell-docs/Dockerfile"}, {"dispatch_name":"showcase-harness","filter_key":"showcase_harness","context":".","image":"showcase-harness","timeout":20,"lfs":false,"build_args":"","dockerfile":"showcase/harness/Dockerfile"}, {"dispatch_name":"showcase-aimock","filter_key":"showcase_aimock","context":"showcase/aimock","image":"showcase-aimock","timeout":5,"lfs":false,"build_args":"","dockerfile":"showcase/aimock/Dockerfile"} ]' - CHANGES='${{ steps.filter.outputs.changes }}' - CHANGES="${CHANGES:-[]}" + # Substitute trusted PR head sha/ref into JSON placeholders. + # Routed through env: above to satisfy zizmor template-injection check. + # Note: PR_HEAD_REF is attacker-controllable (fork branch name) but is only + # consumed as a Docker build-arg label inside the isolated build context. + ALL_SERVICES="${ALL_SERVICES//__GH_SHA__/$PR_HEAD_SHA}" + ALL_SERVICES="${ALL_SERVICES//__GH_REF_NAME__/$PR_HEAD_REF}" + + CHANGES="${FILTER_CHANGES:-[]}" # PR event only — filter to changed services MATRIX=$(echo "$ALL_SERVICES" | jq -c --argjson changes "$CHANGES" ' @@ -163,12 +180,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: lfs: ${{ matrix.service.lfs }} + persist-credentials: false - name: Setup Depot - uses: depot/setup-action@v1 + uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1 - name: Prepare build args id: build-args @@ -218,7 +236,7 @@ jobs: done - name: Build Docker image (no push) - uses: depot/build-push-action@v1 + uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1 with: project: m2kw2wmmcp context: ${{ matrix.service.context }} diff --git a/.github/workflows/showcase_capture-previews.yml b/.github/workflows/showcase_capture-previews.yml index e3096bc5aaf..b59145c4563 100644 --- a/.github/workflows/showcase_capture-previews.yml +++ b/.github/workflows/showcase_capture-previews.yml @@ -56,17 +56,19 @@ jobs: # it for checkout + push. Mirrors the pattern in # CopilotKit/internal-skills's sync-versions.yml. id: app-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: app-id: "1108748" private-key: ${{ secrets.DEVOPS_BOT_PRIVATE_KEY }} + permission-contents: write - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: lfs: true ref: ${{ github.event.workflow_run.head_branch || github.ref }} token: ${{ steps.app-token.outputs.token }} + persist-credentials: false - name: Ensure preview release exists env: @@ -79,12 +81,12 @@ jobs: --latest=false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Install ffmpeg run: sudo apt-get update && sudo apt-get install -y ffmpeg @@ -113,34 +115,44 @@ jobs: npx tsx generate-registry.ts - name: Capture previews + env: + INPUT_SLUG: ${{ inputs.slug }} + INPUT_DEMO: ${{ inputs.demo }} + EVENT_NAME: ${{ github.event_name }} + DETECTED_SLUGS: ${{ steps.detect.outputs.slugs }} run: | - ARGS="" + # Build the args as an array (not a space-joined string) so a + # slug or demo value containing whitespace or shell metacharacters + # stays a single argument rather than being re-tokenized by the shell. + ARGS=() # Use input slug/demo if provided (manual dispatch) - if [ -n "${{ inputs.slug }}" ]; then - ARGS="$ARGS --slug ${{ inputs.slug }}" + if [ -n "$INPUT_SLUG" ]; then + ARGS+=(--slug "$INPUT_SLUG") fi - if [ -n "${{ inputs.demo }}" ]; then - ARGS="$ARGS --demo ${{ inputs.demo }}" + if [ -n "$INPUT_DEMO" ]; then + ARGS+=(--demo "$INPUT_DEMO") fi # Use detected slugs for push events (capture only changed) - if [ "${{ github.event_name }}" = "push" ] && [ -n "${{ steps.detect.outputs.slugs }}" ]; then + if [ "$EVENT_NAME" = "push" ] && [ -n "$DETECTED_SLUGS" ]; then # Capture each changed slug - IFS=',' read -ra SLUGS <<< "${{ steps.detect.outputs.slugs }}" + IFS=',' read -ra SLUGS <<< "$DETECTED_SLUGS" for slug in "${SLUGS[@]}"; do npx tsx showcase/scripts/capture-previews.ts --slug "$slug" || true done else - npx tsx showcase/scripts/capture-previews.ts $ARGS || true + npx tsx showcase/scripts/capture-previews.ts "${ARGS[@]}" || true fi - - name: Commit registry updates + - name: Configure git for push run: | - # Commit as the devops-bot installation. The `+[bot]` - # email is the conventional GitHub App noreply form; paired with - # the app-token used for checkout, the push lands on protected - # main via the bot's PROTECT_OUR_MAIN bypass. git config user.name "devops-bot[bot]" git config user.email "1108748+devops-bot[bot]@users.noreply.github.com" + git config --local url."https://x-access-token:${TOKEN}@github.com/".insteadOf "https://github.com/" + env: + TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Commit registry updates + run: | cd showcase/shell/src/data if git diff --quiet registry.json; then echo "No registry changes" @@ -250,7 +262,7 @@ jobs: - name: Notify Slack (failure) if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook diff --git a/.github/workflows/showcase_deploy.yml b/.github/workflows/showcase_deploy.yml index 92d83ccacae..710ebebb864 100644 --- a/.github/workflows/showcase_deploy.yml +++ b/.github/workflows/showcase_deploy.yml @@ -56,10 +56,16 @@ concurrency: env: RAILWAY_ENV_ID: "b14919f4-6417-429f-848d-c6ae2201e04f" +permissions: + contents: read + jobs: resolve-matrix: runs-on: ubuntu-latest timeout-minutes: 3 + permissions: + contents: read + actions: read # Only run verification when the build workflow succeeded. # On workflow_dispatch, always run (workflow_run context is absent). if: >- @@ -71,12 +77,18 @@ jobs: build_run_id: ${{ steps.build-matrix.outputs.build_run_id }} build_run_url: ${{ steps.build-matrix.outputs.build_run_url }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Build verification matrix id: build-matrix env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DISPATCH_SERVICE: ${{ github.event.inputs.service }} + WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} + WORKFLOW_RUN_URL: ${{ github.event.workflow_run.html_url }} + REPO_FULL: ${{ github.repository }} run: | # Service registry: same as showcase_build.yml but only the fields # needed for verification (dispatch_name, railway_id, health_path). @@ -107,16 +119,16 @@ jobs: {"dispatch_name":"showcase-aimock","railway_id":"0fa0435d-8a66-46f0-84fd-e4250b580013","health_path":"/health"} ]' - DISPATCH="${{ github.event.inputs.service }}" - BUILD_RUN_ID="${{ github.event.workflow_run.id }}" - BUILD_RUN_URL="${{ github.event.workflow_run.html_url }}" + DISPATCH="$DISPATCH_SERVICE" + BUILD_RUN_ID="$WORKFLOW_RUN_ID" + BUILD_RUN_URL="$WORKFLOW_RUN_URL" if [ "$DISPATCH" = "all" ] || [ -z "$DISPATCH" ]; then if [ -n "$BUILD_RUN_ID" ]; then # workflow_run trigger: discover which services the build # workflow actually built by inspecting its matrix job names. # Job names follow "build (, ...)" pattern. - JOBS_JSON=$(gh api "repos/${{ github.repository }}/actions/runs/${BUILD_RUN_ID}/jobs" --paginate 2>/dev/null \ + JOBS_JSON=$(gh api "repos/${REPO_FULL}/actions/runs/${BUILD_RUN_ID}/jobs" --paginate 2>/dev/null \ | jq -cs '[.[].jobs[]?]' 2>/dev/null || echo '[]') BUILD_JOBS=$(echo "$JOBS_JSON" | jq -c '[.[] | select((.name // "") | startswith("build"))]') # Extract dispatch_names from successful build job names @@ -148,6 +160,7 @@ jobs: if: needs.resolve-matrix.outputs.has_services == 'true' runs-on: ubuntu-latest timeout-minutes: 15 + environment: railway permissions: contents: read actions: read @@ -274,6 +287,7 @@ jobs: RUN_ID: ${{ github.run_id }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO_FULL: ${{ github.repository }} run: | set -euo pipefail @@ -290,7 +304,7 @@ jobs: CANCELLED=false else # Partial failure: query per-job conclusions from the verify matrix - JOBS_JSON=$(gh api "repos/${{ github.repository }}/actions/runs/${RUN_ID}/jobs" --paginate 2>/dev/null \ + JOBS_JSON=$(gh api "repos/${REPO_FULL}/actions/runs/${RUN_ID}/jobs" --paginate 2>/dev/null \ | jq -cs '[.[].jobs[]?]' 2>/dev/null || echo '[]') VERIFY_JOBS=$(echo "$JOBS_JSON" | jq -c '[.[] | select((.name // "") | startswith("verify"))]' 2>/dev/null || echo '[]') FAILED=$(echo "$SERVICES" | jq -c --argjson jobs "$VERIFY_JOBS" ' diff --git a/.github/workflows/showcase_docs-sync.yml b/.github/workflows/showcase_docs-sync.yml index b4564a56ee8..97170bc3ce9 100644 --- a/.github/workflows/showcase_docs-sync.yml +++ b/.github/workflows/showcase_docs-sync.yml @@ -8,6 +8,9 @@ on: - "docs/snippets/**" workflow_dispatch: +permissions: + contents: read + concurrency: group: showcase-docs-sync cancel-in-progress: false @@ -23,14 +26,15 @@ jobs: SHOWCASE_BRANCH: main SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ env.SHOWCASE_BRANCH }} fetch-depth: 0 + persist-credentials: false - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20 cache: pnpm @@ -76,10 +80,12 @@ jobs: - name: Generate bot token id: bot-token if: steps.sync.outputs.action == 'auto_push' || steps.sync.outputs.action == 'push_and_pr' - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: app-id: 1108748 private-key: ${{ secrets.DEVOPS_BOT_PRIVATE_KEY }} + permission-contents: write + permission-pull-requests: write # Create PR. For action=auto_push (no review items) the PR is # auto-merged via the devops bot (bypasses branch protection). For @@ -366,7 +372,7 @@ jobs: - name: Notify Slack (auto-sync) id: notify-auto-sync if: always() && env.SLACK_WEBHOOK != '' && steps.push.outcome == 'success' && steps.push.outputs.files_changed != '0' && steps.push.outputs.needs_review != 'true' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook @@ -375,7 +381,7 @@ jobs: - name: Notify Slack (merge failed) id: notify-merge-failed if: failure() && env.SLACK_WEBHOOK != '' && steps.push.outputs.pr_opened == 'true' && steps.push.outputs.needs_review != 'true' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook @@ -389,7 +395,7 @@ jobs: - name: Notify Slack (review needed, with PR) id: notify-review-with-pr if: always() && env.SLACK_WEBHOOK != '' && steps.push.outputs.needs_review == 'true' && steps.push.outputs.pr_opened == 'true' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook @@ -402,7 +408,7 @@ jobs: - name: Notify Slack (review needed, no PR) id: notify-review-no-pr if: always() && env.SLACK_WEBHOOK != '' && steps.sync.outputs.action == 'push_and_pr' && steps.push.outputs.pr_opened == 'false' && steps.push.outputs.existing_pr != 'true' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook @@ -413,7 +419,7 @@ jobs: - name: Notify Slack (review needed, existing PR) id: notify-review-existing-pr if: always() && env.SLACK_WEBHOOK != '' && steps.push.outputs.existing_pr == 'true' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook @@ -422,7 +428,7 @@ jobs: - name: Notify Slack (failure) id: notify-failure if: failure() && env.SLACK_WEBHOOK != '' && steps.push.outputs.pr_opened != 'true' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook diff --git a/.github/workflows/showcase_eval-webhook_build.yml b/.github/workflows/showcase_eval-webhook_build.yml index 32921124014..31bc39c7ea5 100644 --- a/.github/workflows/showcase_eval-webhook_build.yml +++ b/.github/workflows/showcase_eval-webhook_build.yml @@ -16,17 +16,19 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: showcase/eval-webhook push: true diff --git a/.github/workflows/showcase_eval.yml b/.github/workflows/showcase_eval.yml index b3b1d8e53b0..8d2c5920c55 100644 --- a/.github/workflows/showcase_eval.yml +++ b/.github/workflows/showcase_eval.yml @@ -69,7 +69,7 @@ jobs: steps: - name: Check commenter has write access id: auth - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ @@ -159,7 +159,7 @@ jobs: - name: Resolve PR HEAD ref id: pr-ref - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const { data: pr } = await github.rest.pulls.get({ @@ -175,7 +175,7 @@ jobs: core.setOutput('pr_number', pr.number); - name: React with rocket emoji - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | await github.rest.reactions.createForIssueComment({ @@ -186,7 +186,7 @@ jobs: }); - name: Post running status comment - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: LEVEL: ${{ steps.parse.outputs.level }} SCOPE_DISPLAY: ${{ steps.parse.outputs.scope_display }} @@ -229,7 +229,7 @@ jobs: steps: - name: Resolve PR HEAD SHA id: resolve - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const pr = await github.rest.pulls.get({ @@ -268,19 +268,19 @@ jobs: steps: - name: Checkout PR HEAD - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ env.PR_SHA }} fetch-depth: 0 persist-credentials: false - - uses: actions/setup-node@v4 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22.x - - uses: pnpm/action-setup@v4.4.0 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Install dependencies run: pnpm install --ignore-scripts @@ -339,7 +339,7 @@ jobs: - name: Upload eval artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: showcase-eval-results path: | @@ -361,7 +361,7 @@ jobs: steps: - name: Post eval results to PR - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: EVAL_STATUS: ${{ needs.eval.result }} RESULT_JSON: ${{ needs.eval.outputs.result_json }} @@ -550,14 +550,15 @@ jobs: - name: Generate devops-bot token id: bot-token if: needs.dispatch-gate.outputs.check_run_id != '' - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: app-id: 1108748 private-key: ${{ secrets.DEVOPS_BOT_PRIVATE_KEY }} + permission-checks: write - name: Update Check Run with results if: needs.dispatch-gate.outputs.check_run_id != '' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: github-token: ${{ steps.bot-token.outputs.token }} script: | diff --git a/.github/workflows/showcase_eval_check.yml b/.github/workflows/showcase_eval_check.yml index 6c20571a1e0..d50113af129 100644 --- a/.github/workflows/showcase_eval_check.yml +++ b/.github/workflows/showcase_eval_check.yml @@ -19,14 +19,17 @@ jobs: steps: - name: Generate devops-bot token id: bot-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: app-id: 1108748 private-key: ${{ secrets.DEVOPS_BOT_PRIVATE_KEY }} + permission-checks: write + permission-issues: write + permission-pull-requests: write - name: Create Check Run id: check-run - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: github-token: ${{ steps.bot-token.outputs.token }} script: | @@ -53,7 +56,7 @@ jobs: core.setOutput('check_run_id', result.data.id); - name: Post or update bot comment with trigger link - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: github-token: ${{ steps.bot-token.outputs.token }} script: | diff --git a/.github/workflows/showcase_keep-alive.yml b/.github/workflows/showcase_keep-alive.yml index 978737b3c89..f5471c95833 100644 --- a/.github/workflows/showcase_keep-alive.yml +++ b/.github/workflows/showcase_keep-alive.yml @@ -15,8 +15,13 @@ on: - cron: "*/5 * * * *" workflow_dispatch: {} +permissions: + contents: read + jobs: ping: + permissions: + contents: read name: Ping ${{ matrix.slug }} if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest diff --git a/.github/workflows/showcase_qa-sync.yml b/.github/workflows/showcase_qa-sync.yml index 1c737901ed6..ee3066dbb64 100644 --- a/.github/workflows/showcase_qa-sync.yml +++ b/.github/workflows/showcase_qa-sync.yml @@ -13,25 +13,32 @@ concurrency: group: showcase-qa-sync cancel-in-progress: true +permissions: + contents: read + jobs: sync-qa: name: Sync QA Instructions runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: read env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Install dependencies working-directory: showcase/scripts @@ -49,7 +56,7 @@ jobs: - name: Notify Slack (failure) if: failure() && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook diff --git a/.github/workflows/showcase_validate.yml b/.github/workflows/showcase_validate.yml index 470913a185d..9672c534952 100644 --- a/.github/workflows/showcase_validate.yml +++ b/.github/workflows/showcase_validate.yml @@ -23,10 +23,8 @@ on: - ".github/workflows/showcase_smoke-monitor.yml" # Least-privilege by default. Individual jobs/steps can widen when needed. -# id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). permissions: contents: read - id-token: write # Split concurrency per event so main-branch push runs are never canceled # mid-execution (we need Slack failure alerts to fire reliably). PR runs @@ -49,6 +47,10 @@ jobs: # typically reduces to ~5-8m. 25m timeout retained as headroom. runs-on: depot-ubuntu-24.04-4 timeout-minutes: 25 + permissions: + contents: read + # id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). + id-token: write defaults: run: # Pin shell so `set -euo pipefail` + `mapfile` behave the same @@ -58,10 +60,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 # Cache npm for the showcase/shell `npm ci` step below (shell is @@ -73,7 +77,7 @@ jobs: # Pinned to a specific minor rather than floating @v4 so that a # silent upstream major/minor change can't alter install semantics # on a random CI run. Bump deliberately when refreshing the toolchain. - uses: pnpm/action-setup@v4.4.0 + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Verify lockfile is up to date run: pnpm install --frozen-lockfile --ignore-scripts @@ -472,7 +476,7 @@ jobs: - name: Notify Slack (failure) if: failure() && github.event_name == 'push' && env.SLACK_WEBHOOK != '' - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook @@ -500,6 +504,8 @@ jobs: # Python unit-test regressions. pytest runs independently of JS/TS checks. runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: read strategy: # Fail-fast disabled so a 3.10-only regression (e.g. typing_extensions # fallback path breaking) doesn't cancel the 3.12 run and leave us @@ -513,10 +519,12 @@ jobs: python-version: ["3.10", "3.12"] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} cache: "pip" diff --git a/.github/workflows/social_copy-generator.yml b/.github/workflows/social_copy-generator.yml index 3676f93b519..16c565178a0 100644 --- a/.github/workflows/social_copy-generator.yml +++ b/.github/workflows/social_copy-generator.yml @@ -14,8 +14,6 @@ on: permissions: contents: read - pull-requests: write - issues: write jobs: post-comment: @@ -23,9 +21,13 @@ jobs: (github.event_name == 'workflow_dispatch') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write steps: - name: Post initial comment - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.pull_request.number }} with: @@ -79,13 +81,17 @@ jobs: contains(github.event.comment.body, '') && contains(github.event.comment.body, '- [x]') runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + issues: write concurrency: group: social-copy-${{ github.event.issue.number }} cancel-in-progress: true steps: - name: Verify checkbox transition and permissions id: verify - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | // Check that the sender has write access (not an external collaborator or random user) @@ -125,7 +131,7 @@ jobs: - name: Update comment to generating state if: steps.verify.outputs.should_run == 'true' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: COMMENT_ID: ${{ steps.verify.outputs.comment_id }} with: @@ -147,7 +153,7 @@ jobs: - name: Get PR details if: steps.verify.outputs.should_run == 'true' id: pr - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const pr = await github.rest.pulls.get({ @@ -164,10 +170,11 @@ jobs: - name: Checkout repo if: steps.verify.outputs.should_run == 'true' - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ steps.pr.outputs.head_sha }} fetch-depth: 0 + persist-credentials: false - name: Get PR diff if: steps.verify.outputs.should_run == 'true' @@ -307,7 +314,7 @@ jobs: - name: Upload social copy artifact if: steps.verify.outputs.should_run == 'true' && always() && steps.extract.outcome == 'success' id: artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: social-copy-pr${{ github.event.issue.number }} path: .claude-tmp/pr-social-copy.md @@ -315,7 +322,7 @@ jobs: - name: Update comment with results if: steps.verify.outputs.should_run == 'true' && always() && steps.extract.outcome == 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: HEAD_SHA: ${{ steps.pr.outputs.head_sha }} COMMENT_ID: ${{ steps.verify.outputs.comment_id }} @@ -347,7 +354,7 @@ jobs: - name: Update comment on failure if: steps.verify.outputs.should_run == 'true' && always() && steps.extract.outcome != 'success' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: COMMENT_ID: ${{ steps.verify.outputs.comment_id }} with: diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml index e7f39f782c8..7f2d3f13e8d 100644 --- a/.github/workflows/stable-release.yml +++ b/.github/workflows/stable-release.yml @@ -40,9 +40,10 @@ jobs: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 15 + environment: npm steps: - name: Check for existing release PR - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -63,18 +64,19 @@ jobs: } - name: Checkout Repo - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20.x @@ -102,7 +104,7 @@ jobs: - name: Create release PR if: inputs.dry_run != true id: create_pr - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 with: token: ${{ secrets.GITHUB_TOKEN }} branch: release/publish/${{ inputs.scope }}/v${{ steps.prepare.outputs.version }} @@ -147,7 +149,7 @@ jobs: - name: Comment Notion link on PR if: inputs.dry_run != true && steps.ai_notes.outputs.notion_url - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: NOTION_URL: ${{ steps.ai_notes.outputs.notion_url }} PR_NUMBER: ${{ steps.create_pr.outputs.pull-request-number }} diff --git a/.github/workflows/static_check-binaries.yml b/.github/workflows/static_check-binaries.yml index e04e4195590..f8de4e272cb 100644 --- a/.github/workflows/static_check-binaries.yml +++ b/.github/workflows/static_check-binaries.yml @@ -10,20 +10,27 @@ permissions: jobs: check-config-files: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Check build config allowlist run: bash .github/scripts/check-config-allowlist.sh check-binaries: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 + persist-credentials: false - name: Check for binary and build artifacts env: diff --git a/.github/workflows/static_danger.yml b/.github/workflows/static_danger.yml index 658b9a98b32..88701637e95 100644 --- a/.github/workflows/static_danger.yml +++ b/.github/workflows/static_danger.yml @@ -15,22 +15,31 @@ env: NX_CI_EXECUTION_ID: ${{ github.head_ref }}-${{ github.sha }}-${{ github.run_attempt }} NX_CI_EXECUTION_ENV: "Danger" +permissions: + contents: read + jobs: danger: runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: read + # Danger posts review comments on the PR via GITHUB_TOKEN + pull-requests: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Use Node.js 20 - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20.x # setup-node built-in cache is fork-safe (fork PRs can't write to base repo cache) diff --git a/.github/workflows/static_quality.yml b/.github/workflows/static_quality.yml index f8f42e33691..2721b11d27c 100644 --- a/.github/workflows/static_quality.yml +++ b/.github/workflows/static_quality.yml @@ -24,6 +24,9 @@ env: NX_CI_EXECUTION_ID: ${{ github.head_ref }}-${{ github.sha }}-${{ github.run_attempt }} NX_CI_EXECUTION_ENV: "Static Quality" +permissions: + contents: read + jobs: format: runs-on: ubuntu-latest @@ -33,8 +36,9 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: + persist-credentials: false ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} token: ${{ secrets.GITHUB_TOKEN }} # Full history so we can diff HEAD against the current base branch @@ -45,7 +49,11 @@ jobs: run: npm install -g oxfmt@0.36 - name: Install ruff - run: pipx install ruff + # Pin ruff so a compromised or breaking release can't land on the next + # PR run with the persisted-credentials write token in this job. + # Bumped automatically by Dependabot? — no; ruff isn't tracked there. + # Bump manually when needed. + run: pipx install ruff==0.15.13 - name: Collect PR-changed files for formatting if: github.event_name == 'pull_request' @@ -115,6 +123,18 @@ jobs: ruff format --check . fi + - name: Configure git for push + if: >- + env.format_fixed == 'true' && + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local url."https://x-access-token:${TOKEN}@github.com/".insteadOf "https://github.com/" + env: + TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Commit formatting fixes if: >- env.format_fixed == 'true' && @@ -125,8 +145,6 @@ jobs: echo "No formatting changes to commit" exit 0 fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git diff --name-only -z | xargs -0 git add git commit -m "style: auto-fix formatting" git push @@ -134,17 +152,21 @@ jobs: oxlint: runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: read steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Use Node.js 20 - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20.x # setup-node built-in cache is fork-safe (fork PRs can't write to base repo cache) @@ -160,17 +182,21 @@ jobs: package-quality: runs-on: ubuntu-latest timeout-minutes: 10 + permissions: + contents: read steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Use Node.js 20 - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20.x # setup-node built-in cache is fork-safe (fork PRs can't write to base repo cache) @@ -194,20 +220,22 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 permissions: + contents: read pull-requests: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Use Node.js 20 - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 20.x # setup-node built-in cache is fork-safe (fork PRs can't write to base repo cache) @@ -243,7 +271,7 @@ jobs: - name: Post fix suggestion on failure if: github.event_name == 'pull_request' && steps.commitlint.outcome == 'failure' continue-on-error: true - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const fs = require('fs'); diff --git a/.github/workflows/test_e2e-dojo.yml b/.github/workflows/test_e2e-dojo.yml index 8bc36026fd7..30245a2d210 100644 --- a/.github/workflows/test_e2e-dojo.yml +++ b/.github/workflows/test_e2e-dojo.yml @@ -32,15 +32,22 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: detect-changes: runs-on: ubuntu-latest + permissions: + contents: read outputs: ts-changed: ${{ steps.changes.outputs.ts }} python-changed: ${{ steps.changes.outputs.python }} steps: - - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 id: changes with: filters: | @@ -56,6 +63,8 @@ jobs: name: ${{ matrix.suite }} runs-on: depot-ubuntu-24.04 timeout-minutes: 30 + permissions: + contents: read strategy: fail-fast: false matrix: @@ -153,9 +162,13 @@ jobs: - name: Detect fork PR id: fork-check + env: + EVENT_NAME: ${{ github.event_name }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + REPO_FULL: ${{ github.repository }} run: | - if [[ "${{ github.event_name }}" == "pull_request" && \ - "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then + if [[ "$EVENT_NAME" == "pull_request" && \ + "$PR_HEAD_REPO" != "$REPO_FULL" ]]; then echo "prefix=fork-" >> "$GITHUB_OUTPUT" else echo "prefix=" >> "$GITHUB_OUTPUT" @@ -163,31 +176,35 @@ jobs: - name: Checkout CPK if: steps.should-run.outputs.skip != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: lfs: true path: CopilotKit ref: ${{ github.event.inputs.branch || github.ref }} + persist-credentials: false - name: Checkout AGUI if: steps.should-run.outputs.skip != 'true' - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: repository: ag-ui-protocol/ag-ui path: ag-ui ref: main + persist-credentials: false - name: Set up Node.js if: steps.should-run.outputs.skip != 'true' - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "22" - name: Install pnpm + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). if: steps.should-run.outputs.skip != 'true' - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 with: - version: 10.13.1 + package_json_file: CopilotKit/package.json # Now that pnpm is available, cache its store to speed installs - name: Resolve pnpm store path @@ -197,7 +214,7 @@ jobs: - name: Cache pnpm store if: steps.should-run.outputs.skip != 'true' - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ${{ steps.pnpm-store.outputs.STORE_PATH }} key: ${{ steps.fork-check.outputs.prefix }}${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} @@ -208,7 +225,7 @@ jobs: - name: Cache Python dependencies (restore-only) if: steps.should-run.outputs.skip != 'true' && matrix.needs_python id: cache-python - uses: actions/cache/restore@v4 + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: | ~/.cache/pip @@ -221,7 +238,7 @@ jobs: - name: Install Poetry if: steps.should-run.outputs.skip != 'true' && matrix.needs_python - uses: snok/install-poetry@v1 + uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1 with: version: latest virtualenvs-create: true @@ -229,7 +246,7 @@ jobs: - name: Install uv if: steps.should-run.outputs.skip != 'true' && matrix.needs_python - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6 - name: Install cpk dependencies if: steps.should-run.outputs.skip != 'true' @@ -288,7 +305,7 @@ jobs: echo "LANGSMITH_API_KEY=${LANGSMITH_API_KEY}" >> typescript/examples/.env - name: Run dojo+agents - uses: JarvusInnovations/background-action@v1 + uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635 # v1 env: NODE_OPTIONS: --max-old-space-size=8192 OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} @@ -314,7 +331,7 @@ jobs: - name: Upload traces – ${{ matrix.suite }} if: always() && steps.should-run.outputs.skip != 'true' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ matrix.suite }}-playwright-traces path: | diff --git a/.github/workflows/test_e2e-legacy-v1.yml b/.github/workflows/test_e2e-legacy-v1.yml index fbd58d84b50..45e712f2148 100644 --- a/.github/workflows/test_e2e-legacy-v1.yml +++ b/.github/workflows/test_e2e-legacy-v1.yml @@ -26,10 +26,8 @@ env: NX_CI_EXECUTION_ENV: "E2E Examples" # Least-privilege by default. Individual jobs/steps can widen when needed. -# id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). permissions: contents: read - id-token: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -40,6 +38,10 @@ jobs: name: ${{ matrix.example }} runs-on: depot-ubuntu-24.04-4 timeout-minutes: 20 + permissions: + contents: read + # id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). + id-token: write strategy: fail-fast: false matrix: @@ -52,26 +54,31 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: lfs: true ref: ${{ github.event.inputs.branch || github.ref }} + persist-credentials: false - name: Set up Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "22" - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 10.13.1 + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Detect fork PR id: fork-check + env: + EVENT_NAME: ${{ github.event_name }} + PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + REPO_FULL: ${{ github.repository }} run: | - if [[ "${{ github.event_name }}" == "pull_request" && \ - "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then + if [[ "$EVENT_NAME" == "pull_request" && \ + "$PR_HEAD_REPO" != "$REPO_FULL" ]]; then echo "prefix=fork-" >> "$GITHUB_OUTPUT" else echo "prefix=" >> "$GITHUB_OUTPUT" @@ -82,7 +89,7 @@ jobs: run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT - name: Cache pnpm store - uses: actions/cache@v4 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ${{ steps.pnpm-store.outputs.STORE_PATH }} key: ${{ steps.fork-check.outputs.prefix }}${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} @@ -124,7 +131,7 @@ jobs: - name: Upload Playwright artifacts if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: examples-e2e-${{ matrix.example }} path: | diff --git a/.github/workflows/test_e2e-showcase-on-demand.yml b/.github/workflows/test_e2e-showcase-on-demand.yml index ea0011549ee..13fbbc353ae 100644 --- a/.github/workflows/test_e2e-showcase-on-demand.yml +++ b/.github/workflows/test_e2e-showcase-on-demand.yml @@ -120,7 +120,7 @@ jobs: - name: Resolve PR HEAD ref id: pr-ref if: github.event_name == 'issue_comment' - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const { data: pr } = await github.rest.pulls.get({ @@ -139,7 +139,7 @@ jobs: core.setOutput('ref', pr.head.sha); core.setOutput('pr_number', pr.number); - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ steps.pr-ref.outputs.ref || github.sha }} # Do NOT leave the workflow's GITHUB_TOKEN in `.git/config` after @@ -150,13 +150,13 @@ jobs: # depth cheap — disable credential persistence. persist-credentials: false - - uses: actions/setup-node@v4 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22.x - - uses: pnpm/action-setup@v4.4.0 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Determine slug id: slug @@ -325,7 +325,7 @@ jobs: - name: Setup Python agent if: steps.pkg-type.outputs.has_python == 'true' - uses: actions/setup-python@v5 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" # Cache pip to avoid reinstalling CrewAI's heavy transitive dep @@ -501,7 +501,7 @@ jobs: - name: Upload test artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: playwright-report-${{ steps.slug.outputs.slug }} path: showcase/integrations/${{ steps.slug.outputs.slug }}/playwright-report/ @@ -526,7 +526,7 @@ jobs: issues: write steps: - name: Post result to PR - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 # Pass dynamic values through env (NOT `${{ ... }}` interpolation into # the script body). Even though the slug is whitelisted upstream, the # env-var pattern is the defensive default: any future additions that diff --git a/.github/workflows/test_integration-docs.yml b/.github/workflows/test_integration-docs.yml index 7c4a9896573..d71ad027bb8 100644 --- a/.github/workflows/test_integration-docs.yml +++ b/.github/workflows/test_integration-docs.yml @@ -8,19 +8,23 @@ on: paths: ["docs/**"] # Least-privilege by default. Individual jobs/steps can widen when needed. -# id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). permissions: contents: read - id-token: write jobs: validate-model-names: runs-on: depot-ubuntu-24.04-4 timeout-minutes: 15 + permissions: + contents: read + # id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). + id-token: write steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 cache: pnpm @@ -31,20 +35,26 @@ jobs: runs-on: depot-ubuntu-24.04-4 timeout-minutes: 15 needs: validate-model-names + permissions: + contents: read + # id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). + id-token: write steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: actions/setup-node@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + - uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 cache: pnpm - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" - run: pnpm install --frozen-lockfile - name: Install and start aimock run: | - npm install -g @copilotkit/aimock@latest + npm install -g @copilotkit/aimock@1.24.1 AIMOCK_BIN=$(npm root -g)/../bin/aimock echo "aimock binary: $AIMOCK_BIN" ls -la "$AIMOCK_BIN" || echo "binary not found at expected path" diff --git a/.github/workflows/test_integration-runtime.yml b/.github/workflows/test_integration-runtime.yml index b87b836335b..3ecd9935f20 100644 --- a/.github/workflows/test_integration-runtime.yml +++ b/.github/workflows/test_integration-runtime.yml @@ -20,10 +20,8 @@ on: type: string # Least-privilege by default. Individual jobs/steps can widen when needed. -# id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). permissions: contents: read - id-token: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -34,19 +32,24 @@ jobs: name: "runtime / node" runs-on: depot-ubuntu-24.04-4 timeout-minutes: 15 + permissions: + contents: read + # id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). + id-token: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ github.event.inputs.branch || github.ref }} + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Use Node.js 22.x - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "22.x" @@ -71,24 +74,29 @@ jobs: name: "runtime / bun" runs-on: depot-ubuntu-24.04-4 timeout-minutes: 15 + permissions: + contents: read + # id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). + id-token: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ github.event.inputs.branch || github.ref }} + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Use Node.js 22.x - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "22.x" - name: Setup Bun - uses: oven-sh/setup-bun@v2 + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: latest diff --git a/.github/workflows/test_smoke-starter.yml b/.github/workflows/test_smoke-starter.yml index 70a6105254b..0b3355548cf 100644 --- a/.github/workflows/test_smoke-starter.yml +++ b/.github/workflows/test_smoke-starter.yml @@ -41,10 +41,11 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 1 lfs: false + persist-credentials: false - name: Run starter smoke tests working-directory: examples/integrations/${{ matrix.starter }} @@ -120,7 +121,7 @@ jobs: - name: Upload test artifacts on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: smoke-starter-${{ matrix.starter }} path: showcase/tests/test-results/ @@ -161,7 +162,7 @@ jobs: - name: Alert Slack on failure if: failure() && env.SLACK_WEBHOOK != '' && (github.event_name == 'schedule' || github.event_name == 'workflow_run') - uses: slackapi/slack-github-action@v2.1.0 + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }} webhook-type: incoming-webhook diff --git a/.github/workflows/test_unit-python-sdk.yml b/.github/workflows/test_unit-python-sdk.yml index 4b34e99ff8b..cdc77f84974 100644 --- a/.github/workflows/test_unit-python-sdk.yml +++ b/.github/workflows/test_unit-python-sdk.yml @@ -13,10 +13,8 @@ on: - ".github/workflows/test_unit-python-sdk.yml" # Least-privilege by default. Individual jobs/steps can widen when needed. -# id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). permissions: contents: read - id-token: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -26,20 +24,26 @@ jobs: test: runs-on: depot-ubuntu-24.04-4 timeout-minutes: 10 + permissions: + contents: read + # id-token: write is required for Depot OIDC auth (runs-on: depot-ubuntu-*). + id-token: write strategy: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} - name: Install Poetry - uses: snok/install-poetry@v1 + uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1 with: version: latest virtualenvs-create: true diff --git a/.github/workflows/test_unit.yml b/.github/workflows/test_unit.yml index 06f0d2658e9..641c1328879 100644 --- a/.github/workflows/test_unit.yml +++ b/.github/workflows/test_unit.yml @@ -51,17 +51,18 @@ jobs: node-version: [20.x, 22.x, 24.x] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ github.event.inputs.branch || github.ref }} + persist-credentials: false - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: "10.13.1" + # Omit `version:` so pnpm/action-setup inherits from the repo's + # `packageManager` field in package.json (via corepack). + uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ matrix.node-version }} # setup-node built-in cache is fork-safe (fork PRs can't write to base repo cache) diff --git a/.github/workflows/update-branch.yml b/.github/workflows/update-branch.yml index bb1c02742c2..860f7c36381 100644 --- a/.github/workflows/update-branch.yml +++ b/.github/workflows/update-branch.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Update PR branch with base - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const { owner, repo } = context.repo; diff --git a/.github/zizmor.yml b/.github/zizmor.yml new file mode 100644 index 00000000000..16387d38893 --- /dev/null +++ b/.github/zizmor.yml @@ -0,0 +1,92 @@ +# zizmor configuration — static analysis for GitHub Actions workflows. +# +# Docs: https://docs.zizmor.sh/configuration/ +# +# This file lives at `.github/zizmor.yml` because zizmor auto-discovers +# it there. The companion workflow at `.github/workflows/security_zizmor.yml` +# runs zizmor in CI and fails the build on findings at the configured level. +# +# When a finding genuinely cannot be remediated (e.g. the action behavior +# is correct but tripped a check), add it under `rules:` with a clear +# justification comment — never blanket-suppress. + +rules: + # `template-injection` flags ${{ }} expansions that put untrusted input + # (issue titles, PR titles, branch names, comment bodies) directly into + # `run:` scripts. The fix is always to route through `env:` and reference + # the env var inside the shell. We treat any new occurrence as a blocker. + template-injection: + ignore: [] + + # `dangerous-triggers` flags `pull_request_target` and similar triggers + # that run with secrets against fork-controlled refs. Anything new must + # be reviewed by a maintainer. + # + # Suppressions below were each reviewed by a maintainer; new additions + # must come with a comment explaining why the workflow is safe. + dangerous-triggers: + ignore: + # update-branch.yml: pull_request_target gated on `types: [labeled]` + # with a maintainer-applied "update-branch" label. The workflow uses + # the base-repo checkout (not the PR head), so no fork-controlled + # code runs with the elevated token. This is the documented safe + # pattern for label-gated automation. + - update-branch.yml + # showcase_deploy.yml / showcase_capture-previews.yml: workflow_run + # triggered by the in-repo "Showcase: Build & Push" workflow. Only + # main-branch builds fire workflow_run, and the downstream workflows + # never check out PR-head code — they checkout the same trusted + # main-branch ref or use the head_branch of the triggering run + # (which is also main per the upstream filter). + - showcase_deploy.yml + - showcase_capture-previews.yml + # Dependabot auto-merge: uses pull_request_target for write token. + # Does NOT checkout PR code. Actor-gated to dependabot[bot]. + - dependabot-auto-merge.yml + # Dependabot major analysis: uses pull_request_target for PR comments. + # Does NOT checkout PR code. Actor-gated to dependabot[bot]. + - dependabot-major-analysis.yml + # test_smoke-starter.yml: workflow_run triggered by the in-repo + # "publish / release" workflow. Listens to release completions; no + # fork-controlled refs reach this workflow. + - test_smoke-starter.yml + + # `unpinned-uses` is satisfied because every `uses:` is SHA-pinned and + # Dependabot keeps them current (see .github/dependabot.yml). + + # `excessive-permissions` flags jobs that grant token scopes they don't + # need. We surface but don't auto-suppress — fixing requires per-job + # `permissions:` blocks that match the actual API calls in the job. + excessive-permissions: + ignore: [] + + # `artipacked` flags `actions/checkout` calls that leave the default + # GITHUB_TOKEN persisted in the local git config. Every other checkout + # in the repo sets `persist-credentials: false`. The suppressions below + # are the workflows that legitimately need the persisted token to push + # back to the repo (release tagging, docs sync, PR comment from + # pkg-pr-new). Each call site carries a `persist-credentials required:` + # comment immediately above it. + # + # static_quality.yml was removed: its format job now uses + # persist-credentials: false and injects credentials via insteadOf + # only before the push step. + artipacked: + ignore: + # Release workflows that push tags / branches via GITHUB_TOKEN. + # publish-release.yml is intentionally NOT suppressed: its build job + # now uses persist-credentials: false (the artifact it uploads would + # otherwise leak a workflow-scoped token through .git/config) and the + # publish job downloads the artifact and sets up its own git auth via + # `git config insteadOf`. + - prerelease.yml + # pkg-pr-new posts snapshot comments on the PR using the repo token + # — credentials must remain persisted for the action to authenticate. + - publish-commit.yml + + # dependabot-cooldown: cooldown configured (default-days: 1) but zizmor + # wants longer. Migrating to Renovate — suppressing rather than tuning + # Dependabot config that will be removed. + dependabot-cooldown: + ignore: + - dependabot.yml diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000..14c0d398a3c --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +minimum-release-age=1440 +block-exotic-subdeps=true diff --git a/examples/canvas/gemini/package.json b/examples/canvas/gemini/package.json index 63200fb63fd..ab6e4fc138a 100644 --- a/examples/canvas/gemini/package.json +++ b/examples/canvas/gemini/package.json @@ -51,7 +51,7 @@ "clsx": "^2.1.1", "cmdk": "1.0.4", "concurrently": "^9.2.1", - "copilotkit": "^0.0.45", + "copilotkit": "^2.0.0", "date-fns": "4.1.0", "embla-carousel-react": "8.5.1", "geist": "^1.3.1", diff --git a/examples/integrations/agent-spec/package.json b/examples/integrations/agent-spec/package.json index ac0e32ba955..a1930f6379d 100644 --- a/examples/integrations/agent-spec/package.json +++ b/examples/integrations/agent-spec/package.json @@ -21,12 +21,12 @@ "@ag-ui/core": "^0.0.46", "@ag-ui/encoder": "^0.0.46", "@ag-ui/proto": "^0.0.46", - "@copilotkit/a2ui-renderer": "0.0.0-mme-ag-ui-0-0-46-20260227141603", - "@copilotkit/react-core": "0.0.0-mme-ag-ui-0-0-46-20260227141603", - "@copilotkit/react-ui": "0.0.0-mme-ag-ui-0-0-46-20260227141603", - "@copilotkit/runtime": "0.0.0-mme-ag-ui-0-0-46-20260227141603", - "@copilotkit/runtime-client-gql": "0.0.0-mme-ag-ui-0-0-46-20260227141603", - "@copilotkit/shared": "0.0.0-mme-ag-ui-0-0-46-20260227141603", + "@copilotkit/a2ui-renderer": "1.57.1", + "@copilotkit/react-core": "1.57.1", + "@copilotkit/react-ui": "1.57.1", + "@copilotkit/runtime": "1.57.1", + "@copilotkit/runtime-client-gql": "1.57.1", + "@copilotkit/shared": "1.57.1", "hono": "^4.11.4", "next": "16.0.10", "react": "^19.2.3", diff --git a/package.json b/package.json index 86495c8d620..661f79f7225 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "engines": { "node": ">=18" }, - "packageManager": "pnpm@10.13.1", + "packageManager": "pnpm@10.33.4", "pnpm": { "peerDependencyRules": { "ignoreMissing": [ diff --git a/packages/runtime/src/v2/runtime/handlers/handle-transcribe.ts b/packages/runtime/src/v2/runtime/handlers/handle-transcribe.ts index 088f0fdcd79..03ba4757962 100644 --- a/packages/runtime/src/v2/runtime/handlers/handle-transcribe.ts +++ b/packages/runtime/src/v2/runtime/handlers/handle-transcribe.ts @@ -87,7 +87,7 @@ async function extractAudioFromFormData( request: Request, ): Promise<{ file: File } | { error: Response }> { const formData = await request.formData(); - const audioFile = formData.get("audio") as File | null; + let audioFile = formData.get("audio") as File | null; if (!audioFile || !(audioFile instanceof File)) { const err = TranscriptionErrors.invalidRequest( @@ -104,6 +104,19 @@ async function extractAudioFromFormData( return { error: createErrorResponse(err) }; } + // Browser MediaRecorder Blobs often arrive at the server with their + // `type` field empty (and `application/octet-stream` is also accepted by + // isValidAudioType for compatibility). OpenAI Whisper rejects files + // without a recognizable MIME / extension with a 502 "Invalid file + // format" — even though the bytes themselves are valid WebM Opus. + // Stamp `audio/webm` (the standard MediaRecorder default in Chromium / + // Firefox / Safari) so Whisper can pick the right decoder. + if (!audioFile.type || audioFile.type === "application/octet-stream") { + audioFile = new File([audioFile], audioFile.name || "recording.webm", { + type: "audio/webm", + }); + } + return { file: audioFile }; } diff --git a/packages/web-inspector/vitest.config.ts b/packages/web-inspector/vitest.config.ts index eb33e8aacb7..0ea5419fe28 100644 --- a/packages/web-inspector/vitest.config.ts +++ b/packages/web-inspector/vitest.config.ts @@ -5,6 +5,7 @@ export default defineConfig({ environment: "jsdom", globals: true, clearMocks: true, + setupFiles: ["./vitest.setup.ts"], reporters: [["default", { summary: false }]], silent: true, }, diff --git a/packages/web-inspector/vitest.setup.ts b/packages/web-inspector/vitest.setup.ts new file mode 100644 index 00000000000..bb3f377fbdf --- /dev/null +++ b/packages/web-inspector/vitest.setup.ts @@ -0,0 +1,42 @@ +// Node 22.4+ ships an experimental built-in `localStorage` global that is +// installed before vitest's jsdom environment runs, leaving `window.localStorage` +// as an uninitialized stub (no `getItem`/`setItem`/`clear` methods). Replace it +// with a minimal in-memory Storage shim so telemetry persistence tests behave +// the same on Node 18/20/22/25+ without depending on `--no-experimental-webstorage`. + +class MemoryStorage implements Storage { + private store = new Map(); + + get length(): number { + return this.store.size; + } + + clear(): void { + this.store.clear(); + } + + getItem(key: string): string | null { + const k = String(key); + return this.store.has(k) ? this.store.get(k)! : null; + } + + setItem(key: string, value: string): void { + this.store.set(String(key), String(value)); + } + + removeItem(key: string): void { + this.store.delete(String(key)); + } + + key(index: number): string | null { + return Array.from(this.store.keys())[index] ?? null; + } +} + +for (const name of ["localStorage", "sessionStorage"] as const) { + Object.defineProperty(globalThis, name, { + value: new MemoryStorage(), + writable: true, + configurable: true, + }); +} diff --git a/scripts/release/bump-prerelease.ts b/scripts/release/bump-prerelease.ts new file mode 100644 index 00000000000..08227d6af36 --- /dev/null +++ b/scripts/release/bump-prerelease.ts @@ -0,0 +1,54 @@ +/** + * Bump package versions for a prerelease (runs in the secrets-free build job). + * + * This is extracted from prerelease.ts so that version bumping happens before + * the build, in a job that has no access to NPM_TOKEN or other publish secrets. + * The publish job then receives pre-built, correctly-versioned artifacts. + * + * Usage: tsx scripts/release/bump-prerelease.ts --scope [--suffix