diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 00000000..c792d3c2
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,47 @@
+---
+name: CodeQL
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: '0 6 * * 1'
+
+permissions:
+ contents: read
+
+jobs:
+ analyze:
+ name: Analyze (Java)
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ permissions:
+ security-events: write
+ packages: read
+ actions: read
+ contents: read
+
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: actions/setup-java@v5
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: java-kotlin
+ queries: security-and-quality
+
+ - name: Build with Maven
+ run: ./mvnw -B -ntp clean compile -DskipTests
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:java-kotlin"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 839af913..0b697a97 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -2,10 +2,7 @@
name: Release
permissions:
- contents: write
- packages: write
- statuses: write
- checks: write
+ contents: read
on:
workflow_dispatch:
@@ -14,7 +11,11 @@ jobs:
release:
permissions:
contents: write
+ packages: write
runs-on: ubuntu-latest
+ outputs:
+ tag: ${{ steps.publish.outputs.tag }}
+ version: ${{ steps.publish.outputs.version }}
steps:
- uses: actions/checkout@v6
- uses: webfactory/ssh-agent@v0.10.0
@@ -32,6 +33,7 @@ jobs:
gpg-passphrase: MAVEN_GPG_PASSPHRASE
- name: Publish package to Maven Central
+ id: publish
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
@@ -39,6 +41,8 @@ jobs:
cat release.properties
TAG=$(grep 'scm.tag=' release.properties | cut -d'=' -f2)
VERSION=${TAG#v}
+ echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
mvn -B -ntp -Dstyle.color=always release:perform -DconnectionUrl=scm:git:https://github.com/${{ github.repository }}.git
echo "Released ${TAG} 🚀" >> $GITHUB_STEP_SUMMARY
env:
@@ -65,3 +69,71 @@ jobs:
git push
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ # Attestations and the GitHub Release SBOM upload run in a separate job so
+ # the OIDC token (id-token: write) and attestations: write permission are
+ # never available to the preceding `mvn release:*` steps, which execute
+ # repository-controlled plugin code. If this job fails after Maven Central
+ # has accepted the version, the release job stays green (the publish did
+ # succeed), this job is visibly red, and "Re-run failed jobs" reruns only
+ # the attestation/Release steps without retrying the Central publish.
+ # The job checks out the release tag so that provenance is anchored to the
+ # tagged release commit (not the pre-release snapshot commit that triggered
+ # the workflow_dispatch), and rebuilds the artifacts cleanly from that commit.
+ attest:
+ needs: release
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ id-token: write
+ attestations: write
+ steps:
+ - name: Checkout release tag
+ uses: actions/checkout@v6
+ with:
+ ref: ${{ needs.release.outputs.tag }}
+
+ - name: Set up Java
+ uses: actions/setup-java@v5
+ with:
+ java-version: "17"
+ distribution: "temurin"
+ cache: "maven"
+
+ - name: Build release artifacts at tagged commit
+ run: mvn -B -ntp package -DskipTests -Dgpg.skip=true
+
+ - name: Attest build provenance for release artifacts
+ uses: actions/attest-build-provenance@v2
+ with:
+ subject-path: |
+ target/elearning-module-parser-*.jar
+ !target/elearning-module-parser-*-sources.jar
+ !target/elearning-module-parser-*-javadoc.jar
+
+ - name: Attest SBOM for release artifacts
+ uses: actions/attest-sbom@v2
+ with:
+ subject-path: |
+ target/elearning-module-parser-*.jar
+ !target/elearning-module-parser-*-sources.jar
+ !target/elearning-module-parser-*-javadoc.jar
+ sbom-path: target/elearning-module-parser-*-cyclonedx.json
+
+ - name: Upload SBOM to GitHub Release
+ run: |
+ TAG="${{ needs.release.outputs.tag }}"
+ if gh release view "${TAG}" >/dev/null 2>&1; then
+ gh release upload "${TAG}" \
+ target/elearning-module-parser-*-cyclonedx.json \
+ target/elearning-module-parser-*-cyclonedx.xml \
+ --clobber
+ else
+ gh release create "${TAG}" \
+ target/elearning-module-parser-*-cyclonedx.json \
+ target/elearning-module-parser-*-cyclonedx.xml \
+ --title "${TAG}" \
+ --generate-notes
+ fi
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml
index 702f4c79..6de269fb 100644
--- a/.github/workflows/snapshot.yml
+++ b/.github/workflows/snapshot.yml
@@ -84,3 +84,62 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
+
+ # Stage post-deploy artifacts (these are exactly what GitHub Packages
+ # received, since `mvn deploy` re-runs the package phase and overwrites
+ # target/) for the separate attest job to consume.
+ - name: Stage snapshot artifacts for attestation
+ if: github.event_name != 'pull_request'
+ run: |
+ mkdir -p snapshot-artifacts
+ cp target/elearning-module-parser-*.jar snapshot-artifacts/
+ cp target/elearning-module-parser-*-cyclonedx.json snapshot-artifacts/
+ cp target/elearning-module-parser-*-cyclonedx.xml snapshot-artifacts/
+
+ - name: Upload snapshot artifacts for attestation job
+ if: github.event_name != 'pull_request'
+ uses: actions/upload-artifact@v4
+ with:
+ name: snapshot-artifacts
+ path: snapshot-artifacts/
+ if-no-files-found: error
+
+ # Attestations run in a separate job so id-token: write and
+ # attestations: write are not available to the preceding Maven build,
+ # which executes repository-controlled plugin code.
+ attest:
+ needs: test-build
+ if: github.event_name != 'pull_request'
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ attestations: write
+ steps:
+ - name: Download snapshot artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: snapshot-artifacts
+ path: artifacts
+
+ - name: Attest build provenance for snapshot artifacts
+ uses: actions/attest-build-provenance@v2
+ with:
+ subject-path: artifacts/elearning-module-parser-*.jar
+
+ - name: Attest SBOM for snapshot artifacts
+ uses: actions/attest-sbom@v2
+ with:
+ subject-path: |
+ artifacts/elearning-module-parser-*.jar
+ !artifacts/elearning-module-parser-*-sources.jar
+ !artifacts/elearning-module-parser-*-javadoc.jar
+ sbom-path: artifacts/elearning-module-parser-*-cyclonedx.json
+
+ - name: Upload SBOM as workflow artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: sbom
+ path: |
+ artifacts/elearning-module-parser-*-cyclonedx.json
+ artifacts/elearning-module-parser-*-cyclonedx.xml
+ if-no-files-found: error
diff --git a/pom.xml b/pom.xml
index 959fba93..ef506abb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,6 +61,7 @@
20251224
0.10.0
3.15.0
+ 2.9.1
3.6.3
3.5.5
3.2.8
@@ -401,6 +402,28 @@
+
+ org.cyclonedx
+ cyclonedx-maven-plugin
+ ${version.plugin.cyclonedx}
+
+
+ make-bom
+ package
+
+ makeAggregateBom
+
+
+
+
+ library
+ 1.6
+ all
+ ${project.artifactId}-${project.version}-cyclonedx
+ true
+ false
+
+
org.jacoco
jacoco-maven-plugin