T09 · Insecure Skill Coding Practices
- Location
src/code_quality_guardian/reports/html_reporter.py:274- Finding
Stored HTML Injection in Generated Reports
- Content
View full analysis
Vulnerability Details
File Location:
src/code_quality_guardian/reports/html_reporter.py:274-283
Vulnerability Type: Stored HTML injection
Risk Level: MediumVulnerable Code
python for issue in sorted_issues: sev_class = severity_class.get(issue.severity, "info") rows.append(f""" <tr> <td class="{sev_class}">{issue.severity.name}</td> <td><code>{issue.code}</code></td> <td>{issue.file}</td> <td>{issue.line}</td> <td>{issue.message}</td> </tr> """)Technical Analysis
The HTML reporter interpolates
issue.code,issue.file, andissue.messagedirectly into an HTML document without context-appropriate escaping. These values originate from external analyzer output and can be influenced by the contents and filenames of the repository being scanned.Python permits filenames containing HTML metacharacters on common operating systems, and analyzer diagnostics may incorporate attacker-controlled identifiers, strings, or source fragments. When such values are inserted into the report, a browser interprets them as markup rather than plain text.
For example, a malicious filename or diagnostic containing an element with an event handler can introduce active content into the generated report. The payload is stored in the report file and activates when a user opens that file in a browser.
Attack Path
- An attacker prepares a repository containing a Python file whose filename or analyzer-triggered diagnostic contains malicious HTML.
- A victim or CI workflow scans the untrusted repository using the HTML format:
bash quality-guardian analyze --path ./untrusted-project --format html --output report.html - Flake8, Pylint, or Bandit returns the attacker-influenced filename or message as part of ...[truncated 824 chars]
- Remediation
View remediation
Remediation Suggestions
Escape every untrusted value before inserting it into HTML. For example:
python from html import escape code = escape(str(issue.code), quote=True) file_name = escape(str(issue.file), quote=True) message = escape(str(issue.message), quote=True) severity_name = escape(str(issue.severity.name), quote=True)Insert only these escaped values into the template. Preferably, replace manual string interpolation with Jinja2 configured using an HTML-aware environment and automatic escaping:
python from jinja2 import Environment, select_autoescape env = Environment(autoescape=select_autoescape(["html", "xml"]))Additional hardening should include:
- Validate
sev_classagainst a fixed allowlist before using it in an attribute. - Add a restrictive Content Security Policy to generated reports, such as disabling scripts entirely.
- Add regression tests using malicious filenames and messages such as an image element with an
onerrorhandler. - Verify that the generated document contains escaped entities and does not create executable DOM elements.
- Treat all output from external analysis tools as untrusted, even when the tool itself is trusted.
- Validate
