Skip to content

ci: repack dist/ on dependabot branches without exposing a write token - #390

Merged
mrbobbytables merged 1 commit into
cncf:mainfrom
mrbobbytables:ci-dependabot-repack
Oct 9, 2026
Merged

mrbobbytables merged 1 commit into
cncf:mainfrom
mrbobbytables:ci-dependabot-repack

Conversation

@mrbobbytables

@mrbobbytables mrbobbytables commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Description

Every dependabot PR that bumps an ncc-bundled dependency permanently fails the "Verify committed dist/ matches source" gate, because dependabot never runs npm run pack (see #167). This PR rebuilds dist/ on same-repo dependabot branches and pushes the result back — reworked per review so the write credential never coexists with dependency code:

  • repack-dist (no secrets): checks out the PR head SHA with persist-credentials: false, installs with npm ci --ignore-scripts, builds, packs, and uploads dist/ as an artifact only when it drifted. The bumped dependency code that runs here can read nothing.
  • push-repacked-dist (no npm): mints a short-lived App token (actions/create-github-app-token), refuses to proceed if the branch moved since the rebuild, downloads the artifact, refuses any change outside dist/, and pushes with --force-with-lease. No contents: write is requested on GITHUB_TOKEN.

Gated on github.event.pull_request.user.login == 'dependabot[bot]' and a same-repo head.

Maintainer setup required: create (or reuse) a GitHub App with contents: write, install it on this repository, and store its id/key as the Dependabot secrets REPACK_APP_ID / REPACK_APP_PRIVATE_KEY (dependabot-triggered runs only see Dependabot secrets). Pushing with the App token is also what re-triggers build-test on the new commit.

Follow-ups noted from review, not in this PR: a tag ruleset or an on-main check in release.yml to constrain what any compromised credential could ship. If the maintainers prefer the simpler route (keep the gate, hand-run npm run pack), this PR can be dropped — #411 carries the js-yaml move independently.

The js-yaml move was split out to #411 per review.

Fixes #171

Testing

  • npm ci --ignore-scripts && npm run build && npm run pack on a clean checkout — reproduces the committed dist/ byte-for-byte (git status --porcelain dist/ empty), so the no-scripts rebuild is sound
  • test.yml parses cleanly (js-yaml load; jobs: build-test, repack-dist, push-repacked-dist)
  • Action pins verified against upstream releases: checkout v7.0.1 / setup-node v7.0.0 / upload-artifact v7.0.1 (repo's existing pins), download-artifact v8.0.2, create-github-app-token v3.2.0

Checklist

  • I ran npm run all to lint and build my code — not applicable, workflow-only
  • I ran npm run pack and committed dist/ — not applicable, dist/ unchanged
  • Commits are signed off (DCO)
  • Note any new dependencies these changes bring in — two new pinned actions: actions/download-artifact, actions/create-github-app-token
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation — none needed
  • My changes generates no new warnings (please describe new warnings if unavoidable)

@hivecommons-hive

Copy link
Copy Markdown
Contributor

Reviewed from the test lane (read-only on a fork head, so comment only). The job logic is sound — in particular the repack commit is pushed by the PAT owner, so the re-triggered run's github.actor is no longer dependabot[bot] and repack-dist cannot loop on itself. Two things worth a look before this lands:

  1. Until DEPENDABOT_REPACK_TOKEN exists, every dependabot PR gets a red repack-dist check. actions/checkout@v7.0.1 reads the token with core.getInput('token', { required: true }) (src/input-helper.ts:157); an unset secret interpolates to '', so the step fails with Input required and not supplied: token rather than falling back to github.token. It is not a required check, but it reads as a broken PR to anyone glancing at it. A cheap guard is a first step that maps the secret into an env var and exit 0s with a notice when it is empty, e.g.
    - name: Skip when the repack token is not configured
      env: { REPACK_TOKEN: ${{ secrets.DEPENDABOT_REPACK_TOKEN }} }
      run: |
        if [ -z "$REPACK_TOKEN" ]; then
          echo "::notice::DEPENDABOT_REPACK_TOKEN is not set; skipping dist/ repack"
          echo "skip=true" >> "$GITHUB_OUTPUT"
        fi
      id: gate
    with if: steps.gate.outputs.skip != 'true' on the remaining steps — or simply say in the body that the job is expected red until the secret is added.
  2. pull_request runs from dependabot[bot] only see Dependabot secrets; the body already says to add it under both Actions and Dependabot, which is right — just flagging that the Actions copy is what the PAT-owner-triggered re-run after the repack push will use for build-test's read-only checkout, so it is not strictly needed there.

git merge-tree --write-tree pr-389 pr-390 exits 0 — this and #389 edit different hunks of test.yml and merge cleanly in either order. The js-yaml dev→prod move leaves dist/ byte-identical here as well (npm run pack && git status --porcelain dist/ empty on this head).


🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5q9t | SHA: unknown

— hive: agent=quality backend=copilot model=claude-fable-5.1

@jeefy jeefy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The js-yaml move is correct. It's imported at runtime (config.ts:5, owners.ts:6), and dist/ is unchanged. Please split it into its own PR so it can merge now.

repack-dist has a high-severity problem: the write token sits on disk while dependency code runs.

  • actions/checkout keeps DEPENDABOT_REPACK_TOKEN in .git/config, and then npm ci (install scripts), build and pack run the code Dependabot just bumped.
  • For a github-actions bump, the workflow comes from the PR, so the bumped checkout/setup-node run next to the token too.
  • A compromised package could read that long-lived contents:write PAT. It could then push branches or a v* tag, which triggers release.yml; I found no on-main check there.

If you want auto-repack:

  1. Job A, no secrets: persist-credentials: false, npm ci --ignore-scripts, build, pack, then upload dist/ as an artifact.
  2. Job B, needs: A, holds the token: checkout at the PR head SHA, download the artifact, check that only dist/ changed, commit, git push --force-with-lease. No npm.
  3. Use a short-lived GitHub App token instead of a PAT. Drop the unused permissions: contents: write (Dependabot runs get a read-only GITHUB_TOKEN). Gate on github.event.pull_request.user.login == 'dependabot[bot]'. Consider a tag ruleset or an on-main check in release.yml.

The simpler alternative is to keep the drift gate and have a maintainer run npm run pack on Dependabot PRs.

Dependabot cannot run 'npm run pack', so bundled-dependency bumps
always fail the dist/ drift gate. Rebuild dist/ and push it back,
split across two jobs per review so the write credential never
coexists with dependency code:

- repack-dist holds no secrets: persist-credentials false,
  npm ci --ignore-scripts, build, pack, dist/ uploaded as an artifact
  (verified locally: the --ignore-scripts build reproduces the
  committed dist/ byte-for-byte)
- push-repacked-dist runs no npm: it mints a short-lived App token,
  checks the branch has not moved since the rebuild, refuses any
  artifact change outside dist/, and pushes with --force-with-lease

Gated on github.event.pull_request.user.login == 'dependabot[bot]'
and a same-repo head. No contents:write GITHUB_TOKEN permission is
requested; the push uses the App token, which is also what makes the
pushed commit re-trigger build-test.

The js-yaml dependency move was split into its own PR per review.

Fixes cncf#171

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Bob Killen <bkillen@linuxfoundation.org>
@mrbobbytables mrbobbytables changed the title ci: repack dist/ on dependabot branches; move js-yaml to dependencies ci: repack dist/ on dependabot branches without exposing a write token Oct 9, 2026
@mrbobbytables

Copy link
Copy Markdown
Member Author

Reworked per review:

  • js-yaml move split out to chore(deps): move js-yaml to dependencies #411 so it can merge now.
  • Two-job split as suggested: repack-dist holds no secrets (persist-credentials: false, npm ci --ignore-scripts, build, pack, artifact upload) — verified locally that the no-scripts rebuild reproduces the committed dist/ byte-for-byte. push-repacked-dist runs no npm: short-lived App token via actions/create-github-app-token (v3.2.0, pinned), checkout at the PR branch with a hard refusal if HEAD moved since the rebuild, artifact download, a guard refusing any change outside dist/, then git push --force-with-lease.
  • Dropped the unused permissions: contents: write and gated on github.event.pull_request.user.login == 'dependabot[bot]' instead of github.actor.
  • Secrets are now REPACK_APP_ID / REPACK_APP_PRIVATE_KEY (Dependabot secrets) for a short-lived installation token — no long-lived PAT.
  • Tag ruleset / on-main check in release.yml noted in the body as follow-ups; happy to file an issue for those. And if you'd rather keep the manual npm run pack route, this PR can simply be closed — chore(deps): move js-yaml to dependencies #411 stands alone.

mrbobbytables added a commit that referenced this pull request Oct 9, 2026
js-yaml is a runtime import (src/utils/owners.ts:6, src/utils/config.ts:5)
bundled into dist/, so it belongs in dependabot's
production-dependencies group where a maintainer expects a repack to
be needed. Split out of #390 per review so it can merge on its own.

Ref #171

Signed-off-by: Bob Killen <bkillen@linuxfoundation.org>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mrbobbytables

Copy link
Copy Markdown
Member Author

/kind cleanup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[quality] dependabot PRs bumping ncc-bundled deps always fail the dist/ gate — add a repack-dist job to test.yml

2 participants