CVE-2019-15477
Description
Jooby before 1.6.4 had a stored/reflected XSS vulnerability via unescaped error messages in the default error handler.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Jooby before 1.6.4 had a stored/reflected XSS vulnerability via unescaped error messages in the default error handler.
Vulnerability
CVE-2019-15477 describes a cross-site scripting (XSS) vulnerability in the Jooby web framework for Java and Kotlin, affecting versions prior to 1.6.4. The root cause is that the default error handler output error messages without HTML escaping. When an exception occurred, the message and reason fields from the exception were directly inserted into HTML responses (and potentially other content types) without sanitization. This allowed an attacker to inject arbitrary JavaScript code through these fields [1][2].
Exploitation
An attacker can exploit this vulnerability by triggering an exception whose error message or reason contains malicious JavaScript. For example, if the framework throws an error for a malformed request, the error message might be user-controllable (e.g., via the request path or other input). The default error handler would then render this message in an HTML error page, executing the injected script in the context of the victim's browser. No authentication is required if the endpoint is publicly accessible. The attack vector is reflected XSS, as the payload is delivered through the error response and does not persist [1][2].
Impact
Successful exploitation allows an attacker to execute arbitrary HTML and JavaScript in a user's browser within the context of the affected website. This can lead to session hijacking, credential theft, defacement, or redirection to malicious sites. The impact is amplified if the victim has administrative privileges or if the application handles sensitive data. The vulnerability is rated with a CVSS v3 base score of 6.1 (Medium), reflecting low attack complexity and network access via the web interface [2].
Mitigation
The vulnerability was fixed in Jooby version 1.6.4. The commit introduces an XSS filter that escapes values for HTML output using env.xss("html") and applies it to the message and reason fields before rendering the error response [1]. Users should upgrade to Jooby 1.6.4 or later. No workarounds were provided for older versions. The issue is not listed in CISA's Known Exploited Vulnerabilities (KEV) catalog as of early 2025 [2].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jooby:joobyMaven | < 1.6.4 | 1.6.4 |
Affected products
2- Jooby/Joobydescription
Patches
134856a738829Avoiding possible XSS attack through the default error handler. See jooby-project/jooby#1366
4 files changed · +55 −5
jooby/src/main/java/org/jooby/Err.java+19 −3 modified@@ -205,7 +205,11 @@ import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Supplier; import com.typesafe.config.Config; import org.jooby.funzy.Try; @@ -287,12 +291,24 @@ public void handle(final Request req, final Response rsp, final Err ex) throws T log.error("execution of: {}{} resulted in exception\nRoute:\n{}\n\nStacktrace:", req.method(), req.path(), req.route().print(6), ex); Config conf = req.require(Config.class); + Env env = req.require(Env.class); boolean stackstrace = Try.apply(() -> conf.getBoolean("err.stacktrace")) - .orElse(req.require(Env.class).name().equals("dev")); + .orElse(env.name().equals("dev")); + + Function<Object, String> xssFilter = env.xss("html").compose(Objects::toString); + BiFunction<String, Object, String> escaper = (k, v) -> xssFilter.apply(v); + + Supplier<Map<String, Object>> detailsProvider = () -> { + Map<String, Object> map = ex.toMap(stackstrace); + map.compute("message", escaper); + map.compute("reason", escaper); + return map; + }; + rsp.send( Results - .when(MediaType.html, () -> Results.html(VIEW).put("err", ex.toMap(stackstrace))) - .when(MediaType.all, () -> ex.toMap(stackstrace))); + .when(MediaType.html, () -> Results.html(VIEW).put("err", detailsProvider.get())) + .when(MediaType.all, detailsProvider::get)); } }
jooby/src/main/java/org/jooby/internal/HttpHandlerImpl.java+1 −1 modified@@ -627,7 +627,7 @@ private static Route[] routes(final Set<Route.Definition> routeDefs, final Strin // default /favicon.ico handler: rsp.status(Status.NOT_FOUND).end(); } else { - throw new Err(Status.NOT_FOUND, req.path(true)); + throw new Err(Status.NOT_FOUND, req.path()); } } }, method, path, "err", accept));
jooby/src/main/java/org/jooby/internal/RouteImpl.java+1 −1 modified@@ -233,7 +233,7 @@ public class RouteImpl implements RouteWithFilter { public static RouteWithFilter notFound(final String method, final String path) { return new FallbackRoute("404", method, path, MediaType.ALL, (req, rsp, chain) -> { if (!rsp.status().isPresent()) { - throw new Err(Status.NOT_FOUND, req.path(true)); + throw new Err(Status.NOT_FOUND, req.path()); } }); }
jooby/src/test/java/org/jooby/DefaultErrHandlerTest.java+34 −0 modified@@ -1,6 +1,8 @@ package org.jooby; import com.google.common.collect.ImmutableList; +import com.google.common.escape.Escapers; +import com.google.common.html.HtmlEscapers; import com.typesafe.config.Config; import static org.easymock.EasyMock.expect; import org.jooby.test.MockUnit; @@ -66,6 +68,7 @@ private MockUnit.Block handleErr(Throwable ex, boolean stacktrace) { expect(conf.getBoolean("err.stacktrace")).andReturn(stacktrace); Env env = unit.get(Env.class); expect(env.name()).andReturn("dev"); + expect(env.xss("html")).andReturn(HtmlEscapers.htmlEscaper()::escape); Request req = unit.get(Request.class); @@ -112,6 +115,37 @@ public void handleWithErrMessage() throws Exception { }); } + @SuppressWarnings({"unchecked"}) + @Test + public void handleWithHtmlErrMessage() throws Exception { + Err ex = new Err(500, "Something something <em>dark</em>"); + + StringWriter writer = new StringWriter(); + ex.printStackTrace(new PrintWriter(writer)); + String[] stacktrace = writer.toString().replace("\r", "").split("\\n"); + + new MockUnit(Request.class, Response.class, Route.class, Env.class, Config.class) + .expect(handleErr(ex, true)) + .run(unit -> { + + Request req = unit.get(Request.class); + Response rsp = unit.get(Response.class); + + new Err.DefHandler().handle(req, rsp, ex); + }, + unit -> { + Result result = unit.captured(Result.class).iterator().next(); + View view = (View) result.ifGet(ImmutableList.of(MediaType.html)).get(); + assertEquals("err", view.name()); + checkErr(stacktrace, "Server Error(500): Something something <em>dark</em>", + (Map<String, Object>) view.model() + .get("err")); + + Object hash = result.ifGet(MediaType.ALL).get(); + assertEquals(4, ((Map<String, Object>) hash).size()); + }); + } + private void checkErr(final String[] stacktrace, final String message, final Map<String, Object> err) { assertEquals(message, err.remove("message"));
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-f5f4-m7qp-w6gcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-15477ghsaADVISORY
- github.com/jooby-project/jooby/commit/34856a738829d8fedca4ed27bd6ff413af87186fghsaWEB
- github.com/jooby-project/jooby/pull/1368ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.