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 12:12:55 UTC
(tooling-agents) branch main updated: ASVS audit: 14.2.6.md (redacted)
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 a314f90 ASVS audit: 14.2.6.md (redacted)
a314f90 is described below
commit a314f90d6c4294f9542f2d4335d7cb63c04319a6
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Tue May 5 14:04:02 2026 +0200
ASVS audit: 14.2.6.md (redacted)
---
ASVS/reports/steve/v3/d0aa7e9/all/14.2.6.md | 172 ++++++++++++++++++++++++++++
1 file changed, 172 insertions(+)
diff --git a/ASVS/reports/steve/v3/d0aa7e9/all/14.2.6.md b/ASVS/reports/steve/v3/d0aa7e9/all/14.2.6.md
new file mode 100644
index 0000000..76887a7
--- /dev/null
+++ b/ASVS/reports/steve/v3/d0aa7e9/all/14.2.6.md
@@ -0,0 +1,172 @@
+# Security Audit Report: ASVS 14.2.6
+
+## Executive Summary
+
+| Field | Value |
+|-------|-------|
+| **Repository** | apache/steve/v3 |
+| **Audit Date** | May 05, 2026 |
+| **Auditor** | Tooling Agents (bundled-pass mode) |
+| **ASVS Requirement** | ASVS 14.2.6 |
+| **Files Analyzed** | 9 relevant / 49 total |
+| **Files Skipped** | 12 |
+
+**Requirement description:** Verify that the application only returns the minimum required sensitive data for the application's functionality. For example, only returning some of the digits of a credit card number and not the full number. If the complete data is required, it should be masked in the user interface unless theβ¦
+
+### Findings Overview
+
+| Severity | Count |
+|----------|-------|
+| π΄ Critical | 0 |
+| π High | 0 |
+| π‘ Medium | 1 |
+| π’ Low | 1 |
+
+---
+
+## ASVS-14.2.6: Minimum Required Sensitive Data Returned
+
+### Findings for 14.2.6
+
+### [MEDIUM]
+
+**Finding ID:** ASVS-1426-MED-001
+
+**Title:** Full Email Addresses Exposed in Session Context Without Masking
+
+**File:** `v3/server/pages.py`, lines 68-77
+**Function:** `basic_info`
+
+**Vulnerable Code:**
+```python
+async def basic_info():
+ s = await asfquart.session.read()
+ if s:
+ basic.update(
+ uid=s['uid'],
+ name=s['fullname'],
+ email=s['email'], # Full email passed to all templates
+ )
+```
+
+**Data Flow:** Session store β `basic_info()` β template context β **all HTML page responses** β client browser.
+
+**Impact:** The full email address from the user's session is passed to every template render. While this may be the user's own email (acceptable for profile page), it's passed to ALL templates including public-facing ones. If any template exposes this in HTML or JavaScript context, the full email is visible. The email should only be returned where functionally required (e.g., profile page).
+
+**Remediation:**
+```python
+async def basic_info():
+ s = await asfquart.session.read()
+ if s:
+ basic.update(
+ uid=s['uid'],
+ name=s['fullname'],
+ # Only include email in contexts that need it
+ # email=s['email'], # Don't include by default
+ )
+ return basic
+
+# In profile_page specifically:
+async def profile_page():
+ result = await basic_info()
+ s = await asfquart.session.read()
+ result.email = s['email'] # Only where needed
+```
+
+---
+
+### [LOW]
+
+**Finding ID:** ASVS-1426-LOW-001
+
+**Title:** Voter Email Addresses Returned in Bulk by get_voters_for_email Without Pagination or Need-Based Filtering
+
+**File:** `v3/steve/election.py`, lines 445-450
+**Function:** `get_voters_for_email`
+
+**Vulnerable Code:**
+```python
+def get_voters_for_email(self):
+ "Return a list of distinct voters (pid, name, email) eligible for this election."
+ self.q_voting_persons.perform(self.eid)
+ return [
+ edict(pid=row.pid, name=row.name, email=row.email)
+ for row in self.q_voting_persons.fetchall()
+ ]
+```
+
+**Data Flow:** Database β `q_voting_persons` β full list of voter PII (pid, name, email) β calling script.
+
+**Impact:** This function returns all voter emails in bulk. Currently used only by `mail-voters.py` (admin script) and `gather_election_data()`. The function itself returns more data than strictly needed for the email-sending operation (pid is not needed for sending mail). The risk is low since this is an admin function, but follows the principle of returning minimum data.
+
+**Remediation:** This is acceptable for the admin email script use case. Ensure this function is not exposed through web endpoints to unauthorized users.
+
+### Security Controls Inventory for 14.2.6
+
+| Control | Location | Status |
+|---------|----------|--------|
+| Salt/opened_key exclusion from API | `election.py` `get_metadata()` line 144 | **PRESENT** β |
+| Salt exclusion from issue data | `election.py` `get_issue()` line 160 | **PRESENT** β |
+| Sensitive columns excluded from listing queries | `queries.yaml` q_open_to_me, q_owned | **PRESENT** β |
+| Vote token not returned to users | `election.py` `add_vote()` | **PRESENT** β |
+| Email masking | `pages.py` `basic_info()` | **NOT PRESENT** |
+| Data minimization in PersonDB queries | `queries.yaml` q_person | Returns all fields (acceptable for admin) |
+
+### Positive Patterns for 14.2.6
+
+- **Explicit salt/key exclusion** β `get_metadata()` at line 144 explicitly constructs a return value omitting `salt` and `opened_key`:
+ ```python
+ return edict(
+ eid=md.eid, title=md.title, owner_pid=md.owner_pid,
+ authz=md.authz, closed=md.closed,
+ open_at=md.open_at, close_at=md.close_at,
+ state=self._compute_state(md),
+ )
+ ```
+- **Comment documentation** β Line 142: `# NOTE: do not return the SALT or OPENED_KEY`
+- **Query design** β `q_open_to_me`, `q_owned`, and `q_upcoming_to_me` queries explicitly list columns to select, omitting `salt` and `opened_key`. The `q_open_to_me` query comment explicitly states: "Note: SALT and OPENED_KEY are never returned for security."
+- **Issue salt never exposed** β `get_issue()` at line 157 returns only `(title, description, type, kv)`, never the `salt` field.
+- **Vote ciphertext stays internal** β The `tally_issue()` method decrypts votes internally and only returns the tally result and voter set, never raw ciphertext or tokens.
+
+---
+
+
+
+## Cross-cutting Architecture Observations
+
+1. **Security Control Consistency Gap**: The application has strong cryptographic controls (Argon2, Fernet encryption, BLAKE2b, CSPRNG for IDs and shuffling) but lacks fundamental HTTP-layer security headers (Cache-Control, X-Content-Type-Options). This suggests the security design focused on cryptographic vote integrity but did not address transport/caching layer concerns.
+
+2. **Internal vs. External API Surface**: The `Election._all_metadata()` method is documented as "INTERNAL ONLY" and returns sensitive fields (salt, opened_key), while `get_metadata()` is the safe public API. This separation is correct but relies on convention rather than enforcement (no access modifier, just naming convention with underscore prefix).
+
+3. **Authorization Gap (Cross-cutting)**: Multiple endpoints have `### check authz` placeholder comments. While authentication is enforced via decorators, fine-grained authorization (e.g., only election owner can open/close/manage) is not implemented. This affects multiple ASVS requirements indirectly β if an unauthorized user can access management endpoints, they could view sensitive election data that should be minimized.
+
+4. **Data Flow Segregation**: The system properly segregates sensitive cryptographic material (salt, opened_key) from public-facing queries at the SQL level (separate queries for internal vs. external use). This is a strong architectural pattern.
+
+5. **Template Security**: The EZT templating engine used for HTML rendering requires careful attention to output encoding. The `vote-on.ezt` template includes a client-side `escapeHtml()` function for JavaScript-rendered content, but server-side rendering relies on EZT's default behavior.
+
+6. **Operational Tooling**: The `mail-voters.py` script runs as a standalone process with direct database access. It accesses voter PII (email addresses) without the web application's session/auth layer, relying instead on OS-level access controls to the script and database file.
+
+---
+
+## Cross-cutting Recommendations
+
+### Immediate (Within Sprint)
+
+1. **Remove debug print statements** (`pages.py` lines 481, 497) β Direct PII/data exposure risk.
+2. **Add `Cache-Control: no-store, private` header** to all authenticated responses via an `@APP.after_request` handler.
+3. **Add `X-Content-Type-Options: nosniff` header** globally.
+
+### Short-term (Within Quarter)
+
+4. **Implement authorization checks** at all `### check authz` placeholder locations β verify the authenticated user is the election owner or in the authorized group before allowing management operations.
+5. **Document data retention policy** β classify each data type (elections, votes, persons) with explicit retention periods and deletion triggers.
+6. **Implement CSRF token validation** β replace the placeholder token with a real per-session token and validate on all POST endpoints.
+7. **Validate `docname` parameter** in `serve_doc()` β restrict to safe filename characters.
+
+### Long-term (Within Year)
+
+8. **Implement automated data purge** β scheduled job to delete closed elections past retention period and purge superseded votes after tally certification.
+9. **Complete migration to XChaCha20-Poly1305** with AAD binding vote ciphertext to vote_token.
+10. **Implement log classification** β ensure logs containing user-action mappings have appropriate retention limits and access controls distinct from application data.
+11. **Add per-response caching policy** β static assets should have appropriate cache headers (`max-age`), while dynamic authenticated content should have `no-store`.
+
\ 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