Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍
🐍 PyPI
Not in CISA KEV
MEDIUM severity

Prowler: Stored XSS in HTML reports through unescaped cloud resource tagsGHSA-c2jg-2778-ggm4

MEDIUMFix: prowler-cloud/prowler#12221

GHSA-c2jg-2778-ggm4 is a medium-severity (CVSS 5.4) Cross-site Scripting (XSS) vulnerability in prowler. A fix is available for prowler — see the affected versions and patch details below.

Also known asCVE-2026-73262PYSEC-2026-3907PYSEC-2026-3908
Published
Updated
Affected
2 pkgs
Patched
2 / 2
Exploits
None indexed
Exploitation data as of Oct 23, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for GHSA-c2jg-2778-ggm4.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs21th percentile — riskier than 21% of all scored CVEsHighest risk
0.00%0.27%0.53%0.80%0.2%0.3%0.3%Sep 26Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

GHSA-c2jg-2778-ggm4 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 394,803 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

2 pkgs affected
🐍prowler🐍prowler-cloud

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

Prowler's HTML output formatter inserts finding.resource_tags into the generated report without HTML escaping. A cloud principal who can create or edit a resource tag in an account that is later scanned can store HTML or JavaScript in that tag. When another user opens the generated Prowler HTML report, the payload executes in the report page.

Impact

This is stored cross-site scripting in a security report artifact. The attacker needs permission to influence tags on a scanned cloud resource, and the victim must open the generated HTML report. In that context, JavaScript can read and modify the report DOM, alter displayed findings, and interact with any same-origin local or hosted report content available to the browser. This can undermine trust in generated security findings and can expose data contained in the report page.

Gate checks

The repository has 13,875 GitHub stars, which is above the 10,000 star floor. SECURITY.md is present and does not reject GitHub private reporting. The dedicated GitHub private vulnerability reporting endpoint for prowler-cloud/prowler returned true.

Affected versions

The vulnerable sink is present in current master at commit 329dfdf8e6cb8bc0424fb54b6595408e20969782 and in the latest GitHub release tag 5.28.0. The package metadata identifies the Python package as prowler.

Technical details

The HTML formatter builds each finding row with an f-string in prowler/lib/outputs/html/html.py. Neighboring fields are explicitly escaped:

  • resource_uid is escaped at line 85 with .replace("<", "&lt;").replace(">", "&gt;").
  • status_extended is escaped at line 87 with the same pattern.

The tag column does not apply equivalent escaping:

<td>{parse_html_string(unroll_dict(finding.resource_tags))}</td>

unroll_dict concatenates tag keys and values into a string, and parse_html_string only prefixes each item with an HTML bullet. Neither function escapes <, >, quotes, or event-handler attributes before insertion into the table cell. As a result, a tag value such as this remains active markup in the generated report:

<img src=x onerror="window.PROWLER_TAG_XSS=1">

Reproduction

  1. Create or identify a cloud resource that Prowler will include in an HTML report.
  2. Add a tag value containing a harmless proof payload, for example:
<img src=x onerror="window.PROWLER_TAG_XSS=1">
  1. Run Prowler against the account and generate HTML output.
  2. Open the generated HTML report in a browser.
  3. The payload executes from the tags column.

A proof HTML fragment using the same formatter structure set window.PROWLER_TAG_XSS=1 from resource_tags, while equivalent payloads in resource_uid and status_extended stayed text because those fields are escaped.

Local evidence:

  • /home/unkn0wn/security_audit/vulnerability-reports/prowler-html-tag-xss-evidence-2026-05-26.json
  • /home/unkn0wn/security_audit/vulnerability-reports/prowler-html-tag-xss-proof-full.html

PoC

The minimized generated row below mirrors the vulnerable formatter output. The first and third candidate payloads are escaped controls. The middle payload is the unescaped resource_tags value and executes when the report is rendered.

<tr class="table-danger">
  <td>&lt;img src=x onerror="window.PROWLER_UID_XSS=1"&gt;</td>
  <td>
    &#x2022;Owner=<img src=x onerror="window.PROWLER_TAG_XSS=1;document.body.setAttribute('data-prowler-tag-xss','1')">
  </td>
  <td>&lt;img src=x onerror="window.PROWLER_STATUS_XSS=1"&gt;</td>
</tr>

Browser proof result from the local artifact:

{"tag": 1, "uid": 0, "status": 0, "attr": "1"}

Remediation

Escape tag keys and values before inserting them into HTML output. Prefer a single HTML escaping helper for every finding field that can originate from provider data. If bullets or separators are needed, build the list structure after escaping each key and value, rather than escaping the final HTML string after markup has been added.

Affected Packages

2 total 2 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIprowlerall versions5.37.0pip install --upgrade 'prowler==5.37.0'
🐍PyPIprowler-cloudall versions5.37.0pip install --upgrade 'prowler-cloud==5.37.0'

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for prowler, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update prowler to 5.37.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-c2jg-2778-ggm4 is resolved across your whole dependency graph.

  3. Workarounds

    Escape or sanitise the affected output on the server side rather than relying on client-side filtering, and add a Content-Security-Policy that blocks inline script execution so injected markup cannot run even if it reaches the page.

Frequently Asked Questions

## Summary Prowler's HTML output formatter inserts `finding.resource_tags` into the generated report without HTML escaping. A cloud principal who can create or edit a resource tag in an account that is later scanned can store HTML or JavaScript in that tag. When another user opens the generated Prowler HTML report, the payload executes in the report page. ## Impact This is stored cross-site scripting in a security report artifact. The attacker needs permission to influence tags on a scanned cloud resource, and the victim must open the generated HTML report. In that context, JavaScript can r
O3 Security · Impact-Aware SCA

Is GHSA-c2jg-2778-ggm4 in your dependencies?

Find it across PyPI, including transitive dependencies.

Prowler: Stored XSS in HTML reports through unescaped…