Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ jobs:
**/target/surefire-reports/
**/target/failsafe-reports/
**/target/jsp-engine/
jakarta-test/target/packaged-war.log

esapi-compatibility:
name: ESAPI ${{ matrix.esapi-version }}
Expand Down
88 changes: 88 additions & 0 deletions jakarta-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Required browser and packaged WAR fixture

This optional application is a test fixture, not a dependency of an encoder
library. From the repository root, with JDK 17, Maven and Docker available:

```sh
mvn -B -ntp -Dmaven.repo.local=/tmp/encoder-browser-m2 clean verify -PtestJakarta
```

Use an empty task-specific Maven directory for fresh validation. The reactor
packages the matching encoder JARs without installing them. CI requires this
profile in the `Java CI gate`; unavailable Docker is a failure, not a skipped or
advisory test. CI also compares the JAR inside the WAR byte-for-byte with the
reactor's Jakarta adapter.

## Coverage decision (#93)

Retain the browser fixture. The Docker-free [Jasper tests](../compatibility/jsp-engine/README.md)
cover every packaged basic/advanced tag and EL binding, coercions, output bytes,
and invalid JSP translation. They cannot replace these browser assertions:

- `ItemControllerTest`: JSP/JSTL startup, tag and EL output interpreted as text in
actual DOM cells, no injected script elements, standards-mode HTML.
- `JavaScriptTemplateTest`: all four JavaScript encoders through quoted strings
and ordinary template literals, interpolation boundaries, HTML script and
event-attribute parsing, controls and lone surrogates through UTF-8, and the
explicitly unsupported raw-template round-trip behavior.
- `PackagedWarIT`: launches `java -jar` on the finished executable WAR on a
random loopback port, renders both packaged views, checks exact encoded cell
content, and confirms JSTL API/implementation and adapter JARs are packaged.
It terminates the server even on failure. This test needs no Docker.

Browser sessions and containers are explicitly closed in `AfterAll` with
`finally` cleanup. Video recording is disabled, so no unused recorder image is
started. Surefire/Failsafe output and `target/packaged-war.log` are retained by CI.
A local Chrome-only diagnostic for the JavaScript suite remains available with
`-Dencoder.browser.local=true -Dtest=JavaScriptTemplateTest`; it is not the CI gate.

## Framework and API boundaries

As reviewed on 2026-09-25, this fixture uses supported Spring Boot **4.1.1** and
its managed dependencies, on JDK **17**, with Tomcat/Jasper **11.0.26**
(Servlet **6.1**, Pages **4.0**, EL **6.0**). The two deliberate BOM overrides are
Tomcat 11.0.26, which contains the September fixes absent from Boot's managed
11.0.24, and Selenium 4.49.0, aligned with the current reviewed browser image.
Testcontainers **2.0.5**, JSTL API **3.0.2** and implementation **3.0.1** follow the
Boot BOM. The standalone Servlet/Pages/EL API JARs are removed; Tomcat supplies
the coherent implementation/API set. Both JSTL components remain packaged.
The unused JSON starter, empty test configuration/launcher, and unused service
mutation scaffold are removed.

