From 5e688eb33562b41e70e5485951e7eb15f56abe77 Mon Sep 17 00:00:00 2001 From: BenCodez <17074231+BenCodez@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:26:04 -0600 Subject: [PATCH 1/3] Harden AdvancedCore GitHub workflows --- .github/workflows/maven.yml | 41 +++++---- .github/workflows/publish-javadoc.yml | 89 ++++++++++++++----- .github/workflows/stale.yml | 2 +- .../build/BuildInputPinningTest.java | 37 +++++++- 4 files changed, 127 insertions(+), 42 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a1b1badba5..2f21632988 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -1,41 +1,44 @@ -# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support documentation. - name: Java CI with Maven on: push: branches: [ "master" ] pull_request: - branches: [ "master", "codex/shared-sql-backend-init" ] + branches: [ "master" ] permissions: - contents: write # required for dependency submission + contents: read jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: '21' distribution: 'temurin' cache: maven - - name: Build with Maven run: mvn -B -f AdvancedCore/pom.xml package - # Only submit dependency snapshot on push (not on pull_request) - - name: Submit Dependency Snapshot - if: github.event_name == 'push' - uses: advanced-security/maven-dependency-submission-action@v5 + dependency-submission: + if: github.event_name == 'push' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - name: Set up JDK 21 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + - name: Submit dependency snapshot + uses: advanced-security/maven-dependency-submission-action@b275d12641ac2d2108b2cbb7598b154ad2f2cee8 # v5.0.0 with: - directory: AdvancedCore # path to pom.xml - # optional: maven-args: "-DskipTests" + directory: AdvancedCore diff --git a/.github/workflows/publish-javadoc.yml b/.github/workflows/publish-javadoc.yml index 03275bbc32..40ce3adf1c 100644 --- a/.github/workflows/publish-javadoc.yml +++ b/.github/workflows/publish-javadoc.yml @@ -3,38 +3,87 @@ name: Deploy Javadoc on: release: types: [published] - branches: - - master - - main permissions: contents: read - pages: write - id-token: write + +concurrency: + group: github-pages + cancel-in-progress: false jobs: - publish: + release-eligibility: runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + permissions: + contents: read + outputs: + eligible: ${{ steps.eligibility.outputs.eligible }} + commit: ${{ steps.eligibility.outputs.commit }} steps: - - name: Checkout - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 - - name: Set up Java - uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: master + fetch-depth: 0 + persist-credentials: false + - name: Verify release tag is reachable from master + id: eligibility + env: + PRERELEASE: ${{ github.event.release.prerelease }} + TAG_NAME: ${{ github.event.release.tag_name }} + run: | + if [ "$PRERELEASE" = "true" ]; then + echo "eligible=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + git fetch --no-tags origin "refs/tags/${TAG_NAME}:refs/tags/release" + tag_commit="$(git rev-parse 'refs/tags/release^{commit}')" + if git merge-base --is-ancestor "$tag_commit" origin/master; then + echo "eligible=true" >> "$GITHUB_OUTPUT" + echo "commit=$tag_commit" >> "$GITHUB_OUTPUT" + else + echo "eligible=false" >> "$GITHUB_OUTPUT" + fi + + build: + needs: release-eligibility + if: needs.release-eligibility.outputs.eligible == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ needs.release-eligibility.outputs.commit }} + persist-credentials: false + - name: Set up JDK 21 + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: - distribution: temurin java-version: '21' + distribution: temurin cache: maven + - name: Build project + run: mvn -B -f AdvancedCore/pom.xml clean package - name: Build Javadoc - run: mvn -B -f AdvancedCore/pom.xml org.apache.maven.plugins:maven-javadoc-plugin:3.12.0:javadoc - - name: Configure Pages - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b + run: mvn -B -f AdvancedCore/pom.xml org.apache.maven.plugins:maven-javadoc-plugin:3.12.0:javadoc -Ddoclint=none - name: Upload Pages artifact - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 with: path: AdvancedCore/target/reports/apidocs - - name: Deploy Pages + + deploy: + needs: [release-eligibility, build] + if: >- + needs.release-eligibility.outputs.eligible == 'true' && + needs.build.result == 'success' + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + permissions: + pages: write + id-token: write + steps: + - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 934ecde8cd..e91c8b70ff 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,7 +18,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v5 + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'Stale issue message' diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java index 25ca5401a6..c987bba644 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java @@ -6,6 +6,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; @@ -14,14 +16,45 @@ class BuildInputPinningTest { @Test void javadocPublisherUsesImmutableRevisionAndExplicitPermissions() throws IOException { String workflow = Files.readString(Path.of("..", ".github", "workflows", "publish-javadoc.yml")); - String normalizedWorkflow = workflow.replace("\r\n", "\n"); + String releaseEligibilityJob = job(workflow, "release-eligibility"); + String buildJob = job(workflow, "build"); + String deployJob = job(workflow, "deploy"); assertFalse(workflow.contains("MathieuSoysal/Javadoc-publisher")); assertTrue(workflow.matches("(?s).*actions/checkout@[0-9a-f]{40}.*")); assertTrue(workflow.matches("(?s).*actions/setup-java@[0-9a-f]{40}.*")); assertTrue(workflow.matches("(?s).*actions/deploy-pages@[0-9a-f]{40}.*")); - assertTrue(normalizedWorkflow.contains("permissions:\n contents: read\n pages: write\n id-token: write")); + assertTrue(buildJob.contains("contents: read")); + assertFalse(buildJob.contains("pages: write")); + assertFalse(buildJob.contains("id-token: write")); + assertTrue(deployJob.contains("pages: write")); + assertTrue(deployJob.contains("id-token: write")); + assertTrue(releaseEligibilityJob.contains("git merge-base --is-ancestor \"$tag_commit\" origin/master")); + assertTrue(releaseEligibilityJob.contains("commit: ${{ steps.eligibility.outputs.commit }}")); + assertTrue(releaseEligibilityJob.contains("echo \"commit=$tag_commit\" >> \"$GITHUB_OUTPUT\"")); + assertFalse(workflow.contains("target_commitish")); + assertTrue(buildJob.contains("needs: release-eligibility")); + assertTrue(buildJob.contains("needs.release-eligibility.outputs.eligible == 'true'")); + assertTrue(buildJob.contains("ref: ${{ needs.release-eligibility.outputs.commit }}")); + assertTrue(deployJob.contains("needs: [release-eligibility, build]")); + assertTrue(deployJob.contains("needs.release-eligibility.outputs.eligible == 'true'")); assertTrue(workflow.contains("maven-javadoc-plugin:3.12.0:javadoc")); assertFalse(workflow.contains("Javadoc-publisher.yml@main")); } + + @Test + void dependencySubmissionCheckoutDoesNotPersistWriteScopedCredentials() throws IOException { + String workflow = Files.readString(Path.of("..", ".github", "workflows", "maven.yml")); + String dependencySubmissionJob = job(workflow, "dependency-submission"); + + assertTrue(dependencySubmissionJob.contains("contents: write")); + assertTrue(dependencySubmissionJob.contains("persist-credentials: false")); + } + + private static String job(String workflow, String name) { + Matcher matcher = Pattern.compile("(?ms)^ " + Pattern.quote(name) + ":\\R(?.*?)(?=^ [A-Za-z0-9_-]+:\\R|\\z)") + .matcher(workflow); + assertTrue(matcher.find(), () -> "Missing " + name + " job"); + return matcher.group("job"); + } } From a505d561394d60ec7e7d2ab7e3bb13f26d6bb17c Mon Sep 17 00:00:00 2001 From: BenCodez <17074231+BenCodez@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:53:28 -0600 Subject: [PATCH 2/3] Drop checkout credentials from build jobs --- .github/workflows/maven.yml | 2 ++ .../com/bencodez/advancedcore/build/BuildInputPinningTest.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 2f21632988..729f8f3cbc 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -14,6 +14,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false - name: Set up JDK 21 uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java index c987bba644..e5f06335c6 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java @@ -45,8 +45,10 @@ void javadocPublisherUsesImmutableRevisionAndExplicitPermissions() throws IOExce @Test void dependencySubmissionCheckoutDoesNotPersistWriteScopedCredentials() throws IOException { String workflow = Files.readString(Path.of("..", ".github", "workflows", "maven.yml")); + String buildJob = job(workflow, "build"); String dependencySubmissionJob = job(workflow, "dependency-submission"); + assertTrue(buildJob.contains("persist-credentials: false")); assertTrue(dependencySubmissionJob.contains("contents: write")); assertTrue(dependencySubmissionJob.contains("persist-credentials: false")); } From 449be1643dd729be1f0e9251c79f0ac021c9c65a Mon Sep 17 00:00:00 2001 From: BenCodez <17074231+BenCodez@users.noreply.github.com> Date: Sat, 26 Sep 2026 22:29:59 -0600 Subject: [PATCH 3/3] Avoid temporary release tag collisions --- .github/workflows/publish-javadoc.yml | 4 ++-- .../bencodez/advancedcore/build/BuildInputPinningTest.java | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-javadoc.yml b/.github/workflows/publish-javadoc.yml index 40ce3adf1c..7232c620e5 100644 --- a/.github/workflows/publish-javadoc.yml +++ b/.github/workflows/publish-javadoc.yml @@ -36,8 +36,8 @@ jobs: exit 0 fi - git fetch --no-tags origin "refs/tags/${TAG_NAME}:refs/tags/release" - tag_commit="$(git rev-parse 'refs/tags/release^{commit}')" + git fetch --no-tags origin "refs/tags/${TAG_NAME}" + tag_commit="$(git rev-parse --verify 'FETCH_HEAD^{commit}')" if git merge-base --is-ancestor "$tag_commit" origin/master; then echo "eligible=true" >> "$GITHUB_OUTPUT" echo "commit=$tag_commit" >> "$GITHUB_OUTPUT" diff --git a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java b/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java index e5f06335c6..3f16ce1af1 100644 --- a/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java +++ b/AdvancedCore/src/test/java/com/bencodez/advancedcore/build/BuildInputPinningTest.java @@ -30,6 +30,8 @@ void javadocPublisherUsesImmutableRevisionAndExplicitPermissions() throws IOExce assertTrue(deployJob.contains("pages: write")); assertTrue(deployJob.contains("id-token: write")); assertTrue(releaseEligibilityJob.contains("git merge-base --is-ancestor \"$tag_commit\" origin/master")); + assertTrue(releaseEligibilityJob.contains("FETCH_HEAD^{commit}")); + assertFalse(releaseEligibilityJob.contains("refs/tags/release")); assertTrue(releaseEligibilityJob.contains("commit: ${{ steps.eligibility.outputs.commit }}")); assertTrue(releaseEligibilityJob.contains("echo \"commit=$tag_commit\" >> \"$GITHUB_OUTPUT\"")); assertFalse(workflow.contains("target_commitish"));