From 0e41a9ba5463bfe733d1e24dab2e15496e89dffa Mon Sep 17 00:00:00 2001 From: Paul McCann Date: Sat, 24 Oct 2026 21:05:09 +0100 Subject: [PATCH 1/8] build: fix known CVEs in jsoup, mariadb-java-client, log4j, httpcore5 and JUnit 4 (PR 1 of #955) (#963) - jsoup 1.22.1 -> 1.23.2 (GHSA-65r4-943x-97jj, GHSA-pmhh-3w7g-xqp8) - mariadb-java-client 3.3.2 -> 3.5.11 (GHSA-xvr9-35cr-46v9, GHSA-qxvw-fvwx-5cp7, GHSA-g9jj-cgmh-9f38, GHSA-wxmm-q36w-r9xj) - log4j 2.25.4 -> 2.26.1 (GHSA-qv9r-c865-cp47 in log4j-api), aligned through log4j-bom - httpcore5 5.2.5 -> 5.4.4 (GHSA-hf6x-8p5f-cgmf), pulled in through shib-networking - Remove json-simple 1.1.1. Nothing uses it, it has been unmaintained since 2012, and it declared JUnit 4.10 (GHSA-269g-pwp5-87pp) and Hamcrest 1.1 at compile scope. Those shipped in the WAR and shadowed Hamcrest 3 in tests. Hamcrest 1.1 was the only unsigned artifact, so its noSig rule leaves .mvn/pgp-keys.map along with the junit and json-simple groupIds. mariadb-java-client 3.5 no longer depends on waffle-jna, so the waffle, caffeine and checkerframework groupIds go too. Fixes #944 Refs #955 Co-authored-by: Claude Opus 5.5 --- .mvn/pgp-keys.map | 9 ------ AGENTS.md | 3 +- pom.xml | 30 ++++++++++++------- .../java/testUtils/HamcrestClasspathTest.java | 25 ++++++++++++++++ 4 files changed, 46 insertions(+), 21 deletions(-) create mode 100644 src/test/java/testUtils/HamcrestClasspathTest.java diff --git a/.mvn/pgp-keys.map b/.mvn/pgp-keys.map index ef7086dec..6e47d0d8d 100644 --- a/.mvn/pgp-keys.map +++ b/.mvn/pgp-keys.map @@ -13,14 +13,11 @@ net.shibboleth = 0xB5B5DD332142AD657E8D87AC7D27E610B8A3DC52 # review it, then add its groupId here. com.fasterxml.jackson.core = any com.fasterxml.jackson.datatype = any -com.github.ben-manes.caffeine = any -com.github.waffle = any com.google.code.findbugs = any com.google.errorprone = any com.google.guava = any com.google.j2objc = any com.google.protobuf = any -com.googlecode.json-simple = any com.mysql = any com.zaxxer = any commons-codec = any @@ -34,7 +31,6 @@ jakarta.activation = any jakarta.el = any jakarta.mail = any jakarta.servlet.jsp.jstl = any -junit = any net.bytebuddy = any net.java.dev.jna = any net.sf.jtidy = any @@ -46,7 +42,6 @@ org.apache.santuario = any org.apache.velocity = any org.apiguardian = any org.bouncycastle = any -org.checkerframework = any org.cryptacular = any org.eclipse.angus = any org.glassfish = any @@ -68,7 +63,3 @@ org.owasp.encoder = any org.pac4j = any org.slf4j = any org.springframework = any - -# Unsigned legacy artifact pulled in via json-simple -> junit 4.10 (to be removed, see #944). -# Exact version only. -org.hamcrest:hamcrest-core:1.1 = noSig diff --git a/AGENTS.md b/AGENTS.md index 8fab41324..276d71232 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -217,10 +217,9 @@ pac4j-saml depends on **OpenSAML 5, which is not on Maven Central**. Shibboleth, - **Repository filtering** (`.mvn/maven.config`, `.mvn/rrf/groupId-shibboleth-releases.txt`): the Shibboleth repository may serve only the `org.opensaml` and `net.shibboleth` groupIds. Everything else comes from Central only. - This needs **Maven 3.9.2 or newer**: older versions don't expand the path in `.mvn/maven.config` and silently skip the filter. `maven-enforcer-plugin` refuses to build on older Maven. - The enforcer itself resolves dependencies before it fails, so if an older Maven was ever used on a machine, delete its local repository (`~/.m2/repository`) before building again. -- **PGP signature verification** (`pgpverify-maven-plugin` in `pom.xml`, rules in `.mvn/pgp-keys.map`): every dependency's signature is checked on every build (with the one unsigned exception below). +- **PGP signature verification** (`pgpverify-maven-plugin` in `pom.xml`, rules in `.mvn/pgp-keys.map`): every dependency's signature is checked on every build. There are no `noSig` exceptions; don't add one without reviewing why the artifact is unsigned. - `org.opensaml` / `net.shibboleth` are pinned to the Shibboleth release key `B5B5DD332142AD657E8D87AC7D27E610B8A3DC52`, from https://shibboleth.net/downloads/PGP_KEYS. Verify any new key there before changing the pin. - Every other groupId is listed by name and needs a valid signature. A new dependency from an unlisted groupId fails with `Not allowed artifact`: review it, then add its groupId. - - **One exception:** `org.hamcrest:hamcrest-core:1.1` is unsigned and allowed with `noSig` for that exact version only. It arrives through `json-simple` → JUnit 4.10, and is to be removed with #944. Don't add `noSig` rules for anything else without the same scrutiny. - **Never add a wildcard rule** (`*`, `org.*`, …): any matching rule lets an artifact pass, so a broad rule would silently undo the Shibboleth pin. - Keys are fetched from `keys.openpgp.org` / `keyserver.ubuntu.com` and cached in the local repository. - **Test:** `bash src/test/scripts/pgp-verification-test.sh` (run in CI's build job) proves three things on the Maven that runs it: the repository filter is enforced, a tampered OpenSAML jar fails the build, and an OpenSAML artifact signed by another key fails the build. diff --git a/pom.xml b/pom.xml index 256c46c8a..1ec0a9f31 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ servlet mocks. --> 7.0.10 6.6.0 + 2.26.1 ${project.basedir}/.mvn/pgp-keys.map @@ -51,6 +52,21 @@ pom import + + + org.apache.logging.log4j + log4j-bom + ${log4j.version} + pom + import + + + + org.apache.httpcomponents.core5 + httpcore5 + 5.4.4 + @@ -71,7 +87,7 @@ org.apache.logging.log4j log4j-slf4j2-impl - 2.25.4 + ${log4j.version} de.mkammerer @@ -82,7 +98,7 @@ org.apache.logging.log4j log4j-core - 2.25.4 + ${log4j.version} @@ -90,12 +106,6 @@ json 20260522 - - - com.googlecode.json-simple - json-simple - 1.1.1 - commons-io commons-io @@ -112,7 +122,7 @@ org.jsoup jsoup - 1.22.1 + 1.23.2 @@ -166,7 +176,7 @@ org.mariadb.jdbc mariadb-java-client - 3.3.2 + 3.5.11 diff --git a/src/test/java/testUtils/HamcrestClasspathTest.java b/src/test/java/testUtils/HamcrestClasspathTest.java new file mode 100644 index 000000000..355212dbe --- /dev/null +++ b/src/test/java/testUtils/HamcrestClasspathTest.java @@ -0,0 +1,25 @@ +package testUtils; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Guards the test classpath against the Hamcrest 1.1 that the unused json-simple dependency brought + * in through JUnit 4.10 (#944). Its {@code Matcher} shadowed Hamcrest 3's, so a failing {@code + * assertThat} threw {@code NoSuchMethodError} instead of reporting the mismatch. + */ +class HamcrestClasspathTest { + + @Test + void failingAssertThat_ReportsExpectedAndActual() { + AssertionError error = + assertThrows(AssertionError.class, () -> assertThat("actual", equalTo("expected"))); + + assertTrue(error.getMessage().contains("Expected: \"expected\""), error.getMessage()); + assertTrue(error.getMessage().contains("but: was \"actual\""), error.getMessage()); + } +} From 8db56e4d1e7265c4d86fd01aab3bc67ce6a711bc Mon Sep 17 00:00:00 2001 From: Paul McCann Date: Sat, 24 Oct 2026 21:05:09 +0100 Subject: [PATCH 2/8] build: update runtime libraries to their supported releases (PR 2 of #955) (#964) - HikariCP 4.0.3 -> 7.1.0 (4.x was the Java 8 line and no longer gets releases) - OWASP Java Encoder 1.3.1 -> 1.6.0 - commons-codec 1.21.0 -> 1.22.0. Not 1.23.0: its strict Base64 decoder, which OpenSAML uses to decode SAMLResponse, rejects input that 1.22.0 accepts, and SamlSsoTest fails on every login. Dependabot is told to skip 1.23.0 only. - commons-logging 1.2 -> 1.4.0 (Spring 7 logs through it) - argon2-jvm 2.11 -> 2.12 - org.json 20260522 -> 20260814 mysql-connector-j stays on 9.7.0, the newest release of its long-term support line. jtidy is left on r938 for now: neither replacement is a drop-in (#960). Refs #955 Co-authored-by: Claude Opus 5.5 --- .github/dependabot.yml | 4 ++++ pom.xml | 15 ++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 74e697194..3c7ee70a5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,10 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + ignore: + # Its strict Base64 decoder breaks SAML login (see the commons-codec comment in pom.xml) + - dependency-name: "commons-codec:commons-codec" + versions: ["1.23.0"] - package-ecosystem: github-actions directory: "/" diff --git a/pom.xml b/pom.xml index 1ec0a9f31..ebe2edb70 100644 --- a/pom.xml +++ b/pom.xml @@ -92,7 +92,7 @@ de.mkammerer argon2-jvm - 2.11 + 2.12 @@ -104,7 +104,7 @@ org.json json - 20260522 + 20260814 commons-io @@ -112,11 +112,12 @@ 2.22.0 - + commons-codec commons-codec - 1.21.0 + 1.22.0 @@ -183,21 +184,21 @@ com.zaxxer HikariCP - 4.0.3 + 7.1.0 org.owasp.encoder encoder - 1.3.1 + 1.6.0 commons-logging commons-logging - 1.2 + 1.4.0 From 6309e6cf646569579486937fdd8ca94322999a47 Mon Sep 17 00:00:00 2001 From: Paul McCann Date: Sat, 24 Oct 2026 21:05:10 +0100 Subject: [PATCH 3/8] build: update test libraries and keep them out of the WAR (PR 3 of #955) (#965) - JUnit Jupiter 5.11.4 -> 6.1.3 - Mockito 4.9.0 -> 5.24.0, now test-scoped (it was compile scope and shipped in the WAR with byte-buddy and objenesis) - slf4j-simple 1.7.36 -> 2.0.20, matching the SLF4J 2 API that pac4j uses - io.github.cdimascio:java-dotenv 5.2.2 -> dotenv-java 3.2.0 (the project's pure-Java successor, same API), now test-scoped. The old artifact shipped the Kotlin 1.4 stdlib in the WAR. - org.jetbrains:annotations is now declared (compile-only) for the @Nullable in XxeLesson and XxeChallenge1OldWebService, which used to compile only because the Kotlin stdlib brought it in. The org.jetbrains.kotlin PGP rule goes. Refs #955 Co-authored-by: Claude Opus 5.5 --- .mvn/pgp-keys.map | 1 - pom.xml | 29 +++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.mvn/pgp-keys.map b/.mvn/pgp-keys.map index 6e47d0d8d..34e4bfbe9 100644 --- a/.mvn/pgp-keys.map +++ b/.mvn/pgp-keys.map @@ -48,7 +48,6 @@ org.glassfish = any org.glassfish.web = any org.hamcrest = any org.jetbrains = any -org.jetbrains.kotlin = any org.json = any org.jsoup = any org.jspecify = any diff --git a/pom.xml b/pom.xml index ebe2edb70..befd02665 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ 7.0.10 6.6.0 2.26.1 + 6.1.3 ${project.basedir}/.mvn/pgp-keys.map @@ -236,14 +237,14 @@ org.junit.jupiter junit-jupiter-api - 5.11.4 + ${junit.version} test org.junit.jupiter junit-jupiter-engine - 5.11.4 + ${junit.version} test @@ -259,22 +260,34 @@ org.slf4j slf4j-simple - 1.7.36 + 2.0.20 test - + org.mockito mockito-core - 4.9.0 + 5.24.0 + test - + + + org.jetbrains + annotations + 26.1.0 + provided + + + io.github.cdimascio - java-dotenv - 5.2.2 + dotenv-java + 3.2.0 + test From b7bb033c4d22ad23bc65f705ff51e10efd00729f Mon Sep 17 00:00:00 2001 From: Paul McCann Date: Sat, 24 Oct 2026 21:05:10 +0100 Subject: [PATCH 4/8] build: update Maven plugins (PR 4 of #955) (#966) - maven-compiler-plugin 3.13.0 -> 3.16.0 - maven-surefire-plugin / maven-failsafe-plugin 3.5.2 -> 3.6.0. failsafe 3.6 deprecates skipTests in favour of skipITs, which the pom already sets (and which still defaults to -DskipTests), so the skipTests setting goes. - maven-war-plugin 3.3.2 -> 3.5.1 - keytool-maven-plugin 1.7 -> 2.0.2. It no longer needs the plexus-utils override, and generates the same keystore (same DN, 2048-bit RSA, SHA256withRSA, 90 days). - exec-maven-plugin 3.6.3 -> 3.6.4, build-helper-maven-plugin 3.6.1 -> 3.6.2, properties-maven-plugin 1.3.0 -> 1.3.1, docker-maven-plugin 0.48.1 -> 0.49.0 - spotless-maven-plugin 3.6.0 -> 3.10.4, google-java-format 1.25.2 -> 1.28.0. 1.28.0 is the newest google-java-format that runs on JDK 17 (1.29.0 and later fail on every file) and it leaves all sources unchanged. Refs #955 Co-authored-by: Claude Opus 5.5 --- pom.xml | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index befd02665..7a8490750 100644 --- a/pom.xml +++ b/pom.xml @@ -353,7 +353,7 @@ exec-maven-plugin - 3.6.3 + 3.6.4 org.codehaus.mojo @@ -371,7 +371,7 @@ org.codehaus.mojo keytool-maven-plugin - 1.7 + 2.0.2 generate-tls-key @@ -390,13 +390,6 @@ ${TLS_KEYSTORE_PASS} ${STORE_TYPE} - - - org.codehaus.plexus - plexus-utils - 4.0.3 - - @@ -414,7 +407,7 @@ io.fabric8 docker-maven-plugin - 0.48.1 + 0.49.0 start @@ -517,7 +510,7 @@ org.codehaus.mojo properties-maven-plugin - 1.3.0 + 1.3.1 initialize @@ -534,7 +527,7 @@ maven-compiler-plugin - 3.13.0 + 3.16.0 17 UTF-8 @@ -542,7 +535,7 @@ maven-war-plugin - 3.3.2 + 3.5.1 src/main/webapp @@ -593,7 +586,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.2 + 3.6.0 ${skipUTs} @@ -615,9 +608,8 @@ org.apache.maven.plugins maven-failsafe-plugin - 3.5.2 + 3.6.0 - ${skipTests} ${skipITs} **/*Test* @@ -638,7 +630,7 @@ com.diffplug.spotless spotless-maven-plugin - 3.6.0 + 3.10.4 @@ -646,8 +638,9 @@ src/test/java/**/*.java src/it/java/**/*.java + - 1.25.2 + 1.28.0 @@ -664,7 +657,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.6.1 + 3.6.2 add-test-source From 5763fdee6570339d730eb84adbacab79c7cbc9b1 Mon Sep 17 00:00:00 2001 From: Paul McCann Date: Sat, 24 Oct 2026 21:05:11 +0100 Subject: [PATCH 5/8] ci: update GitHub Actions (PR 5 of #955) (#967) Every action stays pinned to a full commit SHA. - actions/checkout v6.0.3 -> v7.0.1 (v7 only blocks checking out fork PRs under pull_request_target / workflow_run, which these workflows don't use) - actions/setup-java v5.7.0 -> v6.1.0 - actions/cache v5.0.5 -> v6.1.0 - actions/upload-artifact v7.0.1 -> v7.0.2 - dorny/paths-filter v4.0.1 -> v4.0.3 - anchore/sbom-action v0.24.0 -> v0.24.3 - docker/build-push-action v7.3.0 -> v7.4.0, docker/login-action v4.2.0 -> v4.7.0, docker/metadata-action v6.1.0 -> v6.2.0 Refs #955 Co-authored-by: Claude Opus 5.5 --- .github/workflows/release.yml | 26 +++++++++++++------------- .github/workflows/test.yml | 22 +++++++++++----------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b2ed3a359..b06d124ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,9 +21,9 @@ jobs: name: build runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK 17 - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 with: distribution: 'temurin' java-version: '17' @@ -38,42 +38,42 @@ jobs: - name: Build Maven with Docker Profile and Generate SBOM run: mvn clean install -Pdocker -DskipTests -B -DexcludeTestProject=true cyclonedx:makeBom - name: Upload WAR File - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: security_shepherd_war path: target/*.war - name: Docker Compose Build run: docker-compose build - name: Generate Tomcat Docker Image SBOM - uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3 id: tomcat with: format: cyclonedx-json image: owasp/security-shepherd output-file: ${{ github.workspace }}/target/owasp-security-shepherd-tomcat.cyclonedx.json - name: Generate MariaDB Docker Image SBOM - uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3 id: mariadb with: format: cyclonedx-json image: owasp/security-shepherd_mariadb output-file: ${{ github.workspace }}/target/owasp-security-shepherd-mariadb.cyclonedx.json - name: Generate MongoDB Docker Image SBOM - uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 + uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3 id: mongodb with: format: cyclonedx-json image: owasp/security-shepherd_mongo output-file: ${{ github.workspace }}/target/owasp-security-shepherd-mongodb.cyclonedx.json - name: Upload SBOMs - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: security_shepherd_sboms path: | target/*.json target/*.xml - name: Cache Maven packages - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: key: maven-output-${{ hashFiles('target/**') }} path: | @@ -87,17 +87,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Log in to Docker Hub - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 + uses: docker/login-action@81c6f93d4bc991e510b342a00b419b3bb4efc5c6 # v4.7.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 with: images: owasp/security-shepherd # Branch pushes publish :dev / :master. A release tag such as 4.1.0 @@ -109,7 +109,7 @@ jobs: type=semver,pattern={{major}} - name: Restore Cached Maven packages - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: key: maven-output-${{ hashFiles('target/**') }} path: | @@ -124,7 +124,7 @@ jobs: env-file: .env - name: Build and push Tomcat - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . push: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d28b78d44..4db6614fa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,8 +25,8 @@ jobs: src: ${{ steps.filter.outputs.src }} java: ${{ steps.filter.outputs.java }} steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 id: filter with: filters: | @@ -44,7 +44,7 @@ jobs: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 with: fail-on-severity: critical @@ -54,8 +54,8 @@ jobs: if: needs.detect-changes.outputs.java == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 with: distribution: temurin java-version: '17' @@ -68,8 +68,8 @@ jobs: if: needs.detect-changes.outputs.src == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 with: distribution: temurin java-version: '17' @@ -88,8 +88,8 @@ jobs: if: needs.detect-changes.outputs.src == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 with: distribution: temurin java-version: '17' @@ -111,8 +111,8 @@ jobs: - '8888:3306' options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 with: distribution: temurin java-version: '17' From 776ee74ec41395fa1e36f6afc61254d2563a898b Mon Sep 17 00:00:00 2001 From: Paul McCann Date: Sat, 24 Oct 2026 21:05:11 +0100 Subject: [PATCH 6/8] build: move MariaDB from 10.6.11 to 12.3 LTS, and test against it in CI (PR 6 of #955) (#968) * build: move MariaDB from 10.6.11 to 12.3 LTS, and test against it in CI (PR 6 of #955) MariaDB 10.6 reached end of life in July 2026. 12.3 is the current long-term support release (supported to June 2029). - .env: DB_VERSION=12.3 (the 12.3 tag, so a rebuild picks up 12.3.x patch releases, as the Tomcat image already does with 11.0-jre17-temurin) - docker/mariadb/Dockerfile: start mariadbd. MariaDB 11+ images no longer ship the mysqld name, and the container exited at once. - docker-compose.yml: MARIADB_AUTO_UPGRADE=1, so an existing 10.6 data volume gets its system tables upgraded (after the entrypoint backs them up) on first start. Tested: a 10.6.11 volume keeps its users and stored procedures under 12.3. - coreSchema.sql: name the collation of core (utf8mb4_general_ci, what it has always had) and give backup the same. Both took the server default: on 12.3 that is utf8mb4_uca1400_ai_ci, and backup was latin1 on 10.6. CoreSchemaCharsetIT checks both. The challenge schemas already name their own collations and are unchanged. - CI integration tests ran against mysql:5.7 (end of life 2023), not the database the app ships with. They now use mariadb:12.3. - README: upgrade note and backup command. AGENTS.md: test DB container is mariadb:12.3. Refs #955 Co-Authored-By: Claude Opus 5.5 * fix: pin the collation of every core table, not just the schema On MariaDB 12.3 the integration tests failed with "Illegal mix of collations (utf8mb4_uca1400_ai_ci,IMPLICIT) and (utf8mb4_general_ci,IMPLICIT) for operation '='" in suspendUser, resetUserBadSubmission and every other procedure that compares a column with a parameter. Eight core tables declare DEFAULT CHARACTER SET = utf8mb4 without a collation, and on 12.3 that means utf8mb4_uca1400_ai_ci whatever the schema's collation is. The procedure parameters take the schema's utf8mb4_general_ci. Those tables now name utf8mb4_general_ci too. CoreSchemaCharsetIT now also checks every table in core and backup, and calls a procedure that compares a column with a parameter. Against the previous schema on 12.3 both fail, along with GetterAuthIT. Refs #955 Co-Authored-By: Claude Opus 5.5 * docs: write the MariaDB upgrade backup outside the repository, readable by its owner only Review feedback: with a umask of 022 the redirect created a world-readable dump of every user's password hash, in whatever directory it was run from (often the checkout). It now goes to a mode 700 directory under $HOME and is created mode 600, matching the conf-volume backup instructions. The root password goes to mysqldump through MYSQL_PWD instead of its command line, and --events includes the backup event. Tested against the MariaDB 10.6.11 image: directory drwx------, file -rw-------, complete dump with routines and events. Refs #955 Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- .env | 2 +- .github/workflows/test.yml | 9 +- AGENTS.md | 2 +- README.md | 13 +++ docker-compose.yml | 2 + docker/mariadb/Dockerfile | 2 +- src/it/java/dbProcs/CoreSchemaCharsetIT.java | 94 ++++++++++++++++++++ src/main/resources/database/coreSchema.sql | 26 +++--- 8 files changed, 133 insertions(+), 17 deletions(-) create mode 100644 src/it/java/dbProcs/CoreSchemaCharsetIT.java diff --git a/.env b/.env index 842dd115a..cfd55ed55 100644 --- a/.env +++ b/.env @@ -1,7 +1,7 @@ VERSION=3.1 TOMCAT_DOCKER_VERSION=11.0-jre17-temurin -DB_VERSION=10.6.11 +DB_VERSION=12.3 MONGODB_VERSION=4.1.13 IMAGE_TOMCAT=owasp/security-shepherd diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4db6614fa..fe7f7d257 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -102,14 +102,15 @@ jobs: if: needs.detect-changes.outputs.src == 'true' runs-on: ubuntu-latest services: + # Keep in step with DB_VERSION in .env, the MariaDB the Docker image runs mysql: - image: mysql:5.7 + image: mariadb:12.3 env: - MYSQL_ROOT_PASSWORD: password - MYSQL_DATABASE: core + MARIADB_ROOT_PASSWORD: password + MARIADB_DATABASE: core ports: - '8888:3306' - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + options: --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0 diff --git a/AGENTS.md b/AGENTS.md index 276d71232..4cc54a917 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,7 +61,7 @@ docker run -d --name secshep_test_db \ -e MYSQL_ROOT_PASSWORD=CowSaysMoo \ -e MYSQL_DATABASE=core \ -p 3306:3306 \ - mariadb:10.6.11 + mariadb:12.3 ``` The docker-compose `db` service is **not suitable for quick test runs** — it requires `mvn -Pdocker validate` first to generate SQL init scripts, and those scripts run on first container startup to create all challenge schemas. diff --git a/README.md b/README.md index 797586259..ca5e3a579 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,19 @@ Delete `$BACKUP_DIR` (or move it somewhere safe) once the upgrade is working. On If you had hand-edited the old `server.xml` or `web.xml` (custom connectors, ports, ciphers and so on), re-apply those edits to the new Tomcat 11 files, using the backup as a reference. Don't copy the old files back. +##### MariaDB 12.3 +The database image now runs MariaDB 12.3 (long-term support) instead of 10.6, which is no longer supported. The `data` volume is kept. On its first start, `MARIADB_AUTO_UPGRADE=1` in `docker-compose.yml` upgrades the volume's system tables, after taking a backup of them inside the volume. Back up your data before rebuilding anyway, because a volume can't be moved back to 10.6 once 12.3 has opened it. Run this while the old container is still up. The dump holds every user's password hash, so like the `conf` backup above it goes outside the repository and is readable by you only: + +```console +BACKUP_DIR="$HOME/shepherd-db-backup" +mkdir -p "$BACKUP_DIR" && chmod 700 "$BACKUP_DIR" +(umask 077 && docker exec secshep_mariadb sh -c \ + 'export MYSQL_PWD="$MYSQL_ROOT_PASSWORD"; exec mysqldump -uroot --all-databases --routines --events' \ + > "$BACKUP_DIR/shepherd-db-backup.sql") +``` + +Delete `$BACKUP_DIR` (or move it somewhere safe) once the upgrade is working. + #### Full Guide [Docker-Environment-Setup](https://github.com/OWASP/SecurityShepherd/wiki/Docker-Environment-Setup) diff --git a/docker-compose.yml b/docker-compose.yml index ac07307a5..53fb88809 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,8 @@ services: DOCKER_NETWORK_NAME: ${DOCKER_NETWORK_NAME} environment: - MYSQL_ROOT_PASSWORD=${DB_PASS} + # Upgrades the system tables of a data volume created by an older MariaDB (e.g. 10.6) + - MARIADB_AUTO_UPGRADE=1 ports: - "${DB_SERVER_MAPPED_HOST}:${DB_PORT_MAPPED_HOST}:3306" mongo: diff --git a/docker/mariadb/Dockerfile b/docker/mariadb/Dockerfile index 67db63d26..c7180842d 100644 --- a/docker/mariadb/Dockerfile +++ b/docker/mariadb/Dockerfile @@ -41,4 +41,4 @@ COPY --from=builder /workdir/my.cnf /etc/mysql/ ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 3306 -CMD ["mysqld"] +CMD ["mariadbd"] diff --git a/src/it/java/dbProcs/CoreSchemaCharsetIT.java b/src/it/java/dbProcs/CoreSchemaCharsetIT.java new file mode 100644 index 000000000..2cfc9d9d8 --- /dev/null +++ b/src/it/java/dbProcs/CoreSchemaCharsetIT.java @@ -0,0 +1,94 @@ +package dbProcs; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import testUtils.TestProperties; + +/** + * coreSchema.sql must give core and backup, and every table in them, the same character set and + * collation on every server. Left to the server default they changed between MariaDB 10.6 (latin1 + * for backup) and 12.3 (utf8mb4_uca1400_ai_ci). A table whose collation differs from the schema's + * breaks every stored procedure that compares one of its columns with a parameter ("Illegal mix of + * collations"). + */ +public class CoreSchemaCharsetIT { + + private static final Logger log = LogManager.getLogger(CoreSchemaCharsetIT.class); + + @BeforeAll + public static void setup() throws IOException, SQLException { + TestProperties.setTestPropertiesFileDirectory(log); + TestProperties.createMysqlResource(); + TestProperties.ensureSchemaReady(log); + } + + @Test + public void coreSchema_IsUtf8mb4GeneralCi() throws IOException, SQLException { + assertSchemaCharset("core"); + } + + @Test + public void backupSchema_IsUtf8mb4GeneralCi() throws IOException, SQLException { + assertSchemaCharset("backup"); + } + + @Test + public void coreAndBackupTables_AreUtf8mb4GeneralCi() throws IOException, SQLException { + try (Connection conn = Database.getDatabaseConnection(null); + PreparedStatement ps = + conn.prepareStatement( + "SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION FROM information_schema.TABLES" + + " WHERE TABLE_SCHEMA IN ('core', 'backup') AND TABLE_TYPE = 'BASE TABLE'"); + ResultSet rs = ps.executeQuery()) { + int tables = 0; + while (rs.next()) { + tables++; + assertEquals( + "utf8mb4_general_ci", + rs.getString(3), + "collation of " + rs.getString(1) + "." + rs.getString(2)); + } + assertTrue(tables > 0, "no tables found in core or backup"); + } + } + + @Test + public void procedureComparingColumnWithParameter_Runs() throws IOException, SQLException { + // Other ITs reseed core.users, so don't rely on any row: the collation check happens when the + // statement runs, whether or not a row matches + try (Connection conn = Database.getDatabaseConnection(null); + CallableStatement cs = conn.prepareCall("CALL core.userFind(?)")) { + cs.setString(1, "noSuchUser"); + try (ResultSet rs = cs.executeQuery()) { + assertFalse(rs.next(), "userFind found a user that does not exist"); + } + } + } + + private static void assertSchemaCharset(String schema) throws IOException, SQLException { + try (Connection conn = Database.getDatabaseConnection(null); + PreparedStatement ps = + conn.prepareStatement( + "SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME" + + " FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = ?")) { + ps.setString(1, schema); + try (ResultSet rs = ps.executeQuery()) { + assertTrue(rs.next(), "schema " + schema + " not found"); + assertEquals("utf8mb4", rs.getString(1)); + assertEquals("utf8mb4_general_ci", rs.getString(2)); + } + } + } +} diff --git a/src/main/resources/database/coreSchema.sql b/src/main/resources/database/coreSchema.sql index 12b74f0c1..98450823e 100644 --- a/src/main/resources/database/coreSchema.sql +++ b/src/main/resources/database/coreSchema.sql @@ -6,7 +6,11 @@ SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL'; SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; DROP SCHEMA IF EXISTS `core` ; -CREATE SCHEMA IF NOT EXISTS `core` DEFAULT CHARACTER SET utf8mb4 ; +-- Collation named explicitly, here and on every table: MariaDB 11.5+ defaults utf8mb4 to +-- utf8mb4_uca1400_ai_ci, which compares and sorts differently from the utf8mb4_general_ci core has +-- always had. A table that differs from the schema breaks the stored procedures ("Illegal mix of +-- collations"), because their parameters take the schema's collation. +CREATE SCHEMA IF NOT EXISTS `core` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ; USE `core` ; SELECT "Creating Tables" FROM DUAL; @@ -20,7 +24,7 @@ CREATE TABLE IF NOT EXISTS `core`.`class` ( `classYear` VARCHAR(5) NOT NULL , PRIMARY KEY (`classId`) ) ENGINE = InnoDB -DEFAULT CHARACTER SET = utf8mb4; +DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci; -- ----------------------------------------------------- -- Table `core`.`users` @@ -53,7 +57,7 @@ CREATE TABLE IF NOT EXISTS `core`.`users` ( ON DELETE CASCADE ON UPDATE CASCADE) ENGINE = InnoDB -DEFAULT CHARACTER SET = utf8mb4; +DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci; -- ----------------------------------------------------- -- Table `core`.`modules` @@ -74,7 +78,7 @@ CREATE TABLE IF NOT EXISTS `core`.`modules` ( `isUnsafe` TINYINT(1) NOT NULL DEFAULT TRUE, PRIMARY KEY (`moduleId`) ) ENGINE = InnoDB -DEFAULT CHARACTER SET = utf8mb4; +DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci; -- ----------------------------------------------------- -- Table `core`.`medals` @@ -100,7 +104,7 @@ CREATE TABLE IF NOT EXISTS `core`.`medals` ( ON DELETE CASCADE ON UPDATE CASCADE) ENGINE = InnoDB -DEFAULT CHARACTER SET = utf8mb4; +DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci; -- ----------------------------------------------------- -- Table `core`.`results` @@ -130,7 +134,7 @@ CREATE TABLE IF NOT EXISTS `core`.`results` ( ON DELETE CASCADE ON UPDATE CASCADE) ENGINE = InnoDB -DEFAULT CHARACTER SET = utf8mb4; +DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci; -- ----------------------------------------------------- -- Table `core`.`cheatsheet` @@ -148,7 +152,7 @@ CREATE TABLE IF NOT EXISTS `core`.`cheatsheet` ( ON DELETE CASCADE ON UPDATE CASCADE) ENGINE = InnoDB -DEFAULT CHARACTER SET = utf8mb4; +DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci; -- ----------------------------------------------------- -- Table `core`.`sequence` @@ -158,7 +162,7 @@ CREATE TABLE IF NOT EXISTS `core`.`sequence` ( `currVal` BIGINT(20) NOT NULL DEFAULT 282475249 , PRIMARY KEY (`tableName`) ) ENGINE = InnoDB -DEFAULT CHARACTER SET = utf8mb4; +DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci; -- ----------------------------------------------------- -- Table `core`.`settings` @@ -168,7 +172,7 @@ CREATE TABLE IF NOT EXISTS `core`.`settings` ( `value` VARCHAR(64) NOT NULL , PRIMARY KEY (`setting`) ) ENGINE = InnoDB -DEFAULT CHARACTER SET = utf8mb4; +DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci; SELECT "Creating Procedures" FROM DUAL; @@ -1749,7 +1753,9 @@ call userCreate(null, 'admin', '$argon2i$v=19$m=65536,t=10,p=1$7oxgR8QkdOd4tsHFi SELECT "Creating BackUp Schema" FROM DUAL; DROP DATABASE IF EXISTS backup; -CREATE DATABASE backup; +-- Same character set as core, whose tables it copies; it used to take the server default (latin1 on +-- MariaDB 10.6) +CREATE DATABASE backup DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; SET GLOBAL event_scheduler = ON; SET @@global.event_scheduler = ON; From 4bbf4cf60178dcaac4055adb01ef0c34912b9df3 Mon Sep 17 00:00:00 2001 From: Paul McCann Date: Sat, 24 Oct 2026 21:58:25 +0100 Subject: [PATCH 7/8] ci: stop Dependabot proposing mariadb-java-client 4.x and mysql-connector-j innovation releases (#978) - mariadb-java-client majors: 4.0 turns TLS on by default and the Docker database runs with ssl=0, so every Docker install would lose its database (#975). Remove the rule once #977 is done. - mysql-connector-j majors: stay on 9.7, the long-term support line. 26.x is an innovation release supported only until the next one (#974). Patch releases still come through. Refs #977, #955 Co-authored-by: Claude Opus 5.5 --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3c7ee70a5..32ae5f18f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,13 @@ updates: # Its strict Base64 decoder breaks SAML login (see the commons-codec comment in pom.xml) - dependency-name: "commons-codec:commons-codec" versions: ["1.23.0"] + # 4.0 turns TLS on by default and the Docker database has it off. Remove once #977 is done. + - dependency-name: "org.mariadb.jdbc:mariadb-java-client" + update-types: ["version-update:semver-major"] + # Stay on the long-term support line (9.7): later majors such as 26.x are short-lived + # innovation releases. Patch releases still come through. + - dependency-name: "com.mysql:mysql-connector-j" + update-types: ["version-update:semver-major"] - package-ecosystem: github-actions directory: "/" From 0dbf3d22e4e2927a2d29f1be151bea59f2d7f756 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Oct 2026 22:05:31 +0100 Subject: [PATCH 8/8] build(deps): bump commons-codec:commons-codec from 1.22.0 to 1.22.1 (#976) Bumps [commons-codec:commons-codec](https://github.com/apache/commons-codec) from 1.22.0 to 1.22.1. - [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt) - [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.22.0...rel/commons-codec-1.22.1) --- updated-dependencies: - dependency-name: commons-codec:commons-codec dependency-version: 1.22.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Paul McCann --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7a8490750..2a1d58ea2 100644 --- a/pom.xml +++ b/pom.xml @@ -118,7 +118,7 @@ commons-codec commons-codec - 1.22.0 + 1.22.1