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/04/22 12:33:02 UTC

(tooling-agents) branch main updated: ASVS L3 audit: 5.2.2 (ch05_general)

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 375e0d0  ASVS L3 audit: 5.2.2 (ch05_general)
375e0d0 is described below

commit 375e0d0cb82519ff1b7a5e2db8cf95bad22ded4a
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Wed Apr 22 14:32:57 2026 +0200

    ASVS L3 audit: 5.2.2 (ch05_general)
---
 .../reports/steve/v3/d0aa7e9/ch05_general/5.2.2.md | 380 +++++++++++++++++++++
 1 file changed, 380 insertions(+)

diff --git a/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch05_general/5.2.2.md b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch05_general/5.2.2.md
new file mode 100644
index 0000000..b590897
--- /dev/null
+++ b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch05_general/5.2.2.md
@@ -0,0 +1,380 @@
+# ASVS 5.2.2 Security Audit Report
+## File Upload and Content Validation
+
+---
+
+## Executive Summary
+
+**Audit Metadata:**
+- **Repository:** apache/steve
+- **Audit Date:** Apr 22, 2026
+- **Auditor:** Tooling Agents
+- **ASVS Requirement:** 5.2.2 — Verify that when the application accepts a file, either on its own or within an archive such as a zip file, it checks if the file extension matches an expected file extension and validates that the contents correspond to the type represented by the extension. This includes, but is not limited to, checking the initial 'magic bytes', performing image re-writing, and using specialized libraries for file content validation. For L1, this can focus just on files [...]
+- **Files Analyzed:** 2 relevant out of 49 total, 12 skipped
+
+**Finding Summary:**
+
+| Severity | Count | Status |
+|----------|-------|--------|
+| Critical | 0 | ✓ |
+| High | 0 | ✓ |
+| Medium | 1 | ⚠ |
+| Low | 0 | ✓ |
+| **Total** | **1** | |
+
+**Key Findings:**
+After thorough analysis of the provided source code, **no file upload endpoints exist** in the web application layer. The application accepts only text-based form data and JSON payloads. However, a file-serving endpoint exists with an explicitly acknowledged gap in filename validation, which creates a **related** finding concerning safe file handling. One Medium severity finding was identified related to document serving without proper filename sanitization and content-type enforcement.
+
+---
+
+## Security Controls Inventory
+
+### File Acceptance Controls
+
+| Control | Defined? | Location | Applied? | Coverage |
+|---------|----------|----------|----------|----------|
+| File upload endpoint(s) | **No** | N/A | N/A | No file uploads exist in web layer |
+| File extension allowlist | **No** | N/A | N/A | Not needed (no uploads) |
+| Magic byte / content validation | **No** | N/A | N/A | Not needed (no uploads) |
+| Multipart form handling (`request.files`) | **No** | N/A | N/A | Not used anywhere |
+| Archive extraction (zip/tar) | **No** | N/A | N/A | Not present |
+
+### File Serving Controls
+
+| Control | Defined? | Location | Applied? | Coverage |
+|---------|----------|----------|----------|----------|
+| `send_from_directory` for static | Yes | `pages.py:613-619` | Yes | `/static/`, `/favicon.ico` |
+| `send_from_directory` for docs | Yes | `pages.py:609` | Yes | `/docs/<iid>/<docname>` |
+| Authorization for doc access | Yes | `pages.py:600-604` | Yes | Checks `mayvote` table |
+| Filename/path validation for docs | **No** | `pages.py:607` | **No** | **Acknowledged TODO** |
+
+---
+
+## Critical File Review
+
+### File: `v3/server/pages.py`
+
+| Line(s) | Function | Input Type | File Handling? | Finding? |
+|---------|----------|------------|----------------|----------|
+| 386-430 | `do_vote_endpoint` | `request.form` (text) | No | No |
+| 434-455 | `do_create_endpoint` | `request.form` (text) | No | No |
+| 496-520 | `do_add_issue_endpoint` | `request.form` (text) | No | No |
+| 522-545 | `do_edit_issue_endpoint` | `request.form` (text) | No | No |
+| 85-107 | `_set_election_date` | `request.get_json()` (JSON) | No | No |
+| 594-609 | `serve_doc` | URL params (`iid`, `docname`) | **Yes (serving)** | **Yes** |
+| 613-614 | `serve_static` | URL param (`filename`) | Yes (serving) | No — standard pattern |
+
+### File: `v3/steve/election.py`
+
+| Line(s) | Function | Input Type | File Handling? | Finding? |
+|---------|----------|------------|----------------|----------|
+| 190-205 | `add_issue` | String params | No | No |
+| 208-219 | `edit_issue` | String params | No | No |
+| 261-274 | `add_vote` | String params | No | No |
+
+---
+
+## Findings
+
+### Medium Severity
+
+#### ASVS-522-MED-001: Document Serving Lacks Filename Sanitization and Content-Type Enforcement
+
+**Severity:** MEDIUM
+
+**Location:** `v3/server/pages.py`, function `serve_doc`, lines 594–609
+
+**Related Functions Checked:**
+- `rewrite_description` (line 47) — generates links to this endpoint
+- `send_from_directory` (line 609) — serves files without content validation
+- Database authorization check (lines 600-604) — verifies voter eligibility
+
+**Description:**
+
+The `serve_doc` endpoint serves per-issue documents to authorized voters without validating filename extensions or enforcing safe content types. The code contains an explicit TODO comment acknowledging this gap (`### verify the propriety of DOCNAME.`), but the validation was never implemented. This creates a control gap where documents used in business decision workflows (voters reading documents before voting) are served without proper content validation.
+
+While this endpoint *serves* files rather than *accepting uploads*, ASVS 5.2.2 requires applications to validate that "contents correspond to the type represented by the extension." At Level 1, files used for business decisions are in scope, and these documents directly influence voting decisions.
+
+**Vulnerable Code:**
+
+```python
+@APP.get('/docs/<iid>/<docname>')
+@asfquart.auth.require  # fine-grained per-doc authz within the handler
+async def serve_doc(iid, docname):
+    result = await basic_info()  # get PID  ### grr: called uid
+
+    db = steve.election.Election.open_database(DB_FNAME)
+    row = db.q_get_mayvote.first_row(result.uid, iid)
+    if not row:
+        # Nothing fancy. Just pretend the file does not exist
+        quart.abort(404)
+        # NOTREACHED
+
+    ### verify the propriety of DOCNAME.    # <-- Acknowledged TODO, never implemented
+
+    # Return per-issue document.
+    return await quart.send_from_directory(DOCSDIR / iid, docname)
+```
+
+**Data Flow:**
+
+```
+URL parameter `docname` (user-controlled)
+  → No extension allowlist check
+  → No content-type enforcement
+  → quart.send_from_directory() serves file with MIME type guessed from extension
+  → Browser renders content inline with application's origin
+```
+
+**Proof of Concept:**
+
+If an administrator (or compromised CLI process) places a file `malicious.html` in `DOCSDIR/<valid_iid>/`, and an issue description references `doc:malicious.html`:
+
+1. An authorized voter views the issue
+2. `rewrite_description` generates a link: `<a href="/docs/<iid>/malicious.html">malicious.html</a>`
+3. Clicking the link serves `malicious.html` as `text/html` from the application's origin
+4. JavaScript in that file executes in the application's security context (same-origin XSS)
+
+**Attack Request:**
+```http
+GET /docs/<valid_iid>/malicious.html HTTP/1.1
+Host: voting.example.org
+Cookie: session=<valid_session>
+```
+
+**Impact:**
+
+- **Stored XSS Risk:** Files served with HTML/SVG content types could execute JavaScript in the application's security context
+- **No Extension Validation:** Any file type can be served without restrictions
+- **Content-Type Sniffing:** Browsers may interpret files differently than intended
+- **Business Logic Bypass:** Malicious documents could mislead voters in the decision-making process
+- **Known Gap:** The acknowledged-but-unimplemented validation represents technical debt that could be exploited if document management is compromised
+
+**Remediation:**
+
+Implement comprehensive filename validation and content-type enforcement:
+
+```python
+import mimetypes
+import pathlib
+
+# Allowlist of safe file extensions for election documents
+ALLOWED_DOC_EXTENSIONS = {'.pdf', '.txt', '.md', '.png', '.jpg', '.jpeg', '.gif'}
+
+# Safe MIME types (force download for anything else)
+SAFE_MIME_TYPES = {
+    '.pdf': 'application/pdf',
+    '.txt': 'text/plain',
+    '.md': 'text/plain',
+    '.png': 'image/png',
+    '.jpg': 'image/jpeg',
+    '.jpeg': 'image/jpeg',
+    '.gif': 'image/gif',
+}
+
+@APP.get('/docs/<iid>/<docname>')
+@asfquart.auth.require
+async def serve_doc(iid, docname):
+    result = await basic_info()
+
+    db = steve.election.Election.open_database(DB_FNAME)
+    row = db.q_get_mayvote.first_row(result.uid, iid)
+    if not row:
+        quart.abort(404)
+
+    # Validate filename: no path separators, no special components
+    if '..' in docname or '/' in docname or '\\' in docname:
+        quart.abort(400, 'Invalid document name')
+
+    # Check file extension against allowlist
+    ext = pathlib.Path(docname).suffix.lower()
+    if ext not in ALLOWED_DOC_EXTENSIONS:
+        quart.abort(403, 'File type not allowed')
+
+    # Validate IID format (should be a hex ID, no path components)
+    if '..' in iid or '/' in iid or '\\' in iid:
+        quart.abort(400, 'Invalid issue ID')
+
+    # Serve with explicit safe content type and security headers
+    response = await quart.send_from_directory(DOCSDIR / iid, docname)
+    response.headers['Content-Type'] = SAFE_MIME_TYPES.get(ext, 'application/octet-stream')
+    response.headers['Content-Disposition'] = f'inline; filename="{docname}"'
+    response.headers['X-Content-Type-Options'] = 'nosniff'
+    return response
+```
+
+For full ASVS 5.2.2 compliance, add content validation where files are *accepted* into the docs directory (in the CLI/admin tooling):
+
+```python
+import magic  # python-magic library
+import pathlib
+
+def validate_document(filepath):
+    """Validate file content matches its extension before accepting."""
+    ext = pathlib.Path(filepath).suffix.lower()
+    if ext not in ALLOWED_DOC_EXTENSIONS:
+        raise ValueError(f'Extension {ext} not in allowlist')
+    
+    # Verify magic bytes match claimed type
+    detected = magic.from_file(str(filepath), mime=True)
+    expected_mimes = {
+        '.pdf': 'application/pdf',
+        '.png': 'image/png',
+        '.jpg': 'image/jpeg',
+        '.jpeg': 'image/jpeg',
+        '.gif': 'image/gif',
+        '.txt': 'text/plain',
+        '.md': 'text/plain',
+    }
+    if detected != expected_mimes.get(ext, 'application/octet-stream'):
+        raise ValueError(f'Content mismatch: extension={ext}, detected={detected}')
+```
+
+---
+
+## Positive Security Patterns
+
+### 1. Standard Framework File Serving Functions
+
+**Location:** `v3/server/pages.py:609, 614, 619`
+
+The application consistently uses Quart's `send_from_directory()` function for serving files, which provides built-in path traversal protection for the `docname` parameter within the specified directory. This is a secure baseline that prevents directory traversal attacks like `../../etc/passwd`.
+
+### 2. Authorization Before Document Access
+
+**Location:** `v3/server/pages.py:600-604`
+
+The `serve_doc` endpoint implements fine-grained authorization by checking the `mayvote` table to verify that the requesting voter is eligible for the specific issue before serving documents. This prevents unauthorized access to election materials.
+
+```python
+row = db.q_get_mayvote.first_row(result.uid, iid)
+if not row:
+    quart.abort(404)
+```
+
+### 3. No File Upload Attack Surface
+
+**Location:** All POST routes in `v3/server/pages.py`
+
+All POST handlers use `request.form` (text) or `request.get_json()` for data input. No endpoints use `request.files`, which eliminates the primary attack surface for malicious file uploads. This significantly reduces the application's exposure to file-based attacks.
+
+### 4. Safe Path Construction
+
+**Location:** `v3/server/pages.py:37-41`
+
+The application uses `pathlib.Path` for path construction rather than string concatenation, which provides safer path joining and manipulation:
+
+```python
+DOCSDIR = pathlib.Path('/path/to/docs')
+# Later: DOCSDIR / iid / docname
+```
+
+### 5. Authentication on Sensitive Routes
+
+**Location:** All file-serving routes with `@asfquart.auth.require` decorator
+
+File-related endpoints require authentication, ensuring that only authenticated users can access documents. This is combined with the authorization checks for defense-in-depth.
+
+---
+
+## Architecture Observations
+
+### 1. Document Management is External to Web Layer
+
+The `serve_doc` endpoint and `rewrite_description` function reference documents in `DOCSDIR/<iid>/`, but no web endpoint uploads files to this location. Documents are likely managed through CLI tools or administrative interfaces outside the web application scope.
+
+**Implication:** ASVS 5.2.2 compliance must be verified in the CLI/admin tooling where files are actually accepted into the system. The web layer only serves pre-existing documents, so content validation should occur at the point of document ingestion.
+
+### 2. No Content-Security-Policy for Document Responses
+
+The `serve_doc` endpoint doesn't set `Content-Security-Policy` headers on responses. If documents are served inline (as HTML or SVG), scripts could execute in the application's origin context.
+
+**Recommendation:** Implement CSP headers like `Content-Security-Policy: sandbox` to prevent script execution even if malicious content is served.
+
+### 3. IID Used in Path Construction Without Format Validation
+
+The `iid` URL parameter is used as a directory component (`DOCSDIR / iid`). While the `mayvote` database check provides indirect validation (the IID must exist in the database), explicit format validation of `iid` would be a stronger defense-in-depth measure.
+
+**Note:** The Quart default string converter prevents `/` in `iid`, but `..` is still possible as a value, though the database check would likely fail for invalid IIDs.
+
+### 4. Acknowledged Technical Debt
+
+The explicit TODO comment (`### verify the propriety of DOCNAME.`) indicates that filename validation was planned but never implemented. This represents known technical debt that should be prioritized for remediation.
+
+---
+
+## Recommendations Summary
+
+### Immediate Actions (Priority 1)
+
+| # | Recommendation | Effort | Impact | Timeline |
+|---|----------------|--------|--------|----------|
+| 1 | Implement the acknowledged `docname` validation (ASVS-522-MED-001) with extension allowlist and security headers | Low | High | 1-2 days |
+| 2 | Add explicit `iid` format validation in `serve_doc` (e.g., regex match for hex ID pattern) | Low | Medium | 1 day |
+
+**Immediate Action Details:**
+
+1. **Implement Filename Validation:** Add extension allowlist checking and set `Content-Type`, `X-Content-Type-Options: nosniff`, and `Content-Disposition` headers on document responses to prevent content-type confusion and XSS attacks.
+
+2. **Validate IID Format:** Add explicit format validation for the `iid` parameter to ensure it matches expected patterns (e.g., hexadecimal issue ID) and doesn't contain path traversal sequences.
+
+### Short-Term Actions (Priority 2)
+
+| # | Recommendation | Effort | Impact | Timeline |
+|---|----------------|--------|--------|----------|
+| 3 | Implement file content validation (magic bytes) in CLI/admin tooling where documents are accepted into `DOCSDIR` | Medium | High | 1 week |
+| 4 | Set `Content-Security-Policy: sandbox` on document responses to prevent script execution | Low | Medium | 2 days |
+
+**Short-Term Action Details:**
+
+3. **Content Validation at Ingestion:** Implement magic byte validation using libraries like `python-magic` in the CLI/admin tools that accept documents into the system. This provides full ASVS 5.2.2 compliance at the point of file acceptance.
+
+4. **CSP Headers:** Add Content-Security-Policy headers to document responses to prevent inline script execution even if HTML/SVG content is served.
+
+### Long-Term Actions (Priority 3)
+
+| # | Recommendation | Effort | Impact | Timeline |
+|---|----------------|--------|--------|----------|
+| 5 | Document the file acceptance workflow in CLI/admin tooling and ensure ASVS 5.2.2 compliance is verified there | Low | Medium | Ongoing |
+| 6 | Consider implementing a document management API with proper file upload validation if web-based document management is needed in the future | High | High | Future enhancement |
+
+**Long-Term Action Details:**
+
+5. **Documentation:** Create comprehensive documentation of how documents are accepted into the system, including validation requirements and security controls in the CLI/admin layer.
+
+6. **Future-Proofing:** If web-based document uploads are planned for future versions, design the API with full ASVS 5.2.2 compliance from the start, including extension allowlists, magic byte validation, and content sanitization.
+
+---
+
+## Appendix: Files Analyzed
+
+<details>
+<summary><strong>Relevant Files (2)</strong></summary>
+
+### High Relevance (Score: 7)
+- **`v3/server/pages.py`** — Main web application routes including document serving endpoint (`serve_doc`), static file serving, and all form-based endpoints. Contains the primary finding related to document serving without filename validation.
+
+### Medium Relevance (Score: 4)
+- **`v3/steve/election.py`** — Election management logic including issue creation and editing. Handles text-based data only; no file operations. Reviewed to confirm no file acceptance occurs in business logic layer.
+
+</details>
+
+<details>
+<summary><strong>Files Skipped (12)</strong></summary>
+
+Files skipped during analysis due to low relevance to ASVS 5.2.2 (file upload and content validation):
+- Configuration files
+- Database schema definitions
+- Authentication/authorization modules (unless file-related)
+- Utility functions not related to file handling
+- Test files
+- Documentation files
+- Static assets
+- Third-party dependencies
+- Build/deployment scripts
+
+</details>
+
+---
+
+**End of Report**
\ 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