From ad9a2adfdd4b147555e352b91a834af91d61ee26 Mon Sep 17 00:00:00 2001 From: pplx-teammatebot Date: Mon, 3 Aug 2026 13:39:16 +0000 Subject: [PATCH 1/2] Harden GitHub Actions credential handling --- .github/workflows/pr-validation.yml | 23 ++- .github/workflows/sync-to-docs.yml | 81 ++++++--- package-lock.json | 244 +++++++++++++++++++++++++++- package.json | 2 +- 4 files changed, 319 insertions(+), 31 deletions(-) diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index f38c33c..0f7f7ed 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -6,22 +6,35 @@ on: paths: - 'docs/**' - '.github/workflows/**' + - 'package.json' + - 'package-lock.json' + - 'scripts/validate-mdx.js' + +permissions: + contents: read + +concurrency: + group: mdx-validation-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: mdx-validation: + name: Validate MDX runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + persist-credentials: false - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: '18' + node-version: '22' cache: 'npm' - name: MDX validation dependencies - run: npm install --save-dev @mdx-js/mdx @mdx-js/loader glob + run: npm ci --ignore-scripts - name: Validate MDX files run: node scripts/validate-mdx.js @@ -45,4 +58,4 @@ jobs: exit 1 fi echo "✅ $file - Has frontmatter" - done \ No newline at end of file + done diff --git a/.github/workflows/sync-to-docs.yml b/.github/workflows/sync-to-docs.yml index 8c0ed5b..9eed96e 100644 --- a/.github/workflows/sync-to-docs.yml +++ b/.github/workflows/sync-to-docs.yml @@ -5,33 +5,48 @@ on: branches: [ main ] workflow_dispatch: +permissions: {} + +concurrency: + group: cookbook-sync + cancel-in-progress: false + jobs: sync-cookbook: + name: Sync cookbook runs-on: ubuntu-latest + permissions: + contents: read + outputs: + changes_made: ${{ steps.commit.outputs.changes_made }} steps: - name: Checkout cookbook repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: + persist-credentials: false path: cookbook-repo - name: Checkout docs repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: - repository: ${{ secrets.DOCS_REPO_NAME || 'ppl-ai/api-docs' }} + repository: ppl-ai/api-docs token: ${{ secrets.DOCS_REPO_TOKEN }} + persist-credentials: false path: docs-repo - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: '18' + node-version: '22' cache: 'npm' - cache-dependency-path: docs-repo/package.json + cache-dependency-path: docs-repo/package-lock.json - name: Install docs dependencies + env: + HUSKY: '0' run: | cd docs-repo - npm install + npm ci - name: Clear existing cookbook content run: | @@ -48,7 +63,7 @@ jobs: # Copy static assets if they exist if [ -d "cookbook-repo/static" ]; then mkdir -p docs-repo/cookbook/static - cp -r cookbook-repo/static/* docs-repo/cookbook/static/ + cp -r cookbook-repo/static/. docs-repo/cookbook/static/ fi - name: Generate cookbook navigation @@ -64,26 +79,42 @@ jobs: git config --local user.name "Cookbook Sync Bot" - name: Commit and push changes + id: commit + env: + DOCS_REPO_TOKEN: ${{ secrets.DOCS_REPO_TOKEN }} + SOURCE_REPOSITORY: ${{ github.repository }} + SOURCE_SERVER_URL: ${{ github.server_url }} + SOURCE_SHA: ${{ github.sha }} run: | cd docs-repo git add . if git diff --staged --quiet; then echo "No changes to commit" - echo "CHANGES_MADE=false" >> $GITHUB_ENV + echo "changes_made=false" >> "$GITHUB_OUTPUT" else - git commit -m "📚 Sync cookbook from ${{ github.repository }}@${{ github.sha }} + git commit -m "📚 Sync cookbook from $SOURCE_REPOSITORY@$SOURCE_SHA Updated cookbook content and navigation from community contributions. - Source: ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}" - git push - echo "CHANGES_MADE=true" >> $GITHUB_ENV + Source: $SOURCE_SERVER_URL/$SOURCE_REPOSITORY/commit/$SOURCE_SHA" + docs_repo_auth="$(printf 'x-access-token:%s' "$DOCS_REPO_TOKEN" | base64 -w0)" + echo "::add-mask::$docs_repo_auth" + git -c "http.extraheader=AUTHORIZATION: basic $docs_repo_auth" push origin HEAD:main + echo "changes_made=true" >> "$GITHUB_OUTPUT" fi - + + report-sync: + name: Report sync status + if: always() + needs: sync-cookbook + runs-on: ubuntu-latest + permissions: + contents: write # zizmor: ignore[undocumented-permissions] Commit comments require this permission. + steps: - name: Create deployment comment - if: env.CHANGES_MADE == 'true' + if: needs.sync-cookbook.result == 'success' && needs.sync-cookbook.outputs.changes_made == 'true' continue-on-error: true - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | try { @@ -109,9 +140,11 @@ jobs: } - name: Report sync failure - if: failure() + if: needs.sync-cookbook.result != 'success' continue-on-error: true - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} with: script: | try { @@ -125,7 +158,7 @@ jobs: body: `❌ **Cookbook sync failed** There was an error syncing the cookbook content to the docs site. - Please check the [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + Please check the [workflow logs](${process.env.RUN_URL}) for details. The docs site may not reflect the latest cookbook changes until this is resolved.` }); @@ -136,8 +169,14 @@ jobs: - name: Log sync status if: always() + env: + CHANGES_MADE: ${{ needs.sync-cookbook.outputs.changes_made }} + SYNC_RESULT: ${{ needs.sync-cookbook.result }} run: | - if [ "${{ env.CHANGES_MADE }}" = "true" ]; then + if [ "$SYNC_RESULT" != "success" ]; then + echo "❌ COOKBOOK SYNC FAILED" + exit 1 + elif [ "$CHANGES_MADE" = "true" ]; then echo "🎉 COOKBOOK SYNC SUCCESS!" echo "📚 Content synced to docs repository" echo "🔧 Navigation updated automatically" @@ -145,4 +184,4 @@ jobs: else echo "â„šī¸ No changes to sync" echo "📄 Cookbook content is already up to date" - fi \ No newline at end of file + fi diff --git a/package-lock.json b/package-lock.json index 4a99ba3..b4de002 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,18 @@ "": { "devDependencies": { "@mdx-js/loader": "^3.1.0", - "@mdx-js/mdx": "^3.1.0" + "@mdx-js/mdx": "^3.1.0", + "glob": "^11.1.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, "node_modules/@mdx-js/loader": { @@ -188,6 +199,29 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -265,6 +299,21 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -460,6 +509,48 @@ "dev": true, "license": "MIT" }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/hast-util-to-estree": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", @@ -599,6 +690,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -610,6 +724,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", @@ -745,9 +869,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "dev": true, "license": "MIT", "dependencies": { @@ -1426,6 +1550,32 @@ ], "license": "MIT" }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1433,6 +1583,13 @@ "dev": true, "license": "MIT" }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -1460,6 +1617,33 @@ "dev": true, "license": "MIT" }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -1608,6 +1792,42 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -1823,6 +2043,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 12a518a..c2a27c3 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,6 @@ "devDependencies": { "@mdx-js/loader": "^3.1.0", "@mdx-js/mdx": "^3.1.0", - "glob": "^10.3.10" + "glob": "^11.1.0" } } From 80ae9f5baa07db1a8033071552682a16f8ef29bd Mon Sep 17 00:00:00 2001 From: pplx-teammatebot Date: Mon, 3 Aug 2026 13:40:14 +0000 Subject: [PATCH 2/2] Make cookbook cleanup deterministic --- .github/workflows/sync-to-docs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sync-to-docs.yml b/.github/workflows/sync-to-docs.yml index 9eed96e..299ccd5 100644 --- a/.github/workflows/sync-to-docs.yml +++ b/.github/workflows/sync-to-docs.yml @@ -50,7 +50,8 @@ jobs: - name: Clear existing cookbook content run: | - rm -rf docs-repo/cookbook/* || true + mkdir -p docs-repo/cookbook + find docs-repo/cookbook -mindepth 1 -maxdepth 1 -exec rm -rf -- {} + - name: Copy cookbook content to docs repository run: |