GHSA-GX93-M64W-5M6H

Vulnerability from github – Published: 2026-06-19 21:15 – Updated: 2026-06-19 21:15
VLAI
Summary
Allure Report: Stored XSS via unescaped ANSI helper in status message/trace rendering
Details

Summary

The ansi.js Handlebars helper in allure-generator passes user-controlled statusMessage and statusTrace values from test result files through the ansi-to-html library and wraps the output in Handlebars SafeString without HTML escaping. Since ansi-to-html does not escape HTML entities by default, an attacker who can influence test result content (e.g., via crafted JUnit XML failure messages) can inject arbitrary JavaScript that executes when anyone views the generated Allure report.

Details

The vulnerability is an incomplete fix — commit 4c64b19 (PR #3271) fixed XSS in linky.js and text-with-links.js by adding escapeExpression(), but the same pattern in ansi.js was not addressed.

Vulnerable sink — allure-generator/src/main/javascript/helpers/ansi.js:10-11:

export default function (input) {
    return new SafeString(ansiConverter.toHtml(input));
};

The AnsiToHtml constructor at line 4 does not set escapeForHtml: true:

const ansiConverter = new AnsiToHtml({
    fg: "black",
    bg: "black",
    newline: true,
});

The ansi-to-html library (v0.7.2) defaults escapeForHtml to false, meaning HTML entities in the input pass through unchanged. Wrapping the result in SafeString tells Handlebars to skip its auto-escaping, so the raw HTML reaches the browser.

Template usage — allure-generator/src/main/javascript/blocks/status-details/status-details.hbs:7,10:

<pre class="status-details__message"><code>{{ansi statusMessage}}</code></pre>
...
<pre class="{{b 'status-details' 'trace'}}"><code>{{ansi statusTrace}}</code></pre>

Source — plugins/junit-xml-plugin/src/main/java/io/qameta/allure/junitxml/JunitXmlPlugin.java:307-308:

result.setStatusMessage(element.getAttribute(MESSAGE_ATTRIBUTE_NAME));
result.setStatusTrace(element.getValue());

These values are read directly from XML attributes with no sanitization. The same pattern exists in TRX, xUnit XML, xctest, and Allure1/2 plugins.

Contrast with the fixed helper — linky.js (post-fix) correctly escapes before wrapping in SafeString:

const safeText = escapeExpression(text);
return new SafeString(`<a href="${safeText}" ...>${safeText}</a>`);

PoC

  1. Create a malicious JUnit XML test result file:
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="XSSTest" tests="1" failures="1">
  <testcase name="xssPayload" classname="com.example.Test">
    <failure message="&lt;img src=x onerror=alert(document.cookie)&gt;">
      Stack trace: &lt;img src=x onerror=alert('statusTrace_XSS')&gt;
    </failure>
  </testcase>
</testsuite>
  1. Generate an Allure report:
allure generate /path/to/results-with-malicious-xml -o /tmp/allure-report
  1. Open the report and navigate to the failed test case:
allure open /tmp/allure-report
  1. When viewing the test's status details, the <img onerror> payloads execute JavaScript in the viewer's browser.

Impact

  • Arbitrary JavaScript execution in the browser of anyone viewing the generated Allure report
  • Cookie theft, session hijacking if the report is served from a domain with active sessions (e.g., CI dashboards)
  • Data exfiltration — the injected script can read the full report content and send it to an attacker-controlled server
  • Attack vectors: A malicious dependency that throws crafted exception messages, a CI pipeline processing test results from untrusted pull requests, or a contributor submitting test files containing XSS payloads
  • Allure reports are commonly hosted on CI/CD platforms (Jenkins, GitLab, GitHub Actions artifacts) where session cookies may be present

Recommended Fix

Configure AnsiToHtml with escapeForHtml: true to escape HTML entities while preserving ANSI-to-HTML conversion:

import AnsiToHtml from "ansi-to-html";
import {SafeString} from "handlebars/runtime";

const ansiConverter = new AnsiToHtml({
    fg: "black",
    bg: "black",
    newline: true,
    escapeForHtml: true,  // Escape HTML entities in non-ANSI input
});

export default function (input) {
    return new SafeString(ansiConverter.toHtml(input));
};

This is the correct approach because it preserves the ANSI escape sequence → HTML conversion (colored output) while ensuring that any non-ANSI HTML in the input is safely escaped. The alternative of using escapeExpression() on the input would destroy ANSI sequences before they could be converted.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.38.1"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "io.qameta.allure:allure-generator"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.39.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-55847"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-79"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-19T21:15:53Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nThe `ansi.js` Handlebars helper in allure-generator passes user-controlled `statusMessage` and `statusTrace` values from test result files through the `ansi-to-html` library and wraps the output in Handlebars `SafeString` without HTML escaping. Since `ansi-to-html` does not escape HTML entities by default, an attacker who can influence test result content (e.g., via crafted JUnit XML failure messages) can inject arbitrary JavaScript that executes when anyone views the generated Allure report.\n\n## Details\n\nThe vulnerability is an incomplete fix \u2014 commit `4c64b19` (PR #3271) fixed XSS in `linky.js` and `text-with-links.js` by adding `escapeExpression()`, but the same pattern in `ansi.js` was not addressed.\n\n**Vulnerable sink** \u2014 `allure-generator/src/main/javascript/helpers/ansi.js:10-11`:\n```javascript\nexport default function (input) {\n    return new SafeString(ansiConverter.toHtml(input));\n};\n```\n\nThe `AnsiToHtml` constructor at line 4 does not set `escapeForHtml: true`:\n```javascript\nconst ansiConverter = new AnsiToHtml({\n    fg: \"black\",\n    bg: \"black\",\n    newline: true,\n});\n```\n\nThe `ansi-to-html` library (v0.7.2) defaults `escapeForHtml` to `false`, meaning HTML entities in the input pass through unchanged. Wrapping the result in `SafeString` tells Handlebars to skip its auto-escaping, so the raw HTML reaches the browser.\n\n**Template usage** \u2014 `allure-generator/src/main/javascript/blocks/status-details/status-details.hbs:7,10`:\n```handlebars\n\u003cpre class=\"status-details__message\"\u003e\u003ccode\u003e{{ansi statusMessage}}\u003c/code\u003e\u003c/pre\u003e\n...\n\u003cpre class=\"{{b \u0027status-details\u0027 \u0027trace\u0027}}\"\u003e\u003ccode\u003e{{ansi statusTrace}}\u003c/code\u003e\u003c/pre\u003e\n```\n\n**Source** \u2014 `plugins/junit-xml-plugin/src/main/java/io/qameta/allure/junitxml/JunitXmlPlugin.java:307-308`:\n```java\nresult.setStatusMessage(element.getAttribute(MESSAGE_ATTRIBUTE_NAME));\nresult.setStatusTrace(element.getValue());\n```\n\nThese values are read directly from XML attributes with no sanitization. The same pattern exists in TRX, xUnit XML, xctest, and Allure1/2 plugins.\n\n**Contrast with the fixed helper** \u2014 `linky.js` (post-fix) correctly escapes before wrapping in `SafeString`:\n```javascript\nconst safeText = escapeExpression(text);\nreturn new SafeString(`\u003ca href=\"${safeText}\" ...\u003e${safeText}\u003c/a\u003e`);\n```\n\n## PoC\n\n1. Create a malicious JUnit XML test result file:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003ctestsuite name=\"XSSTest\" tests=\"1\" failures=\"1\"\u003e\n  \u003ctestcase name=\"xssPayload\" classname=\"com.example.Test\"\u003e\n    \u003cfailure message=\"\u0026lt;img src=x onerror=alert(document.cookie)\u0026gt;\"\u003e\n      Stack trace: \u0026lt;img src=x onerror=alert(\u0027statusTrace_XSS\u0027)\u0026gt;\n    \u003c/failure\u003e\n  \u003c/testcase\u003e\n\u003c/testsuite\u003e\n```\n\n2. Generate an Allure report:\n\n```bash\nallure generate /path/to/results-with-malicious-xml -o /tmp/allure-report\n```\n\n3. Open the report and navigate to the failed test case:\n\n```bash\nallure open /tmp/allure-report\n```\n\n4. When viewing the test\u0027s status details, the `\u003cimg onerror\u003e` payloads execute JavaScript in the viewer\u0027s browser.\n\n## Impact\n\n- **Arbitrary JavaScript execution** in the browser of anyone viewing the generated Allure report\n- **Cookie theft, session hijacking** if the report is served from a domain with active sessions (e.g., CI dashboards)\n- **Data exfiltration** \u2014 the injected script can read the full report content and send it to an attacker-controlled server\n- **Attack vectors**: A malicious dependency that throws crafted exception messages, a CI pipeline processing test results from untrusted pull requests, or a contributor submitting test files containing XSS payloads\n- Allure reports are commonly hosted on CI/CD platforms (Jenkins, GitLab, GitHub Actions artifacts) where session cookies may be present\n\n## Recommended Fix\n\nConfigure `AnsiToHtml` with `escapeForHtml: true` to escape HTML entities while preserving ANSI-to-HTML conversion:\n\n```javascript\nimport AnsiToHtml from \"ansi-to-html\";\nimport {SafeString} from \"handlebars/runtime\";\n\nconst ansiConverter = new AnsiToHtml({\n    fg: \"black\",\n    bg: \"black\",\n    newline: true,\n    escapeForHtml: true,  // Escape HTML entities in non-ANSI input\n});\n\nexport default function (input) {\n    return new SafeString(ansiConverter.toHtml(input));\n};\n```\n\nThis is the correct approach because it preserves the ANSI escape sequence \u2192 HTML conversion (colored output) while ensuring that any non-ANSI HTML in the input is safely escaped. The alternative of using `escapeExpression()` on the input would destroy ANSI sequences before they could be converted.",
  "id": "GHSA-gx93-m64w-5m6h",
  "modified": "2026-06-19T21:15:53Z",
  "published": "2026-06-19T21:15:53Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/allure-framework/allure2/security/advisories/GHSA-gx93-m64w-5m6h"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/allure-framework/allure2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Allure Report: Stored XSS via unescaped ANSI helper in status message/trace rendering"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…