See the [Boot support policy](https://github.com/spring-projects/spring-boot/wiki/Supported-Versions),
[system requirements](https://docs.spring.io/spring-boot/system-requirements.html),
[Spring advisories](https://spring.io/security/), and
[Tomcat 11 advisories](https://tomcat.apache.org/security-11.html).
An OSV query of the 34 resolved third-party JAR coordinates in the packaged WAR
(including provided container libraries) returned no advisories on 2026-09-25.
That dated result excludes container OS packages, build plugins and test-only
JARs; it is not a permanent or repository-wide clean bill.
Recheck these sources and the resolved dependency graph with each upgrade and
before release. Do not copy these fixture requirements into library support claims.
The published adapters retain Java 8 and their existing provided APIs. The
separate javax/Jakarta engines and Java 8/11/17/21/25 packaged consumers still
exercise older contracts. OSGi's conservative Pages import range is unchanged;
this Boot application is not an OSGi container test.

## Container provenance and updates

`BrowserFixture.java` and test-only `testcontainers.properties` contain immutable
multi-platform index digests fetched from Docker Hub's registry and verified
against the SHA-256 of each manifest response on 2026-09-25:

| Use | Reviewed tag | Index SHA-256 |
| --- | --- | --- |
| Browser | `selenium/standalone-chrome:4.49.0-20260909` | `7efe71e7e4a83bdf574b26bd354690928075e8f443223d2ced16a2c208eae1d7` |
| Cleanup | `testcontainers/ryuk:0.14.0` | `7c1a8a9a47c780ed0f983770a662f80deb115d95cce3e2daa3d12115b8cd28f0` |
| Host-port forwarding | `testcontainers/sshd:1.3.0` | `c50c0f59554dcdb2d9e5e705112144428ae9d04ac0af6322b365a18e24213a6a` |
| Docker startup probe | `alpine:3.24.2` | `294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6` |

The [Selenium release](https://github.com/SeleniumHQ/docker-selenium/releases/tag/4.49.0-20260909)
and [Testcontainers 2.0.5 source](https://github.com/testcontainers/testcontainers-java/tree/2.0.5)
control the browser/helper choices. The startup probe uses maintained Alpine
instead of the old default 3.17. Digest pins provide immutable identity, not a
claim that an image contains no vulnerabilities. Review publisher release notes,
image scan results and all helper versions when updating. Keep the Selenium
client and image aligned, update the tag and digest together, verify the manifest
hash/platforms again, and run the full required profile before merging. These
source/property pins require manual review; Maven Dependabot does not update them.
62 changes: 33 additions & 29 deletions jakarta-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.16</version>
<version>4.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.owasp.encoder.testing</groupId>
Expand All @@ -16,6 +16,10 @@
<description>Test for OWASP encoder jakarta JSP</description>
<properties>
<java.version>17</java.version>
<!-- September security fixes, newer than Boot 4.1.1's managed 11.0.24. -->
<tomcat.version>11.0.26</tomcat.version>
<!-- Keep the client aligned with the reviewed, digest-pinned browser image. -->
<selenium.version>4.49.0</selenium.version>
<!-- Must equal the version in the root pom.xml so this app tests the
encoder-jakarta-jsp built in the same reactor. CI checks this. -->
<encoder.version>1.5.0-SNAPSHOT</encoder.version>
Expand All @@ -28,7 +32,14 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<artifactId>spring-boot-starter-webmvc</artifactId>
<!-- This fixture renders JSPs; it has no JSON endpoints. -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jackson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
Expand All @@ -40,25 +51,14 @@
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp</groupId>
<artifactId>jakarta.servlet.jsp-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet.jsp.jstl</groupId>
<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.el</groupId>
<artifactId>jakarta.el-api</artifactId>
<version>5.0.1</version>
<exclusions>
<!-- Jasper supplies these APIs; JSTL's older transitive copies must not enter WEB-INF/lib. -->
<exclusion><groupId>jakarta.el</groupId><artifactId>jakarta.el-api</artifactId></exclusion>
<exclusion><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId></exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
Expand All @@ -70,14 +70,9 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>selenium</artifactId>
<artifactId>testcontainers-selenium</artifactId>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -90,16 +85,25 @@
<artifactId>selenium-chrome-driver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<finalName>jakarta-test</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<fixture.war>${project.build.directory}/${project.build.finalName}.war</fixture.war>
</systemPropertyVariables>
</configuration>
<executions>
<execution>
<goals><goal>integration-test</goal><goal>verify</goal></goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.owasp.encoder.testing.jakarta_test.controller;

import org.owasp.encoder.testing.jakarta_test.service.ItemService;
import java.util.List;
import org.owasp.encoder.testing.jakarta_test.dto.Item;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -14,15 +15,13 @@
@RequestMapping("/item")
public class ItemController {

private final ItemService itemService;

public ItemController(ItemService itemService) {
this.itemService = itemService;
}
private static final List<Item> ITEMS = List.of(
new Item(1, "menu", "blob"),
new Item(2, "top<script>alert(1)</script>", "fancy <script>alert(1)</script>"));

@GetMapping("/viewItems")
public String viewItems(Model model) {
model.addAttribute("items", itemService.getItems());
model.addAttribute("items", ITEMS);
return "view-items";
}
}
Original file line number Diff line number Diff line change
@@ -1,77 +1,18 @@
package org.owasp.encoder.testing.jakarta_test.dto;

/**
*
* @author jeremy
*/
public class Item {

private int id;

private String name;

private String description;

public Item() {
}
/** Immutable values exposed as bean properties to JSP EL. */
public final class Item {
private final int id;
private final String name;
private final String description;

public Item(int id, String name, String description) {
this.id = id;
this.name = name;
this.description = description;
}

/**
* Get the value of id
*
* @return the value of id
*/
public int getId() {
return id;
}

/**
* Set the value of id
*
* @param id new value of id
*/
public void setId(int id) {
this.id = id;
}

/**
* Get the value of name
*
* @return the value of name
*/
public String getName() {
return name;
}

/**
* Set the value of name
*
* @param name new value of name
*/
public void setName(String name) {
this.name = name;
}

/**
* Get the value of description
*
* @return the value of description
*/
public String getDescription() {
return description;
}

/**
* Set the value of description
*
* @param description new value of description
*/
public void setDescription(String description) {
this.description = description;
}
public int getId() { return id; }
public String getName() { return name; }
public String getDescription() { return description; }
}

This file was deleted.

This file was deleted.

5 changes: 3 additions & 2 deletions jakarta-test/src/main/webapp/WEB-INF/jsp/index.jsp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page session="false" contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<!DOCTYPE html>
<html>
<head>
Expand All @@ -7,6 +8,6 @@
</head>
<body>
<h1>Hello World!</h1>
You are likely looking for the test page located <a href="/jakarta-test/item/viewItems">here</a>.
You are likely looking for the test page located <a href="<c:url value="/item/viewItems"/>">here</a>.
</body>
</html>
3 changes: 2 additions & 1 deletion jakarta-test/src/main/webapp/WEB-INF/jsp/view-items.jsp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<%@page contentType="text/html;charset=UTF-8" language="java"%>
<%@page session="false" contentType="text/html;charset=UTF-8" language="java"%>
<%@taglib prefix="c" uri="jakarta.tags.core"%>
<%@taglib prefix="e" uri="owasp.encoder.jakarta"%>
<!DOCTYPE html>
<html>
<head>
<title>View Items</title>
Expand Down
Loading
Loading