From 2a48ffc981682ef6b1376a59f34acb4151e04f3a Mon Sep 17 00:00:00 2001 From: Toks Fawibe Date: Fri, 23 Jan 2026 13:41:54 -0600 Subject: [PATCH 1/4] fix: Replace reusable workflow with standalone canary release The sdk repo is public and cannot access private reusable workflows from github-internal. This replaces the reusable workflow reference with a standalone implementation that works for public repositories. Uses Lerna for: - Detecting changed packages - Bumping versions with canary prerelease - Publishing with canary dist-tag --- .github/workflows/canary.yaml | 185 +++++++++++++++++++++++++++++++++- 1 file changed, 181 insertions(+), 4 deletions(-) diff --git a/.github/workflows/canary.yaml b/.github/workflows/canary.yaml index 83ddadb9..de597698 100644 --- a/.github/workflows/canary.yaml +++ b/.github/workflows/canary.yaml @@ -6,7 +6,184 @@ on: - created jobs: - trigger-deploy: - name: Canary Deploy - uses: jupiterone/github-internal/.github/workflows/monorepo-canary-release.yaml@v1 - secrets: inherit + canary-release: + name: Canary Release + runs-on: ubuntu-latest + if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/canary-release') }} + permissions: + contents: write + pull-requests: write + id-token: write + steps: + - name: Add reaction to comment + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes', + }); + + - name: Post starting comment + id: start-comment + uses: actions/github-script@v7 + with: + script: | + const comment = await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `🚀 Canary release workflow has been triggered.\n\nYou can follow the progress [here](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}).` + }); + return comment.data.id; + result-encoding: string + + - name: Checkout PR + uses: actions/checkout@v4 + with: + ref: refs/pull/${{ github.event.issue.number }}/head + fetch-depth: 0 + token: ${{ secrets.AUTO_GITHUB_PAT_TOKEN }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + + - name: Configure npm for JupiterOne packages + run: | + echo "@jupiterone:registry=https://npm.pkg.github.com" >> .npmrc + echo "//npm.pkg.github.com/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" >> .npmrc + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" >> .npmrc + + - name: Install dependencies + run: npm ci --include=optional + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + + - name: Configure git + run: | + git config --global user.email "automation@jupiterone.com" + git config --global user.name "Automation" + + - name: Get changed packages + id: changed + run: | + # Get list of packages changed since main + CHANGED=$(npx lerna changed --json 2>/dev/null || echo "[]") + echo "Changed packages: $CHANGED" + echo "packages=$CHANGED" >> $GITHUB_OUTPUT + + # Check if there are any changed packages + if [ "$CHANGED" = "[]" ]; then + echo "has_changes=false" >> $GITHUB_OUTPUT + else + echo "has_changes=true" >> $GITHUB_OUTPUT + fi + + - name: Build packages + if: steps.changed.outputs.has_changes == 'true' + run: npm run build:dist + + - name: Publish canary versions + id: publish + if: steps.changed.outputs.has_changes == 'true' + run: | + PREID="canary-${{ github.event.issue.number }}-${{ github.run_attempt }}" + + # Version bump with canary prerelease + npx lerna version prerelease --preid "$PREID" --no-git-tag-version --no-push --yes + + # Publish with canary tag + npx lerna publish from-package --dist-tag canary --yes 2>&1 | tee publish-output.txt + + # Extract published versions + PUBLISHED=$(grep -E "Successfully published" publish-output.txt || echo "") + echo "published<> $GITHUB_OUTPUT + echo "$PUBLISHED" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} + GH_TOKEN: ${{ secrets.AUTO_GITHUB_PAT_TOKEN }} + + - name: Get published versions + id: versions + if: steps.changed.outputs.has_changes == 'true' + run: | + # Get the canary versions from package.json files + VERSIONS="" + for pkg in $(echo '${{ steps.changed.outputs.packages }}' | jq -r '.[].name'); do + PKG_PATH=$(echo '${{ steps.changed.outputs.packages }}' | jq -r ".[] | select(.name == \"$pkg\") | .location") + VERSION=$(cat "$PKG_PATH/package.json" | jq -r '.version') + VERSIONS="$VERSIONS\n- \`$pkg@$VERSION\`" + done + + echo "versions<> $GITHUB_OUTPUT + echo -e "$VERSIONS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Comment with published versions + if: steps.changed.outputs.has_changes == 'true' + uses: actions/github-script@v7 + with: + script: | + const versions = `${{ steps.versions.outputs.versions }}`; + const packages = JSON.parse('${{ steps.changed.outputs.packages }}'); + + let installCommands = packages.map(pkg => { + const pkgJson = require(`${process.env.GITHUB_WORKSPACE}/${pkg.location.replace(process.env.GITHUB_WORKSPACE + '/', '')}/package.json`); + return `npm install ${pkg.name}@${pkgJson.version}`; + }).join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `✅ **Canary release published successfully!**\n\n**Published packages:**\n${versions}\n\n**Install with:**\n\`\`\`bash\n${installCommands}\n\`\`\`` + }); + + - name: Comment if no changes + if: steps.changed.outputs.has_changes == 'false' + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `⚠️ **No packages to publish.**\n\nNo changes detected compared to main branch.` + }); + + - name: Add success reaction + if: success() + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'rocket', + }); + + - name: Add failure reaction + if: failure() + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'confused', + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `❌ **Canary release failed.**\n\nCheck the [workflow run](${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}) for details.` + }); From 3216eb28e8dda6820df26919e713a0952553969b Mon Sep 17 00:00:00 2001 From: Toks Fawibe Date: Fri, 23 Jan 2026 13:45:11 -0600 Subject: [PATCH 2/4] refactor: Address Copilot review feedback - Rename PREID to PRERELEASE_ID for better clarity - Simplify path construction in package.json lookup Note: Intentionally keeping NPM_AUTH_TOKEN for GitHub Packages as GITHUB_TOKEN lacks permissions for private @jupiterone packages --- .github/workflows/canary.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/canary.yaml b/.github/workflows/canary.yaml index de597698..5dbfe48e 100644 --- a/.github/workflows/canary.yaml +++ b/.github/workflows/canary.yaml @@ -92,10 +92,10 @@ jobs: id: publish if: steps.changed.outputs.has_changes == 'true' run: | - PREID="canary-${{ github.event.issue.number }}-${{ github.run_attempt }}" + PRERELEASE_ID="canary-${{ github.event.issue.number }}-${{ github.run_attempt }}" # Version bump with canary prerelease - npx lerna version prerelease --preid "$PREID" --no-git-tag-version --no-push --yes + npx lerna version prerelease --preid "$PRERELEASE_ID" --no-git-tag-version --no-push --yes # Publish with canary tag npx lerna publish from-package --dist-tag canary --yes 2>&1 | tee publish-output.txt @@ -134,7 +134,10 @@ jobs: const packages = JSON.parse('${{ steps.changed.outputs.packages }}'); let installCommands = packages.map(pkg => { - const pkgJson = require(`${process.env.GITHUB_WORKSPACE}/${pkg.location.replace(process.env.GITHUB_WORKSPACE + '/', '')}/package.json`); + const pkgPath = pkg.location.startsWith(process.env.GITHUB_WORKSPACE) + ? pkg.location + : `${process.env.GITHUB_WORKSPACE}/${pkg.location}`; + const pkgJson = require(`${pkgPath}/package.json`); return `npm install ${pkg.name}@${pkgJson.version}`; }).join('\n'); From c242c72f1ca9215fe33a14008bac6a38bd5f3d28 Mon Sep 17 00:00:00 2001 From: Toks Fawibe Date: Fri, 23 Jan 2026 13:50:23 -0600 Subject: [PATCH 3/4] security: Add authorization checks and environment protection Security improvements for public repository canary releases: 1. Actor validation gate job: - Only OWNER, MEMBER, or COLLABORATOR can trigger releases - Unauthorized attempts get thumbs-down reaction + explanatory comment - Logs author association for audit trail 2. Environment protection: - Adds 'canary-publish' environment requirement - Allows configuring required reviewers in GitHub Settings - Secrets only available when environment rules pass Setup required: - Go to GitHub Settings > Environments - Create 'canary-publish' environment - Add required reviewers (optional but recommended) - Environment protection rules will gate secret access --- .github/workflows/canary.yaml | 45 ++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/.github/workflows/canary.yaml b/.github/workflows/canary.yaml index 5dbfe48e..2b408fe4 100644 --- a/.github/workflows/canary.yaml +++ b/.github/workflows/canary.yaml @@ -6,10 +6,53 @@ on: - created jobs: + # Gate job to check authorization before running the main workflow + check-authorization: + name: Check Authorization + runs-on: ubuntu-latest + if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/canary-release') }} + outputs: + is-authorized: ${{ steps.check.outputs.authorized }} + steps: + - name: Check if user is authorized + id: check + uses: actions/github-script@v7 + with: + script: | + const authorAssociation = context.payload.comment.author_association; + const authorizedRoles = ['OWNER', 'MEMBER', 'COLLABORATOR']; + const isAuthorized = authorizedRoles.includes(authorAssociation); + + console.log(`Author: ${context.payload.comment.user.login}`); + console.log(`Association: ${authorAssociation}`); + console.log(`Authorized: ${isAuthorized}`); + + core.setOutput('authorized', isAuthorized ? 'true' : 'false'); + + if (!isAuthorized) { + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: '-1', + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `⛔ **Unauthorized**: Only organization members can trigger canary releases.\n\n` + + `User \`${context.payload.comment.user.login}\` has association: \`${authorAssociation}\`` + }); + } + canary-release: name: Canary Release runs-on: ubuntu-latest - if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/canary-release') }} + needs: check-authorization + if: ${{ needs.check-authorization.outputs.is-authorized == 'true' }} + # Environment with protection rules - configure in GitHub Settings > Environments + environment: canary-publish permissions: contents: write pull-requests: write From e172653ffe289ec8b10085feb35e93e98c063ee3 Mon Sep 17 00:00:00 2001 From: Toks Fawibe Date: Fri, 23 Jan 2026 14:08:10 -0600 Subject: [PATCH 4/4] refactor: Use jq direct file read instead of cat pipe Addresses Copilot review feedback - jq can read files directly without needing cat, improving performance and clarity. --- .github/workflows/canary.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/canary.yaml b/.github/workflows/canary.yaml index 2b408fe4..f371fe4f 100644 --- a/.github/workflows/canary.yaml +++ b/.github/workflows/canary.yaml @@ -160,7 +160,7 @@ jobs: VERSIONS="" for pkg in $(echo '${{ steps.changed.outputs.packages }}' | jq -r '.[].name'); do PKG_PATH=$(echo '${{ steps.changed.outputs.packages }}' | jq -r ".[] | select(.name == \"$pkg\") | .location") - VERSION=$(cat "$PKG_PATH/package.json" | jq -r '.version') + VERSION=$(jq -r '.version' "$PKG_PATH/package.json") VERSIONS="$VERSIONS\n- \`$pkg@$VERSION\`" done