Skip to content

CAMEL-24502: Least privilege for the sync workflows and pin the Maven wrapper downloads - #1930

Merged
Croway merged 1 commit into
apache:mainfrom
Croway:CAMEL-24502-ci-hardening
Sep 2, 2026
Merged

CAMEL-24502: Least privilege for the sync workflows and pin the Maven wrapper downloads#1930
Croway merged 1 commit into
apache:mainfrom
Croway:CAMEL-24502-ci-hardening

Conversation

@Croway

@Croway Croway commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Implements CAMEL-24502. Build infrastructure only, no shipped artifact is touched.

1. Least-privilege permissions: on the two scheduled workflows

.github/workflows/automatic-sync-main.yml and .github/workflows/generate-sbom-main.yml declared no permissions: block, so their job ran with the repository default GITHUB_TOKEN grants. pr-build-main.yml and pr-doc-validation.yml already declare permissions: contents: read; this brings the remaining two workflows in line.

Both now declare permissions: {} at the workflow level, and each job opts in to exactly what it needs.

2. Build split away from the PR-creation step

Previously one job checked out and fully built apache/camel, then built camel-spring-boot, and then handed the write-capable token to peter-evans/create-pull-request — all in the same job. It is now two jobs:

job grants what it does
build contents: read checks out apache/camel and camel-spring-boot, runs the same two ./mvnw commands as before, uploads the regenerated changes as an artifact
create-pull-request contents: write, pull-requests: write checks out camel-spring-boot, applies the artifact, calls create-pull-request

Cron schedule, the if: github.repository == 'apache/camel-spring-boot' guard, the ./mvnw command lines, the automatic-periodic-sync branch name and the PR title/body text are all unchanged. Only the job structure and the grants change.

Why a patch rather than a copy of the tree

