From 01831a54bfcf8c1c276506f8cf341eab1c8c6eea Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 14:55:47 +0000 Subject: [PATCH 1/5] ci: add zizmor GitHub Actions security analysis Co-Authored-By: cbro --- .github/workflows/zizmor.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 000000000..3929dd35f --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,24 @@ +name: GitHub Actions Security Analysis with zizmor + +on: + push: + branches: ["main"] + pull_request: + branches: ["**"] + +permissions: {} + +jobs: + zizmor: + name: Run zizmor + runs-on: ubuntu-latest + permissions: + security-events: write # Required to upload SARIF results to code scanning. + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 From 644099da562f29368678304c868874d56e5c2cf5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 15:01:23 +0000 Subject: [PATCH 2/5] ci: apply zizmor auto-fixes (SHA pin actions, persist-credentials false, env-var indirection) Co-Authored-By: cbro --- .../workflows/auto-merge-on-docs-release.yml | 4 +- .github/workflows/check-devin-pr-assignee.yml | 2 +- .github/workflows/check-links.yml | 47 ++++++++++++------- .github/workflows/fern-scribe.yml | 11 +++-- .github/workflows/preview-docs.yml | 19 +++++--- .github/workflows/publish-docs.yml | 6 ++- .github/workflows/stale-bot.yml | 2 +- .github/workflows/update-versions.yml | 10 ++-- .github/workflows/vale.yml | 8 ++-- 9 files changed, 67 insertions(+), 42 deletions(-) diff --git a/.github/workflows/auto-merge-on-docs-release.yml b/.github/workflows/auto-merge-on-docs-release.yml index a79dad075..91ca21ec8 100644 --- a/.github/workflows/auto-merge-on-docs-release.yml +++ b/.github/workflows/auto-merge-on-docs-release.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Find and merge dependent PRs - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | const version = context.payload.client_payload.version; @@ -47,4 +47,4 @@ jobs: console.log(`Merged PR #${pr.number}: ${pr.title}`); } } - } \ No newline at end of file + } diff --git a/.github/workflows/check-devin-pr-assignee.yml b/.github/workflows/check-devin-pr-assignee.yml index ccba24823..9d9af64ab 100644 --- a/.github/workflows/check-devin-pr-assignee.yml +++ b/.github/workflows/check-devin-pr-assignee.yml @@ -13,7 +13,7 @@ jobs: if: ${{ github.event.pull_request.user.login == 'devin-ai-integration[bot]' }} steps: - name: Auto-assign requester from PR description - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index 2ddf259d9..c2491b01a 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -16,7 +16,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + persist-credentials: false - name: Create lychee config run: | @@ -380,7 +382,7 @@ jobs: - name: Upload URLs (early, for debugging) - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: urls path: | @@ -392,7 +394,7 @@ jobs: - name: Check GitHub links (very low concurrency to avoid 503 rate limiting) id: lychee_github if: steps.extract_github_http.outputs.github_http_count != '0' - uses: lycheeverse/lychee-action@v2 + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2 with: args: >- --no-progress @@ -408,7 +410,7 @@ jobs: - name: Check non-GitHub links (high concurrency) id: lychee_main - uses: lycheeverse/lychee-action@v2 + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2 with: args: >- --config lychee.toml @@ -600,15 +602,15 @@ jobs: broken_count=$(wc -l < broken-links.txt | tr -d ' ') # Get rate limit stats - rate_limited="${{ steps.retry429.outputs.rate_limited_count }}" + rate_limited="${STEPS_RETRY429_OUTPUTS_RATE_LIMITED_COUNT}" rate_limited=${rate_limited:-0} - still_failing_429="${{ steps.retry429.outputs.still_failing_429 }}" + still_failing_429="${STEPS_RETRY429_OUTPUTS_STILL_FAILING_429}" still_failing_429=${still_failing_429:-0} # Get GitHub local verification stats - github_verified="${{ steps.verify_github.outputs.verified_count }}" + github_verified="${STEPS_VERIFY_GITHUB_OUTPUTS_VERIFIED_COUNT}" github_verified=${github_verified:-0} - github_missing="${{ steps.verify_github.outputs.missing_count }}" + github_missing="${STEPS_VERIFY_GITHUB_OUTPUTS_MISSING_COUNT}" github_missing=${github_missing:-0} # Build clean errors-only report @@ -682,9 +684,9 @@ jobs: cat lychee-summary-table.md echo "" # Get repo-internal GitHub URL counts - verified_locally="${{ steps.retry429.outputs.verified_locally }}" + verified_locally="${STEPS_RETRY429_OUTPUTS_VERIFIED_LOCALLY}" verified_locally=${verified_locally:-0} - missing_locally="${{ steps.retry429.outputs.missing_locally }}" + missing_locally="${STEPS_RETRY429_OUTPUTS_MISSING_LOCALLY}" missing_locally=${missing_locally:-0} echo "Recovery Info:" @@ -763,10 +765,17 @@ jobs: echo "" } >> "$GITHUB_STEP_SUMMARY" + env: + STEPS_RETRY429_OUTPUTS_RATE_LIMITED_COUNT: ${{ steps.retry429.outputs.rate_limited_count }} + STEPS_RETRY429_OUTPUTS_STILL_FAILING_429: ${{ steps.retry429.outputs.still_failing_429 }} + STEPS_VERIFY_GITHUB_OUTPUTS_VERIFIED_COUNT: ${{ steps.verify_github.outputs.verified_count }} + STEPS_VERIFY_GITHUB_OUTPUTS_MISSING_COUNT: ${{ steps.verify_github.outputs.missing_count }} + STEPS_RETRY429_OUTPUTS_VERIFIED_LOCALLY: ${{ steps.retry429.outputs.verified_locally }} + STEPS_RETRY429_OUTPUTS_MISSING_LOCALLY: ${{ steps.retry429.outputs.missing_locally }} - name: Upload errors-only report if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: lychee-report path: ./lychee-report.md @@ -774,7 +783,7 @@ jobs: - name: Upload lychee outputs and verification results if: always() - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: lychee-outputs path: | @@ -787,7 +796,7 @@ jobs: - name: Create PR for broken links id: create-pr if: steps.check_failures.outputs.has_other_failures == 'true' || steps.retry429.outputs.has_429_failures == 'true' || steps.verify_github.outputs.has_missing == 'true' - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: DEVIN_PROMPT: | @devin-ai-integration Please fix the broken links detected by the scheduled link checker. @@ -1042,7 +1051,7 @@ jobs: - name: Send Slack notification for broken links if: steps.create-pr.outputs.pr_created == 'true' - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: SLACK_TOKEN: ${{ secrets.DEVIN_AI_PR_BOT_SLACK_TOKEN }} PR_URL: ${{ steps.create-pr.outputs.pr_url }} @@ -1085,14 +1094,18 @@ jobs: if: steps.check_failures.outputs.has_other_failures == 'true' || steps.retry429.outputs.has_429_failures == 'true' || steps.verify_github.outputs.has_missing == 'true' run: | echo "Link check failed!" - if [ "${{ steps.check_failures.outputs.has_other_failures }}" == "true" ]; then + if [ "${STEPS_CHECK_FAILURES_OUTPUTS_HAS_OTHER_FAILURES}" == "true" ]; then echo "There are broken links (non-429 failures) in the report." fi - if [ "${{ steps.retry429.outputs.has_429_failures }}" == "true" ]; then + if [ "${STEPS_RETRY429_OUTPUTS_HAS_429_FAILURES}" == "true" ]; then echo "Some URLs still returned 429 after exponential backoff retry." echo "These URLs may need to be excluded or the rate limit needs more time to reset." fi - if [ "${{ steps.verify_github.outputs.has_missing }}" == "true" ]; then + if [ "${STEPS_VERIFY_GITHUB_OUTPUTS_HAS_MISSING}" == "true" ]; then echo "Some GitHub URLs point to paths that don't exist in the repos." fi exit 1 + env: + STEPS_CHECK_FAILURES_OUTPUTS_HAS_OTHER_FAILURES: ${{ steps.check_failures.outputs.has_other_failures }} + STEPS_RETRY429_OUTPUTS_HAS_429_FAILURES: ${{ steps.retry429.outputs.has_429_failures }} + STEPS_VERIFY_GITHUB_OUTPUTS_HAS_MISSING: ${{ steps.verify_github.outputs.has_missing }} diff --git a/.github/workflows/fern-scribe.yml b/.github/workflows/fern-scribe.yml index cc4888039..1881eda78 100644 --- a/.github/workflows/fern-scribe.yml +++ b/.github/workflows/fern-scribe.yml @@ -16,12 +16,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: token: ${{ secrets.GITHUB_TOKEN }} + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 with: node-version: '20' cache: 'npm' @@ -62,7 +63,7 @@ jobs: - name: Comment on issue if: success() - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | github.rest.issues.createComment({ @@ -74,7 +75,7 @@ jobs: - name: Comment on failure if: failure() - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | github.rest.issues.createComment({ @@ -82,4 +83,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: '❌ **Fern Scribe encountered an error**\n\nThere was an issue processing your documentation request. Please check the action logs and try again.\n\nIf the problem persists, please contact the maintainers.' - }); \ No newline at end of file + }); diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml index c8e4591d7..195c86dc5 100644 --- a/.github/workflows/preview-docs.yml +++ b/.github/workflows/preview-docs.yml @@ -15,9 +15,10 @@ jobs: contents: read # For checking out code steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: fetch-depth: 0 # Fetch full history for git diff + persist-credentials: false - name: Checkout PR run: | @@ -25,7 +26,7 @@ jobs: git checkout pr-${{ github.event.pull_request.number }} - name: Setup Fern CLI - uses: fern-api/setup-fern-cli@v1 + uses: fern-api/setup-fern-cli@d07601425e9c9ede8745d71ca75c4c462d98755d # v1 - name: Generate preview URL id: generate-docs @@ -43,8 +44,9 @@ jobs: id: page-links env: FERN_TOKEN: ${{ secrets.FERN_TOKEN }} + STEPS_GENERATE_DOCS_OUTPUTS_PREVIEW_URL: ${{ steps.generate-docs.outputs.preview_url }} run: | - PREVIEW_URL="${{ steps.generate-docs.outputs.preview_url }}" + PREVIEW_URL="${STEPS_GENERATE_DOCS_OUTPUTS_PREVIEW_URL}" CHANGED_FILES=$(git diff --name-only origin/main...HEAD -- '*.mdx' 2>/dev/null || echo "") if [ -z "$CHANGED_FILES" ] || [ -z "$PREVIEW_URL" ]; then @@ -69,16 +71,19 @@ jobs: - name: Create comment content run: | - echo ":herb: **Preview your docs:** <${{ steps.generate-docs.outputs.preview_url }}>" > comment.md + echo ":herb: **Preview your docs:** <${STEPS_GENERATE_DOCS_OUTPUTS_PREVIEW_URL}>" > comment.md - if [ -n "${{ steps.page-links.outputs.page_links }}" ]; then + if [ -n "${STEPS_PAGE_LINKS_OUTPUTS_PAGE_LINKS}" ]; then echo "" >> comment.md echo "Here are the markdown pages you've updated:" >> comment.md - echo "${{ steps.page-links.outputs.page_links }}" >> comment.md + echo "${STEPS_PAGE_LINKS_OUTPUTS_PAGE_LINKS}" >> comment.md fi + env: + STEPS_GENERATE_DOCS_OUTPUTS_PREVIEW_URL: ${{ steps.generate-docs.outputs.preview_url }} + STEPS_PAGE_LINKS_OUTPUTS_PAGE_LINKS: ${{ steps.page-links.outputs.page_links }} - name: Post PR comment - uses: thollander/actions-comment-pull-request@v2.4.3 + uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # v2.4.3 with: filePath: comment.md comment_tag: preview-docs diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 2099eb158..7ad73f377 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -11,10 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + persist-credentials: false - name: Setup Fern CLI - uses: fern-api/setup-fern-cli@v1 + uses: fern-api/setup-fern-cli@d07601425e9c9ede8745d71ca75c4c462d98755d # v1 - name: Publish Docs env: diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml index 49974df86..1a92d13ea 100644 --- a/.github/workflows/stale-bot.yml +++ b/.github/workflows/stale-bot.yml @@ -12,7 +12,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v10 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10 with: stale-pr-message: 'This PR is stale because it has been open 25 days with no activity. Remove stale label or comment or this will be closed in 5 days.' close-pr-message: 'This PR was closed because it has been inactive for 5 days after being marked stale.' diff --git a/.github/workflows/update-versions.yml b/.github/workflows/update-versions.yml index 71aaec8a7..07030914a 100644 --- a/.github/workflows/update-versions.yml +++ b/.github/workflows/update-versions.yml @@ -9,9 +9,10 @@ jobs: update-versions: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: ref: main + persist-credentials: false - name: update-csharp-version run: curl -s "https://registry.hub.docker.com/v2/repositories/fernapi/fern-csharp-sdk/tags" | jq -r -j '[.results[] | select(.name != "latest" and .name != "AUTO")] | .[0].name' > fern/snippets/version-number-csharp.mdx - name: update-go-version @@ -36,7 +37,7 @@ jobs: run: curl -s https://api.github.com/repos/fern-api/fern/releases/latest | jq -r -j '.tag_name' > fern/snippets/version-number-cli.mdx - name: create PR id: cpr - uses: peter-evans/create-pull-request@v8 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 with: commit-message: "update versions from docker hub" title: "Update versions from docker hub" @@ -45,7 +46,7 @@ jobs: delete-branch: true - name: Enable Pull Request Automerge if: steps.cpr.outputs.pull-request-operation == 'created' - uses: peter-evans/enable-pull-request-automerge@v3 + uses: peter-evans/enable-pull-request-automerge@a660677d5469627102a1c1e11409dd063606628d # v3 with: pull-request-number: ${{ steps.cpr.outputs.pull-request-number }} merge-method: squash @@ -53,6 +54,7 @@ jobs: if: steps.cpr.outputs.pull-request-operation == 'created' env: GH_TOKEN: ${{ secrets.FERN_GITHUB_TOKEN }} + STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER: ${{ steps.cpr.outputs.pull-request-number }} run: | echo "Approving PR" - gh pr review ${{ steps.cpr.outputs.pull-request-number }} --approve + gh pr review ${STEPS_CPR_OUTPUTS_PULL_REQUEST_NUMBER} --approve diff --git a/.github/workflows/vale.yml b/.github/workflows/vale.yml index c343c4c39..03b1b7280 100644 --- a/.github/workflows/vale.yml +++ b/.github/workflows/vale.yml @@ -15,11 +15,13 @@ jobs: github.event.pull_request.user.login != 'fern-support' && github.event.pull_request.user.login != 'github-actions' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + with: + persist-credentials: false - name: Get changed files id: changed-files - uses: tj-actions/changed-files@v47 + uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47 with: files: | fern/**/*.md @@ -34,4 +36,4 @@ jobs: reporter: github-pr-review fail_on_error: false env: - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} From 2f084bb5af586031dfa89db67756292ee9a20798 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 15:12:36 +0000 Subject: [PATCH 3/5] ci(dependabot): keep GitHub Actions pinned SHAs up to date (7d cooldown) Co-Authored-By: cbro --- .github/dependabot.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..72cf0f1ac --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,21 @@ +# Configures Dependabot to keep our GitHub Actions pinned references up to date. +# Other ecosystems are intentionally not enabled here. +# +# Pair with the `unpinned-uses` policy enforced by `.github/workflows/zizmor.yml`: +# zizmor requires actions to be pinned to a full-length commit SHA with the +# version tag as a trailing comment. Dependabot will keep both in sync. +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + cooldown: + default-days: 7 + open-pull-requests-limit: 10 + commit-message: + prefix: "ci" + include: "scope" + labels: + - "dependencies" + - "github-actions" From 37f6c7398a126792ce3ff030418e55cdc1d53aad Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 18:26:03 +0000 Subject: [PATCH 4/5] ci(vale): pin errata-ai/vale-action to v2.1.1 SHA (was @reviewdog branch) Co-Authored-By: cbro --- .github/workflows/vale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/vale.yml b/.github/workflows/vale.yml index 03b1b7280..619b2592e 100644 --- a/.github/workflows/vale.yml +++ b/.github/workflows/vale.yml @@ -28,7 +28,7 @@ jobs: fern/**/*.mdx files_ignore: | **/changelog/** - - uses: errata-ai/vale-action@reviewdog + - uses: errata-ai/vale-action@d89dee975228ae261d22c15adcd03578634d429c # v2.1.1 if: steps.changed-files.outputs.any_changed == 'true' with: files: ${{ steps.changed-files.outputs.all_changed_files }} From 3ba83a139f8d9baacd2609fe711a6cff4a808b6b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 21:04:16 +0000 Subject: [PATCH 5/5] ci: swap preview-docs trigger from pull_request_target to pull_request Resolves zizmor's dangerous-triggers finding. The same-repo guard (if: head.repo.full_name == github.repository) is retained as belt-and-braces, even though pull_request from a fork wouldn't receive secrets anyway. Co-Authored-By: cbro --- .github/workflows/preview-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preview-docs.yml b/.github/workflows/preview-docs.yml index 195c86dc5..d5d8263ed 100644 --- a/.github/workflows/preview-docs.yml +++ b/.github/workflows/preview-docs.yml @@ -1,7 +1,7 @@ name: Preview Docs on: - pull_request_target: + pull_request: types: [opened, synchronize, ready_for_review] branches: - main