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 11:00:31 UTC
(tooling-agents) branch main updated: ASVS L3 audit: 3.5.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 175cec9 ASVS L3 audit: 3.5.8 (ch03_general)
175cec9 is described below
commit 175cec9d8d0a151c3675f0e35b021fd578ef074f
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Wed Apr 22 13:00:27 2026 +0200
ASVS L3 audit: 3.5.8 (ch03_general)
---
.../reports/steve/v3/d0aa7e9/ch03_general/3.5.8.md | 480 +++++++++++++++++++++
1 file changed, 480 insertions(+)
diff --git a/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch03_general/3.5.8.md b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch03_general/3.5.8.md
new file mode 100644
index 0000000..275a6b7
--- /dev/null
+++ b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/ch03_general/3.5.8.md
@@ -0,0 +1,480 @@
+# ASVS 3.5.8 Security Audit Report
+
+## Executive Summary
+
+**Audit Metadata:**
+- **Repository:** apache/steve
+- **Audit Date:** Apr 22, 2026
+- **Auditor:** Tooling Agents
+- **ASVS Requirement:** 3.5.8 — Verify that authenticated resources (such as images, videos, scripts, and other documents) can be loaded or embedded on behalf of the user only when intended. This can be accomplished by strict validation of the Sec-Fetch-* HTTP request header fields to ensure that the request did not originate from an inappropriate cross-origin call, or by setting a restrictive Cross-Origin-Resource-Policy HTTP response header field to instruct the browser to block return [...]
+- **Files Analyzed:** 3 relevant out of 49 total, 12 skipped
+
+**Finding Summary:**
+
+| Severity | Count | Status |
+|----------|-------|--------|
+| Critical | 0 | ✅ None found |
+| High | 2 | ⚠️ Requires immediate attention |
+| Medium | 1 | ⚠️ Requires attention |
+| Low | 0 | ✅ None found |
+| **Total** | **3** | |
+
+**Key Findings:**
+The application has **no implementation** of either Sec-Fetch-* request header validation or Cross-Origin-Resource-Policy response headers. This represents a complete absence of the controls specified in ASVS 3.5.8, creating systemic Type A gaps across all authenticated endpoints that serve resources.
+
+---
+
+## Security Controls Inventory
+
+### Current Control Status
+
+| Control | Defined? | Location | Applied To | Coverage |
+|---------|----------|----------|------------|----------|
+| Sec-Fetch-* header validation | ❌ No | N/A | N/A | 0% — Not implemented anywhere |
+| Cross-Origin-Resource-Policy header | ❌ No | N/A | N/A | 0% — Not set on any response |
+| X-Frame-Options / frame-ancestors CSP | ❌ No | N/A | N/A | 0% — Not visible in code |
+| CORS configuration | ❌ No | N/A | N/A | 0% — Not visible in code |
+| Authentication decorators | ✅ Yes | `pages.py` | Most endpoints | ~85% of sensitive endpoints |
+
+### Entry Points Requiring Sec-Fetch/CORP Protection
+
+| Endpoint | Method | Auth | Serves Resources | Sec-Fetch Check | CORP Header | Gap Type |
+|----------|--------|------|-----------------|-----------------|-------------|----------|
+| `/docs/<iid>/<docname>` | GET | Session | Documents/images/scripts | ❌ | ❌ | Type A |
+| `/voter` | GET | Committer | HTML page | ❌ | ❌ | Type A |
+| `/vote-on/<eid>` | GET | Committer | HTML page | ❌ | ❌ | Type A |
+| `/admin` | GET | Committer | HTML page | ❌ | ❌ | Type A |
+| `/manage/<eid>` | GET | Committer | HTML page | ❌ | ❌ | Type A |
+| `/manage-stv/<eid>/<iid>` | GET | Committer | HTML page | ❌ | ❌ | Type A |
+| `/profile` | GET | Session | HTML page | ❌ | ❌ | Type A |
+| `/do-open/<eid>` | GET | Committer | State-changing (redirect) | ❌ | ❌ | Type A |
+| `/do-close/<eid>` | GET | Committer | State-changing (redirect) | ❌ | ❌ | Type A |
+| `/do-vote/<eid>` | POST | Committer | State-changing | ❌ | ❌ | Type A |
+| `/do-set-open_at/<eid>` | POST | Committer | State-changing | ❌ | ❌ | Type A |
+| `/do-set-close_at/<eid>` | POST | Committer | State-changing | ❌ | ❌ | Type A |
+
+---
+
+## Critical File Review
+
+### `v3/server/pages.py`
+
+| Line(s) | Pattern | Status | Notes |
+|---------|---------|--------|-------|
+| 78 | CSRF token | ⚠️ Placeholder only | `basic.csrf_token = 'placeholder'` — acknowledged debt |
+| 462–478 | State change via GET | ❌ Vulnerable | `/do-open/` and `/do-close/` — no Sec-Fetch check |
+| 587–603 | Document serving | ❌ Vulnerable | No CORP header, no Sec-Fetch validation |
+| All endpoints | Response headers | ❌ Missing | No `after_request` hook for security headers |
+| 137, 229, 294, 339, 361 | Auth decorators | ✅ Present | `@asfquart.auth.require` correctly applied |
+| 400, 440, 499, 521, 544 | POST for mutations | ✅ Correct | Most state-changing endpoints use POST |
+
+### `v3/server/templates/manage.ezt`
+
+| Line(s) | Pattern | Status | Notes |
+|---------|---------|--------|-------|
+| ~310 | CSRF in fetch | ⚠️ Incomplete | Token sent in header but server uses placeholder value |
+| ~277, ~285 | `window.location.href` for state changes | ❌ GET navigation | Triggers `/do-open/` and `/do-close/` via GET |
+| ~226–229 | EZT escaping | ✅ Good | `[format "js,html"]` used for JS context |
+| ~173 | Form CSRF token | ⚠️ Placeholder | `<input type="hidden" name="csrf_token" value="[csrf_token]">` |
+
+### `v3/steve/election.py`
+
+| Line(s) | Pattern | Status | Notes |
+|---------|---------|--------|-------|
+| 87–95 | `open()` irreversible | ⚠️ Context | Generates salts and key — called from unprotected GET endpoint |
+| 120–123 | `close()` irreversible | ⚠️ Context | Marks election closed — called from unprotected GET endpoint |
+| 132–155 | State assertions | ✅ Good | `assert self.is_editable()` / `assert self.is_open()` prevent wrong-state operations |
+| 196–207 | Metadata filtering | ✅ Good | `get_metadata()` excludes SALT and OPENED_KEY |
+| 303–319 | Vote encryption | ✅ Good | Proper crypto pipeline for vote storage |
+
+---
+
+## Findings
+
+### High Severity Findings
+
+#### Finding ASVS-358-HIGH-001
+
+**Cross-Origin Resource Loading of Authenticated Documents**
+
+| Attribute | Value |
+|-----------|-------|
+| **Severity** | HIGH |
+| **Finding ID** | ASVS-358-HIGH-001 |
+| **File** | `v3/server/pages.py` |
+| **Function** | `serve_doc()` (lines 587–603) |
+| **Gap Type** | Type A — No control exists |
+
+**Description:**
+
+The `/docs/<iid>/<docname>` endpoint serves authenticated documents (images, scripts, PDFs, and other files associated with election issues) without setting a `Cross-Origin-Resource-Policy` response header and without validating `Sec-Fetch-*` request headers. This allows a malicious cross-origin page to embed or load these authenticated resources on behalf of a logged-in user.
+
+**Vulnerable Code:**
+
+```python
+# Serve supporting documents for an issue
+@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
+
+ db = steve.election.Election.open_database(DB_FNAME)
+ row = db.q_get_mayvote.first_row(result.uid, iid)
+ if not row:
+ quart.abort(404)
+
+ ### verify the propriety of DOCNAME. # <-- validation TODO noted but not implemented
+
+ # Return per-issue document — NO CORP header, NO Sec-Fetch check
+ return await quart.send_from_directory(DOCSDIR / iid, docname)
+```
+
+**Data Flow:**
+
+1. **Source:** Cross-origin page controlled by attacker includes `<img src="https://voting-app.example.com/docs/ISSUE_ID/secret-ballot.pdf">`
+2. **Sink:** `quart.send_from_directory()` returns the authenticated document
+3. **Missing Control:** No `Sec-Fetch-Site` validation to reject cross-origin requests; No `Cross-Origin-Resource-Policy: same-origin` header on the response
+
+**Proof of Concept:**
+
+```html
+<!-- Attacker-controlled page at evil.example.com -->
+<html>
+<body>
+ <!-- Load authenticated election document cross-origin -->
+ <img src="https://voting-app.example.com/docs/abc123/candidate-photo.jpg"
+ onload="exfiltrate(this)" onerror="tryNext()">
+
+ <!-- Embed as script to probe for existence/size -->
+ <script src="https://voting-app.example.com/docs/abc123/notes.js"></script>
+
+ <!-- Or embed in iframe -->
+ <iframe src="https://voting-app.example.com/docs/abc123/ballot-details.pdf"></iframe>
+</body>
+</html>
+```
+
+If the user is authenticated and visits the attacker page, authenticated documents are loaded cross-origin. Even if content cannot be directly read (due to Same-Origin Policy for some resource types), image dimensions, load/error timing, and existence probing are possible.
+
+**Impact:**
+
+- Authenticated election documents (images, PDFs, scripts) can be loaded by cross-origin pages when the user has an active session
+- Attackers can confirm existence of specific documents and issues
+- Image content is directly rendered; document metadata leaks via timing/size
+- Election-sensitive material (candidate information, ballot details referenced via `doc:filename` in issue descriptions) exposed
+
+**Remediation:**
+
+```python
+@APP.get('/docs/<iid>/<docname>')
+@asfquart.auth.require
+async def serve_doc(iid, docname):
+ # Validate Sec-Fetch-* headers to ensure same-origin navigation
+ sec_fetch_site = quart.request.headers.get('Sec-Fetch-Site', '')
+ sec_fetch_dest = quart.request.headers.get('Sec-Fetch-Dest', '')
+
+ # Only allow same-origin or same-site requests
+ if sec_fetch_site not in ('same-origin', 'same-site', 'none'):
+ quart.abort(403)
+
+ # Only allow document/image/empty destinations (navigation, link clicks)
+ if sec_fetch_dest not in ('document', 'image', 'empty', ''):
+ quart.abort(403)
+
+ 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)
+
+ response = await quart.send_from_directory(DOCSDIR / iid, docname)
+ response.headers['Cross-Origin-Resource-Policy'] = 'same-origin'
+ return response
+```
+
+---
+
+#### Finding ASVS-358-HIGH-002
+
+**State-Changing GET Endpoints Vulnerable to Cross-Origin Resource Embedding**
+
+| Attribute | Value |
+|-----------|-------|
+| **Severity** | HIGH |
+| **Finding ID** | ASVS-358-HIGH-002 |
+| **File** | `v3/server/pages.py` |
+| **Functions** | `do_open_endpoint()` (line 462), `do_close_endpoint()` (line 481) |
+| **Gap Type** | Type A — No control exists |
+
+**Description:**
+
+The `/do-open/<eid>` and `/do-close/<eid>` endpoints perform irreversible state-changing operations (opening and closing elections) via GET requests. Combined with the complete absence of `Sec-Fetch-*` header validation, these endpoints can be triggered by a cross-origin page embedding the URL as a resource (e.g., `<img>`, `<link>`, `<script>`). This is distinct from general CSRF because the attack vector is specifically through cross-origin resource loading — the mechanism ASVS 3.5.8 is [...]
+
+**Vulnerable Code:**
+
+```python
+@APP.get('/do-open/<eid>')
+@asfquart.auth.require({R.committer}) ### need general solution
+@load_election
+async def do_open_endpoint(election):
+ result = await basic_info()
+ ### check authz # <-- authz TODO not implemented
+ pdb = steve.persondb.PersonDB.open(DB_FNAME)
+ election.open(pdb) # Irreversible state change
+ # ...
+ return quart.redirect(f'/manage/{election.eid}', code=303)
+
+
+@APP.get('/do-close/<eid>')
+@asfquart.auth.require({R.committer}) ### need general solution
+@load_election
+async def do_close_endpoint(election):
+ result = await basic_info()
+ ### check authz # <-- authz TODO not implemented
+ election.close() # Irreversible state change
+ # ...
+ return quart.redirect(f'/manage/{election.eid}', code=303)
+```
+
+**Data Flow:**
+
+1. **Source:** Cross-origin page includes `<img src="https://voting-app.example.com/do-open/KNOWN_EID">`
+2. **Sink:** `election.open(pdb)` or `election.close()` executes the irreversible state transition
+3. **Missing Control:** No `Sec-Fetch-Dest` check to reject `image`/`script`/`style` destinations; No `Sec-Fetch-Site` check to reject `cross-site` origin; No `Sec-Fetch-Mode` check to reject `no-cors` mode
+
+**Proof of Concept:**
+
+```html
+<!-- Attacker page: forces election open/close via resource embedding -->
+<html>
+<body>
+ <!-- Trigger election opening via image embed -->
+ <img src="https://voting-app.example.com/do-open/e_abc123def" width="1" height="1">
+
+ <!-- Or close an election -->
+ <img src="https://voting-app.example.com/do-close/e_xyz789" width="1" height="1">
+
+ <!-- Multiple elections can be targeted simultaneously -->
+ <link rel="prefetch" href="https://voting-app.example.com/do-open/e_target2">
+</body>
+</html>
+```
+
+When a committer visits this page while authenticated, the browser sends GET requests with session cookies. The server has no way to distinguish these from legitimate navigations because `Sec-Fetch-*` headers are not checked.
+
+**Impact:**
+
+- **Election integrity compromise:** An attacker can force-open elections prematurely (before proper voter rolls, issues, or dates are finalized)
+- **Denial of service:** An attacker can force-close open elections, permanently ending voting
+- Both operations are explicitly irreversible (`assert self.is_editable()` / `assert self.is_open()` — once changed, cannot revert)
+- The `election.py` code confirms: `open()` triggers salt generation and key derivation (line 88-95); `close()` permanently marks the election closed (line 120)
+
+**Remediation:**
+
+```python
+# 1. Change to POST methods (defense-in-depth alongside Sec-Fetch)
+@APP.post('/do-open/<eid>')
+@asfquart.auth.require({R.committer})
+@load_election
+async def do_open_endpoint(election):
+ validate_sec_fetch(quart.request) # See middleware below
+ # ... existing logic ...
+
+@APP.post('/do-close/<eid>')
+@asfquart.auth.require({R.committer})
+@load_election
+async def do_close_endpoint(election):
+ validate_sec_fetch(quart.request)
+ # ... existing logic ...
+
+# 2. Update the JavaScript in manage.ezt template:
+# Change: window.location.href = '/do-open/[eid]';
+# To: fetch('/do-open/[eid]', { method: 'POST', headers: {...} })
+# .then(r => window.location.href = '/manage/[eid]');
+```
+
+---
+
+### Medium Severity Findings
+
+#### Finding ASVS-358-MEDIUM-003
+
+**Systemic Absence of Cross-Origin Resource Protection**
+
+| Attribute | Value |
+|-----------|-------|
+| **Severity** | MEDIUM |
+| **Finding ID** | ASVS-358-MEDIUM-003 |
+| **File** | `v3/server/pages.py` |
+| **Function** | All authenticated endpoints (global) |
+| **Gap Type** | Type A — No control exists at any layer |
+
+**Description:**
+
+The application has no global mechanism — neither middleware, `after_request` hook, nor per-endpoint logic — to set `Cross-Origin-Resource-Policy` response headers or validate `Sec-Fetch-*` request headers on any response. This is a systemic architectural gap affecting all 15+ authenticated endpoints. ASVS 3.5.8 requires one of these mechanisms; neither is present.
+
+**Vulnerable Code (representative — `basic_info()` and all endpoints):**
+
+```python
+async def basic_info():
+ """Return base-level EZT template data."""
+ basic = edict()
+ # ... session data ...
+ ### generate a real token and store in the session
+ basic.csrf_token = 'placeholder' # No CORP/Sec-Fetch logic anywhere
+ return basic
+
+# No @APP.after_request or middleware adding CORP headers
+# No Sec-Fetch validation decorator or utility function exists
+```
+
+**Comprehensive Coverage Gap Analysis:**
+
+| Authenticated Endpoint | Resource Type | Sec-Fetch Check | CORP Header |
+|------------------------|--------------|-----------------|-------------|
+| `GET /voter` | HTML (election list, voter data) | ❌ None | ❌ None |
+| `GET /vote-on/<eid>` | HTML (ballot content, candidates) | ❌ None | ❌ None |
+| `GET /admin` | HTML (owned elections) | ❌ None | ❌ None |
+| `GET /manage/<eid>` | HTML (election management, issues) | ❌ None | ❌ None |
+| `GET /manage-stv/<eid>/<iid>` | HTML (STV issue details) | ❌ None | ❌ None |
+| `GET /profile` | HTML (user profile) | ❌ None | ❌ None |
+| `GET /settings` | HTML (user settings) | ❌ None | ❌ None |
+| `GET /docs/<iid>/<docname>` | Mixed (docs/images/scripts) | ❌ None | ❌ None |
+| `POST /do-vote/<eid>` | Redirect (after vote) | ❌ None | ❌ None |
+| `POST /do-set-open_at/<eid>` | Empty 204 | ❌ None | ❌ None |
+| `POST /do-set-close_at/<eid>` | Empty 204 | ❌ None | ❌ None |
+| `POST /do-add-issue/<eid>` | Redirect | ❌ None | ❌ None |
+| `POST /do-edit-issue/<eid>/<iid>` | Redirect | ❌ None | ❌ None |
+| `POST /do-delete-issue/<eid>/<iid>` | Redirect | ❌ None | ❌ None |
+| `GET /do-open/<eid>` | Redirect (state change) | ❌ None | ❌ None |
+| `GET /do-close/<eid>` | Redirect (state change) | ❌ None | ❌ None |
+
+**Impact:**
+
+- No browser-enforced cross-origin resource blocking on any authenticated response
+- Authenticated HTML pages can be iframed by malicious sites (clickjacking vector; no `X-Frame-Options` visible either)
+- Cross-origin scripts can probe authenticated endpoints for timing/error-based information disclosure
+- The application relies solely on Same-Origin Policy, which does not prevent resource loading (only reading in some contexts)
+
+**Remediation — Global Middleware:**
+
+```python
+@APP.after_request
+async def add_security_headers(response):
+ """Set Cross-Origin-Resource-Policy and validate Sec-Fetch-* on all responses."""
+ # Set CORP on all responses to block cross-origin resource loads
+ response.headers['Cross-Origin-Resource-Policy'] = 'same-origin'
+
+ # Additional defense-in-depth headers
+ response.headers['X-Frame-Options'] = 'DENY'
+ response.headers['X-Content-Type-Options'] = 'nosniff'
+
+ return response
+
+def validate_sec_fetch(request):
+ """Validate Sec-Fetch-* headers for state-changing and sensitive endpoints."""
+ sec_fetch_site = request.headers.get('Sec-Fetch-Site', '')
+
+ # For browsers that send Sec-Fetch-*, reject cross-origin
+ if sec_fetch_site and sec_fetch_site not in ('same-origin', 'same-site', 'none'):
+ quart.abort(403, 'Cross-origin request blocked')
+
+ # For state-changing endpoints, also check mode
+ sec_fetch_mode = request.headers.get('Sec-Fetch-Mode', '')
+ if sec_fetch_mode and sec_fetch_mode == 'no-cors':
+ quart.abort(403, 'Unexpected request mode')
+
+# Apply to sensitive endpoints as a decorator:
+def require_same_origin(func):
+ @functools.wraps(func)
+ async def wrapper(*args, **kwargs):
+ validate_sec_fetch(quart.request)
+ return await func(*args, **kwargs)
+ return wrapper
+```
+
+---
+
+## Positive Security Patterns
+
+The following security patterns are correctly implemented and should be maintained:
+
+1. **Authentication decorators consistently applied**: All sensitive endpoints use `@asfquart.auth.require` with appropriate role requirements (`R.committer`, `R.pmc_member`).
+
+2. **Template escaping for JavaScript context**: The `manage.ezt` template uses `[format "js,html"]` when passing issue titles and descriptions to JavaScript functions, preventing XSS in that context.
+
+3. **Authorization check on document serving**: The `serve_doc()` function performs per-document authorization via `q_get_mayvote.first_row(result.uid, iid)` before serving files.
+
+4. **State machine enforcement in election.py**: Assertions like `assert self.is_editable()` and `assert self.is_open()` enforce correct state transitions server-side, preventing logical misuse even if browser controls are bypassed.
+
+5. **Most state-changing operations use POST**: Endpoints like `/do-vote`, `/do-create-election`, `/do-add-issue`, `/do-edit-issue`, `/do-delete-issue`, `/do-set-open_at`, `/do-set-close_at` correctly use POST method.
+
+6. **Sensitive metadata exclusion**: `get_metadata()` in `election.py` explicitly excludes `salt` and `opened_key` from returned data.
+
+---
+
+## Architecture Observations
+
+1. **No middleware layer for security headers**: The application has no `@APP.after_request` or equivalent hook to add security headers globally. This is the root cause of the systemic CORP gap.
+
+2. **Mixed GET/POST for state changes**: While most mutation endpoints correctly use POST, the critical `do-open` and `do-close` endpoints use GET, making them uniquely vulnerable to cross-origin resource embedding attacks.
+
+3. **Template-driven JavaScript navigation**: The `manage.ezt` template uses `window.location.href` for state-changing actions, which generates simple GET navigations. This pattern drives the GET method choice for those endpoints.
+
+4. **Placeholder security controls**: The `csrf_token = 'placeholder'` pattern (line 78) and multiple `### check authz` TODO comments indicate security controls that are designed-for but not yet implemented, creating false confidence if the template infrastructure suggests they're active.
+
+---
+
+## Recommendations Summary
+
+### Immediate Actions (Within 1 Sprint)
+
+| Priority | Recommendation | Addresses | Effort |
+|----------|---------------|-----------|--------|
+| **1** | Add `@APP.after_request` hook that sets `Cross-Origin-Resource-Policy: same-origin` on all responses | ASVS-358-HIGH-001, ASVS-358-MEDIUM-003 | Low |
+| **2** | Change `/do-open/<eid>` and `/do-close/<eid>` from GET to POST; update `manage.ezt` JavaScript to use `fetch()` with POST | ASVS-358-HIGH-002 | Medium |
+| **3** | Add `X-Frame-Options: DENY` and `Content-Security-Policy: frame-ancestors 'self'` to the global response headers | ASVS-358-MEDIUM-003 (defense-in-depth) | Low |
+
+### Short-term Actions (1-2 Sprints)
+
+| Priority | Recommendation | Addresses | Effort |
+|----------|---------------|-----------|--------|
+| **4** | Implement `Sec-Fetch-*` validation middleware for all authenticated endpoints, rejecting requests where `Sec-Fetch-Site` is `cross-site` | ASVS-358-HIGH-001, ASVS-358-HIGH-002, ASVS-358-MEDIUM-003 | Medium |
+| **5** | Set `Cross-Origin-Resource-Policy: same-origin` explicitly on `serve_doc()` responses, even if the global middleware is added, as a belt-and-suspenders measure | ASVS-358-HIGH-001 | Low |
+
+### Long-term Actions (Strategic)
+
+- Implement complete CSRF protection to replace placeholder tokens
+- Complete authorization checks for all endpoints marked with `### check authz` TODOs
+- Establish security header testing in CI/CD pipeline to prevent regression
+
+---
+
+## Appendix: Files Analyzed
+
+<details>
+<summary>Click to expand file list</summary>
+
+### Files Reviewed (3 files)
+
+1. **`v3/server/pages.py`** (relevance: 9/10)
+ - Primary application routing and endpoint definitions
+ - Contains all authenticated endpoints
+ - Missing security header middleware
+ - Contains vulnerable `serve_doc()`, `do_open_endpoint()`, and `do_close_endpoint()` functions
+
+2. **`v3/server/templates/manage.ezt`** (relevance: 4/10)
+ - Election management interface template
+ - Contains JavaScript that triggers GET-based state changes
+ - Demonstrates CSRF token placeholder usage
+
+3. **`v3/steve/election.py`** (relevance: 4/10)
+ - Election state management and business logic
+ - Contains irreversible `open()` and `close()` operations
+ - Demonstrates proper state machine assertions
+
+</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