The handover artifact is a git diff --cached --binary patch, not a copy of the working tree:

  • Size. A typical periodic sync changes a handful of files (e.g. [Github Actions] Periodic Sync Camel Spring Boot (Camel 4) #1924 changed 4) out of a repository of well over a hundred thousand. Zipping and shipping the whole source tree twice a day to move four files would dominate the job runtime.
  • Deletions. A file overlay cannot express a deleted file. Regeneration does delete files when a component goes away, and those deletions would be silently dropped from the PR.

Generation excludes the nested apache/camel checkout via the ':!camel' pathspec; target/ build output is already covered by .gitignore.

The patch is applied with git apply --3way. The two jobs check out main at different times — potentially an hour or two apart, given the apache/camel build in between — so --3way merges an advanced main instead of failing on context drift. A genuine conflict fails the step loudly rather than producing a half-applied tree.

actions/download-artifact@v8 verifies the artifact digest and errors on mismatch by default, so the handover between the two jobs is integrity-checked.

I verified the generate/apply round trip locally on a scratch repository covering: modified file, added text file, added binary file, deleted file, a nested git checkout named camel, gitignored target/ output, and an unrelated commit landing on the destination branch between generation and application. All four changes came through, the nested checkout and build output were excluded, and the deletion was staged correctly.

Action pinning

Every uses: in these two workflows is pinned to a full commit SHA with the version as a trailing comment. SHAs were resolved with gh api repos/<owner>/<repo>/git/ref/tags/<tag>; all five refs are of type commit (no annotated tag objects needing dereferencing), so the pins are commits and not tag objects:

actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1              # v7
actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c            # v6
actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a       # v7.0.1
actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c     # v8.0.1
peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1

Anyone can re-run the same gh api calls to confirm. .github/dependabot.yml already has a github-actions ecosystem entry, so the pins will keep being bumped and the trailing comment updated — no change needed there.

3. Maven wrapper checksums

.mvn/wrapper/maven-wrapper.properties gained distributionSha256Sum and wrapperSha256Sum, so mvnw/mvnw.cmd verify what they download instead of trusting the URL. Both values were computed from the artifacts at the exact URLs already in the file:

apache-maven-3.9.11-bin.zip
  sha256 0d7125e8c91097b36edb990ea5934e6c68b4440eef4ea96510a0f6815e7eeadb
  sha1   2b7fdf8f6983c7b295eab505b46825ee2d11959b   == published .sha1 on repo.maven.apache.org
  sha512 03e2d65d...8207076                          == published .sha512 on archive.apache.org

maven-wrapper-3.3.4.jar
  sha256 4e2fbf6554bc8a4702cdfdd3bef464f423393d784ddbb037216320ce55d5e4e1
  sha1   9a69c85449c23142f028e4566fa153e9fafe7a56   == published .sha1 on repo.maven.apache.org

So each value is corroborated by a checksum file published by the ASF alongside the artifact, and the distribution by a second one on an independent host. The wrapper jar sha256 also matches the .mvn/wrapper/maven-wrapper.jar already committed in this repository byte for byte, so the new property agrees with what contributors already run.

Verified locally:

  • ./mvnw -v still works with the existing wrapper cache (exit 0, Maven 3.9.11).
  • ./mvnw -v with an isolated MAVEN_USER_HOME forces a fresh distribution download; it downloads, passes distributionSha256Sum and starts Maven 3.9.11.
  • Negative test with a deliberately wrong wrapperSha256Sum: mvnw aborts with "Failed to validate Maven wrapper SHA-256".
  • Negative test with a deliberately wrong distributionSha256Sum and a clean wrapper home: the wrapper aborts with "Failed to validate Maven distribution SHA-256".

Both properties are therefore actually enforced by the wrapper version in use (3.3.4), not just cosmetic.

Deferred

  • Script-only wrapper mode. The ticket mentions dropping the committed maven-wrapper.jar in favour of distributionType=script / the only-script wrapper. That changes how every contributor and every CI job bootstraps Maven and deserves its own PR and its own discussion, so it is intentionally not done here. With wrapperSha256Sum in place, the committed jar is now checksum-verified on every run, which covers the immediate concern.
  • Pinning the other workflows. pr-build-main.yml, pr-doc-validation.yml and depsreview.yaml still use floating tags. They are out of scope for this ticket and none of them combines a third-party build with a write-capable token; happy to follow up in a separate PR if reviewers want repo-wide consistency.

Validation

  • actionlint 1.7.12 clean on both modified workflows.
  • All five workflow files and .github/dependabot.yml parse as YAML.
  • No Java or Maven module changed, so no test module applies.

Claude Code (Opus 5) on behalf of Federico Mariani

@Croway
Croway requested review from davsclaus and oscerd September 2, 2026 12:50
… wrapper downloads

The two scheduled workflows, automatic-sync-main.yml and generate-sbom-main.yml,
declared no permissions block, so their single job ran with the repository default
GITHUB_TOKEN grants. pr-build-main.yml and pr-doc-validation.yml already declare
`permissions: contents: read`, so this brings the remaining workflows in line.

Each workflow now declares `permissions: {}` at the top and every job opts in to
exactly what it needs. The job is also split in two:

- `build` (contents: read) checks out and builds apache/camel and then
  camel-spring-boot, exactly as before, and uploads the regenerated changes as a
  build artifact.
- `create-pull-request` (contents: write, pull-requests: write) checks out
  camel-spring-boot, applies the artifact and calls
  peter-evans/create-pull-request.

The regenerated tree is handed over as a `git diff --cached --binary` patch rather
than a copy of the working tree. A typical sync changes a handful of files out of
a repository of well over a hundred thousand, so a patch keeps the transfer small,
and unlike a file overlay it also carries deletions, which a regeneration can
produce when a component goes away. `git add --all -- ':!camel'` excludes the
nested apache/camel checkout, and .gitignore already excludes target directories.
The patch is applied with `git apply --3way` so that a main branch that moved
while the build was running is merged rather than silently dropped.

Every `uses:` reference in these two workflows is pinned to a full commit SHA with
the version kept as a trailing comment, so the resolved action code is reproducible
and reviewable. .github/dependabot.yml already has a github-actions ecosystem
entry, so the pins keep getting bumped.

maven-wrapper.properties gained distributionSha256Sum and wrapperSha256Sum, so
mvnw and mvnw.cmd verify what they download instead of trusting the URL. The two
values were computed from the artifacts at the exact URLs already in the file and
cross-checked against the .sha1 files published next to them on
repo.maven.apache.org; the distribution was additionally cross-checked against the
.sha512 published on archive.apache.org, and the wrapper jar sum matches the
maven-wrapper.jar already committed under .mvn/wrapper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Croway
Croway force-pushed the CAMEL-24502-ci-hardening branch from 366d26e to cfac81c Compare September 2, 2026 13:12
@Croway
Croway merged commit 7df1ea2 into apache:main Sep 2, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants