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 10:22:41 UTC
(tooling-agents) branch main updated: ASVS L3 audit: 3.4.8 (ch03_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 794788b ASVS L3 audit: 3.4.8 (ch03_general)
794788b is described below
commit 794788babf8b89a54fbda7fc8da86bd482578091
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Wed Apr 22 12:22:36 2026 +0200
ASVS L3 audit: 3.4.8 (ch03_general)
---
.../reports/steve/v3/d0aa7e9/ch03_general/3.4.8.md | 273 +++++++++++++++++++++
1 file changed, 273 insertions(+)
diff --git a/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch03_general/3.4.8.md b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch03_general/3.4.8.md
new file mode 100644
index 0000000..d9727d2
--- /dev/null
+++ b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch03_general/3.4.8.md
@@ -0,0 +1,273 @@
+# ASVS 3.4.8 Security Audit Report
+
+## Executive Summary
+
+- **Repository:** apache/steve
+- **Audit Date:** Apr 22, 2026
+- **Auditor:** Tooling Agents
+- **ASVS Requirement:** 3.4.8 — Verify that all HTTP responses that initiate a document rendering (such as responses with Content-Type text/html), include the Cross‑Origin‑Opener‑Policy header field with the same-origin directive or the same-origin-allow-popups directive as required. This prevents attacks that abuse shared access to Window objects, such as tabnabbing and frame counting.
+ - **Level:** 3
+ - **Section:** Browser Security Mechanism Headers
+ - **Section Description:** This section describes which security headers should be set on HTTP responses to enable browser security features and restrictions when handling responses from the application.
+ - **Chapter:** Web Frontend Security
+ - **Control Objective:** This category focuses on requirements designed to protect against attacks executed via a web frontend. These requirements do not apply to machine-to-machine solutions.
+- **Files analyzed:** 3 relevant out of 49 total, 12 skipped
+
+### Finding Counts
+
+| Severity | Count |
+|----------|-------|
+| Critical | 0 |
+| High | 0 |
+| Medium | 1 |
+| Low | 0 |
+| **Total** | **1** |
+
+### Overview
+
+The application **does not set the `Cross-Origin-Opener-Policy` (COOP) header** on any HTTP response that renders HTML content. A thorough review of all route handlers, middleware configuration, response construction functions, and the application factory reveals no mechanism—either global or per-route—for adding this header. This leaves all document-rendering responses vulnerable to cross-origin window handle attacks such as tabnabbing and frame counting.
+
+---
+
+## Security Controls Inventory
+
+| Control | Defined? | Location | Applied Globally? | Coverage |
+|---------|----------|----------|-------------------|----------|
+| `Cross-Origin-Opener-Policy` header | **NO** | Not found anywhere | N/A | **0% — Complete absence** |
+| `@APP.after_request` hook for security headers | **NO** | Not found in `main.py` or `pages.py` | N/A | 0% |
+| Per-response COOP header in route handlers | **NO** | Not in any handler | N/A | 0% |
+| Framework-level security header middleware | **NOT EVIDENCED** | `asfquart.construct()` in `main.py:42` | Unknown | Unverifiable from provided code |
+
+---
+
+## Critical File Review
+
+### `v3/server/main.py` — Application Factory
+
+| Line(s) | Function | Security Relevance | COOP Header? |
+|---------|----------|--------------------|--------------|
+| 42 | `create_app()` → `asfquart.construct(...)` | App initialization; no `after_request` hooks registered | **NO** |
+| 45-46 | `import pages; import api` | Route registration; no header middleware added after | **NO** |
+| 83 | `app.runx(...)` | Server startup; no header config | **NO** |
+
+### `v3/server/pages.py` — All HTML-Rendering Endpoints
+
+| Line(s) | Route | Method | Renders HTML? | COOP Header? |
+|---------|-------|--------|---------------|--------------|
+| ~125 | `/` | GET | ✅ (template: `home.ezt`) | **NO** |
+| ~133 | `/voter` | GET | ✅ (template: `voter.ezt`) | **NO** |
+| ~222 | `/vote-on/<eid>` | GET | ✅ (template: `vote-on.ezt`) | **NO** |
+| ~280 | `/admin` | GET | ✅ (template: `admin.ezt`) | **NO** |
+| ~320 | `/manage/<eid>` | GET | ✅ (template: `manage.ezt`) | **NO** |
+| ~343 | `/manage-stv/<eid>/<iid>` | GET | ✅ (template: `manage-stv.ezt`) | **NO** |
+| ~551 | `/profile` | GET | ✅ (template: `profile.ezt`) | **NO** |
+| ~559 | `/settings` | GET | ✅ (template: `settings.ezt`) | **NO** |
+| ~567 | `/about` | GET | ✅ (template: `about.ezt`) | **NO** |
+| ~659 | `raise_404()` | N/A | ✅ (`mimetype='text/html'`) | **NO** |
+| ~575 | `/docs/<iid>/<docname>` | GET | Potentially (HTML docs) | **NO** |
+
+---
+
+## Findings
+
+### Medium Severity
+
+#### Finding ASVS-348-MED-001
+
+**Title:** Missing `Cross-Origin-Opener-Policy` header on all HTML responses
+
+**Gap Type:** Type A — Entry points with NO control
+
+**Location:**
+- **Files:** `v3/server/main.py`, `v3/server/pages.py`
+- **Functions:** Application-wide; specifically `create_app()` (missing global middleware) and all 9 template-rendered endpoints + custom 404 handler
+- **Lines:** `main.py:32-47`, `pages.py:659`, `pages.py:~125, ~133, ~222, ~280, ~320, ~343, ~551, ~559, ~567`
+
+**Related Functions Checked:**
+- `create_app()` in `v3/server/main.py:32-47`
+- `raise_404()` in `v3/server/pages.py:659`
+- `home_page()` in `v3/server/pages.py:~125`
+- `voter_page()` in `v3/server/pages.py:~133`
+- `vote_on_page()` in `v3/server/pages.py:~222`
+- `admin_page()` in `v3/server/pages.py:~280`
+- `manage_page()` in `v3/server/pages.py:~320`
+- `manage_stv_page()` in `v3/server/pages.py:~343`
+- `profile_page()` in `v3/server/pages.py:~551`
+- `settings_page()` in `v3/server/pages.py:~559`
+- `about_page()` in `v3/server/pages.py:~567`
+- `serve_doc()` in `v3/server/pages.py:~575`
+
+**Description:**
+
+The application does not set the `Cross-Origin-Opener-Policy` (COOP) header on any HTTP response that renders HTML content. This leaves all document-rendering responses vulnerable to cross-origin window handle attacks such as tabnabbing and frame counting.
+
+**Vulnerable Code:**
+
+In `v3/server/main.py`, lines 32–47 — Application factory registers no security header middleware:
+```python
+def create_app():
+ "Create the asfquart app and its endpoints."
+
+ # ...
+ app = asfquart.construct('steve', app_dir=THIS_DIR, static_folder=None)
+
+ # Now that we have an APP, import modules that will add page
+ # and API endpoints into the APP.
+ import pages # noqa: F401
+ import api # noqa: F401
+
+ return app # also available as asfquart.APP now
+ # ← No @app.after_request for security headers
+```
+
+In `v3/server/pages.py`, line 659 — Manual HTML response with no COOP header:
+```python
+def raise_404(template, data):
+ content = asfquart.utils.render(template, data)
+ quart.abort(quart.Response(content, status=404, mimetype='text/html'))
+ # ← No Cross-Origin-Opener-Policy header set
+```
+
+In `v3/server/pages.py`, all template-decorated endpoints (e.g., line ~125):
+```python
+@APP.get('/')
+@APP.use_template(TEMPLATES / 'home.ezt')
+async def home_page():
+ result = await basic_info()
+ result.title = 'Home'
+ return result
+ # ← Template rendering returns HTML with no COOP header
+```
+
+**Data Flow:**
+```
+Client Request → Quart Router → Handler Function → @APP.use_template decorator
+ → EZT template rendering → HTTP Response (Content-Type: text/html)
+ → Missing: Cross-Origin-Opener-Policy header
+ → Browser allows cross-origin window handle access
+```
+
+**Proof of Concept:**
+
+An attacker page opened via a link from the voting application (e.g., in an election description) can retain a reference to the opener window:
+
+```html
+<!-- Attacker's page, linked from an election issue description -->
+<script>
+ // Without COOP: same-origin, the opener reference is accessible
+ // from cross-origin pages opened via window.open or target="_blank"
+
+ // Tabnabbing: redirect the voting page to a phishing page
+ if (window.opener) {
+ window.opener.location = 'https://evil.example.com/fake-vote-page';
+ }
+
+ // Frame counting: enumerate open windows/tabs
+ // to infer voting activity patterns
+</script>
+```
+
+A malicious link in an election's issue description (rendered via `rewrite_description()` at line ~52 which converts `doc:filename` patterns to `<a>` links) or in any user-controlled content could enable this attack.
+
+**Impact:**
+- **Tabnabbing:** An attacker-controlled page opened from the voting application can navigate the original tab to a phishing page mimicking the voting UI, potentially capturing credentials or manipulating vote submissions.
+- **Frame Counting:** Cross-origin pages can enumerate browsing contexts to infer voting behavior, undermining the system's anonymity goals.
+- **Window Reference Leakage:** Without COOP, the `window.opener` property leaks a reference across origins, enabling cross-origin state inspection.
+
+**Remediation:**
+
+Add a global `after_request` hook in the application factory to set the `Cross-Origin-Opener-Policy` header on all HTML responses:
+
+```python
+# In v3/server/main.py, inside create_app():
+def create_app():
+ app = asfquart.construct('steve', app_dir=THIS_DIR, static_folder=None)
+
+ @app.after_request
+ async def set_security_headers(response):
+ # Set COOP on all document-rendering responses
+ content_type = response.content_type or ''
+ if 'text/html' in content_type:
+ response.headers['Cross-Origin-Opener-Policy'] = 'same-origin'
+ return response
+
+ import pages
+ import api
+
+ return app
+```
+
+Also update the `raise_404` function in `v3/server/pages.py` to include the header on manual responses:
+
+```python
+def raise_404(template, data):
+ content = asfquart.utils.render(template, data)
+ response = quart.Response(content, status=404, mimetype='text/html')
+ response.headers['Cross-Origin-Opener-Policy'] = 'same-origin'
+ quart.abort(response)
+```
+
+> **Note:** Use `same-origin` as the default directive. If the application requires popup interactions (e.g., OAuth flows using popups), use `same-origin-allow-popups` instead. Given the ASF OAuth flow appears to use redirects rather than popups (see `main.py` lines 37-40), `same-origin` is the appropriate choice.
+
+---
+
+## Positive Security Patterns
+
+| Pattern | Location | Assessment |
+|---------|----------|------------|
+| Authentication enforcement on sensitive routes | `pages.py` — `@asfquart.auth.require({R.committer})` on all voting/admin endpoints | ✅ Consistently applied |
+| Template-based rendering (EZT) | `pages.py` — `@APP.use_template(...)` decorator | ✅ Centralized rendering provides a single point for header injection |
+| TLS support | `main.py` lines 78-80 | ✅ Certificate-based HTTPS available |
+| Static file serving via framework | `pages.py` — `quart.send_from_directory()` | ✅ Framework-level path traversal protection |
+
+---
+
+## Architecture Observations
+
+1. **Centralized template rendering is an advantage:** The `@APP.use_template` decorator pattern means a single `after_request` hook can address all 9 HTML-rendering endpoints simultaneously, making remediation straightforward.
+
+2. **Missing security header strategy entirely:** The absence of COOP is likely symptomatic of a broader gap — no security header middleware exists for any browser security headers (e.g., `X-Content-Type-Options`, `X-Frame-Options`, `Content-Security-Policy`, `Cross-Origin-Embedder-Policy`). A comprehensive `after_request` hook should address all of these together.
+
+3. **The `raise_404` function bypasses any future middleware:** Since it constructs a `quart.Response` directly and passes it to `quart.abort()`, it may bypass `after_request` hooks depending on Quart's abort handling. This needs explicit verification after adding the global hook.
+
+4. **Document serving (`/docs/<iid>/<docname>`) could serve HTML:** The `serve_doc` endpoint at line 575 serves arbitrary files from a directory. If any served document is HTML, it would also need the COOP header. `send_from_directory` responses should be covered by the `after_request` hook, but only if their `Content-Type` is correctly detected.
+
+---
+
+## Recommendations Summary
+
+### Immediate Actions (Priority 1)
+
+| Action | Effort | Impact | Timeline |
+|--------|--------|--------|----------|
+| Add global `after_request` hook setting `Cross-Origin-Opener-Policy: same-origin` on all `text/html` responses | Low | High — Addresses all 9+ HTML endpoints in one change | 1-2 days |
+
+### Short-term Actions (Priority 2)
+
+| Action | Effort | Impact | Timeline |
+|--------|--------|--------|----------|
+| Verify `raise_404` responses pass through the `after_request` hook; add explicit header if not | Low | Medium — Prevents bypass of the global control | 1 day |
+| Add integration tests validating presence of COOP header on all HTML responses | Low | Medium — Prevents regression | 2-3 days |
+
+### Long-term Actions (Priority 3)
+
+| Action | Effort | Impact | Timeline |
+|--------|--------|--------|----------|
+| Expand the `after_request` hook to set other missing browser security headers (`X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Content-Security-Policy`) | Medium | High — Comprehensive browser security posture | 1-2 weeks |
+
+---
+
+## Appendix: Files Analyzed
+
+<details>
+<summary>Click to expand file list (3 files)</summary>
+
+1. `v3/server/pages.py` (relevance: 10)
+2. `v3/server/main.py` (relevance: 7)
+3. `v3/ARCHITECTURE.md` (relevance: 2)
+
+</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