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 05:50:37 UTC
(tooling-agents) branch main updated: ASVS L3 audit: 16.4.2 (logging_and_monitoring)
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 7e3fe4b ASVS L3 audit: 16.4.2 (logging_and_monitoring)
7e3fe4b is described below
commit 7e3fe4bfdcd06549a6a9b71c13d024fbc2a2da52
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Wed Apr 22 07:50:33 2026 +0200
ASVS L3 audit: 16.4.2 (logging_and_monitoring)
---
.../v3/d0aa7e9/logging_and_monitoring/16.4.2.md | 433 +++++++++++++++++++++
1 file changed, 433 insertions(+)
diff --git a/repos/apache/ASVS/reports/steve/v3/d0aa7e9/logging_and_monitoring/16.4.2.md b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/logging_and_monitoring/16.4.2.md
new file mode 100644
index 0000000..f2d7f9a
--- /dev/null
+++ b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/logging_and_monitoring/16.4.2.md
@@ -0,0 +1,433 @@
+# ASVS 16.4.2 Audit Report: apache/steve
+
+## Executive Summary
+
+**Repository:** apache/steve
+**Audit Date:** Apr 22, 2026
+**Auditor:** Tooling Agents
+**ASVS Requirement:** 16.4.2 — Verify that logs are protected from unauthorized access and cannot be modified.
+**Level:** 2
+**Section:** Log Protection
+
+**Files analyzed:** 4 relevant out of 4 total, 0 skipped
+
+### Finding Summary
+
+| Severity | Count | IDs |
+|----------|-------|-----|
+| Critical | 0 | — |
+| High | 2 | ASVS-1642-SEV-001, ASVS-1642-SEV-002 |
+| Medium | 1 | ASVS-1642-SEV-003 |
+| Low | 0 | — |
+| **Total** | **3** | |
+
+### Overview
+
+The application logs security-relevant events (vote submissions, election state changes, authentication decisions) via Python's standard `logging` module and `print()` statements, but provides **no mechanisms to protect log integrity or restrict log access**. For an election auditing system where forensic log reliability is paramount, this represents a significant gap against ASVS 16.4.2.
+
+---
+
+## Security Controls Inventory
+
+| Control | Defined | Location | Purpose | Coverage Status |
+|---------|---------|----------|---------|-----------------|
+| Structured logging format | ✅ | `main.py:56-59`, `main.py:87-91` | Provides parseable log format with timestamp, severity, source | Both standalone and ASGI modes |
+| Security event logging | ✅ | `pages.py` (multiple endpoints) | Records state-changing operations | Most state-changing operations |
+| Log file protection (permissions) | ❌ | N/A | Restrict filesystem access to logs | Not implemented |
+| Log immutability (append-only/remote) | ❌ | N/A | Prevent log modification after write | Not implemented |
+| Log access control | ❌ | N/A | Control who can read logs | Not implemented |
+| Log integrity verification | ❌ | N/A | Detect log tampering | Not implemented |
+| Protected audit trail for tally | ❌ | N/A | Record tally operations immutably | Not implemented |
+
+---
+
+## Critical File Review
+
+### `v3/server/main.py` — Logging Configuration
+
+| Line | Pattern | Status | Notes |
+|------|---------|--------|-------|
+| 52-59 | `logging.basicConfig()` standalone | ⚠️ ISSUE | No file handler, no protection, DEBUG level |
+| 84-91 | `logging.basicConfig()` ASGI | ⚠️ ISSUE | Same as standalone |
+| 63-68 | Logger level adjustments | ✅ OK | Reduces noise from asyncio/hpack |
+| N/A | Log file permissions | ❌ MISSING | No configuration exists |
+| N/A | Remote log forwarding | ❌ MISSING | No SIEM/syslog integration |
+| N/A | Log integrity mechanisms | ❌ MISSING | No checksums/signatures |
+
+### `v3/server/pages.py` — Security Event Logging
+
+| Line | Event | Logged? | Protected? |
+|------|-------|---------|------------|
+| 109 | Set election date | ✅ Yes | ❌ No protection |
+| 362 | Vote submission | ✅ Yes | ❌ No protection |
+| 365 | Vote submission error | ✅ Yes | ❌ No protection |
+| 388 | Election creation | ✅ Yes | ❌ No protection |
+| 410 | Election opened | ✅ Yes | ❌ No protection |
+| 426 | Election closed | ✅ Yes | ❌ No protection |
+| 449 | Issue added | ✅ Yes | ❌ No protection |
+| 470 | Issue edited | ✅ Yes | ❌ No protection |
+| 487 | Issue deleted | ✅ Yes | ❌ No protection |
+| 447 | Form data (debug) | ⚠️ print() | ❌ No protection |
+| 467 | Form data (debug) | ⚠️ print() | ❌ No protection |
+
+### `v3/server/bin/tally.py` — Tally Audit Trail
+
+| Line | Event | Logged? | Protected? |
+|------|-------|---------|------------|
+| ~140 | Tally initiation | ❌ Not logged | N/A |
+| 143-145 | Tampering detected | ⚠️ print() only | ❌ Not in logging framework |
+| 104-113 | Tally results output | ⚠️ print() only | ❌ Not in logging framework |
+| ~147 | Tally completion | ❌ Not logged | N/A |
+
+---
+
+## Findings
+
+### High Severity
+
+#### ASVS-1642-SEV-001 — No Log Immutability or Write-Protection Controls
+
+**Severity:** HIGH
+
+**Location:**
+- **File:** `v3/server/main.py`
+- **Lines:** 52–59 (standalone), 84–91 (ASGI)
+- **Function:** `run_standalone()`, `run_asgi()`
+
+**Related Functions Checked:**
+- `logging.basicConfig()` configuration
+- Log handler initialization
+- File permission settings (none found)
+
+**Description:**
+
+`logging.basicConfig()` is called without a `filename` parameter, directing all log output to `sys.stderr`. There is no configuration for:
+- File-based logging with restricted permissions
+- Append-only or write-once log storage
+- Remote/centralized log forwarding (e.g., syslog, SIEM)
+- Cryptographic integrity verification of log entries
+- Log rotation with retention guarantees
+
+**Vulnerable Code:**
+
+```python
+# run_standalone() - line 52
+logging.basicConfig(
+ level=logging.DEBUG,
+ style='{',
+ format='[{asctime}|{levelname}|{name}] {message}',
+ datefmt=DATE_FORMAT,
+)
+```
+
+```python
+# run_asgi() - line 84
+logging.basicConfig(
+ level=logging.DEBUG,
+ style='{',
+ format='[{asctime}|{levelname}|{name}] {message}',
+ datefmt=DATE_FORMAT,
+)
+```
+
+**Data Flow:**
+- **Source:** Security events from `pages.py` (vote submissions, election state changes, etc.) and `election.py` (issue/election creation)
+- **Sink:** `stderr` stream → no persistent, protected storage
+- **Missing Control:** Log file permissions, append-only enforcement, remote forwarding, integrity checking
+
+**Proof of Concept:**
+
+```bash
+# If running standalone, all audit logs can be discarded:
+./main.py 2>/dev/null
+
+# If logs are directed to a file by systemd/supervisor:
+# Any process with file write access can modify/truncate:
+echo "" > /var/log/steve/steve.log
+
+# No application-level detection of log tampering exists
+```
+
+**Impact:**
+
+An attacker (or malicious administrator) with process-level or filesystem access can:
+1. Redirect `stderr` to `/dev/null`, silencing all audit logs
+2. Modify or delete log files if stderr is redirected to a file by a process manager
+3. Tamper with forensic evidence of vote manipulation, election state changes, or unauthorized access
+4. Undermine the entire auditing chain that the election system's security model depends upon
+
+The domain context explicitly requires: *"logs are write-protected"* — this is completely unmet.
+
+**Remediation:**
+
+```python
+import logging
+import logging.handlers
+import os
+
+def configure_secure_logging(log_dir='/var/log/steve'):
+ """Configure logging with protection controls."""
+ os.makedirs(log_dir, exist_ok=True)
+ log_file = os.path.join(log_dir, 'steve-audit.log')
+
+ # File handler with append-only semantics
+ file_handler = logging.handlers.WatchedFileHandler(
+ log_file, mode='a', encoding='utf-8'
+ )
+ file_handler.setLevel(logging.INFO)
+ file_handler.setFormatter(logging.Formatter(
+ '[{asctime}|{levelname}|{name}] {message}',
+ style='{', datefmt='%Y-%m-%d %H:%M:%S'
+ ))
+
+ # Remote syslog handler for immutable centralized logging
+ syslog_handler = logging.handlers.SysLogHandler(
+ address=('logserver.example.org', 514)
+ )
+ syslog_handler.setLevel(logging.INFO)
+
+ root = logging.getLogger()
+ root.addHandler(file_handler)
+ root.addHandler(syslog_handler)
+
+ # Set restrictive permissions on the log file
+ os.chmod(log_file, 0o640) # Owner read/write, group read only
+```
+
+---
+
+#### ASVS-1642-SEV-002 — Tally Operations Use Unprotected stdout Instead of Audit Log
+
+**Severity:** HIGH
+
+**Location:**
+- **File:** `v3/server/bin/tally.py`
+- **Lines:** 104–113, 143–145
+- **Function:** `tally_election()`, `main()`
+
+**Related Functions Checked:**
+- `tally_election()` — result output
+- `main()` — tampering detection
+- `election.is_tampered()` — integrity check
+- Logging framework integration (none found)
+
+**Description:**
+
+The tally CLI tool outputs complete election results (including vote tallies, voter lists, and human-readable results) via `print()` to `stdout`. The critical tampering detection event is also output via `print()` rather than through the logging framework. Neither path creates a protected, immutable audit trail.
+
+**Vulnerable Code:**
+
+```python
+# tally_election() - line 104
+if output_format == 'json':
+ print(json.dumps(edict(version=RESULTS_VERSION,
+ results=results,
+ voters=sorted(all_voters),
+ ), indent=2))
+else: # text
+ for iid, data in results.items():
+ print(f'Issue {iid}: {data["title"]} ({data["vtype"]})')
+ print(f'Result: {data["human_result"]}')
+ print(f'Details: {data["supporting_data"]}')
+```
+
+```python
+# main() - line 143
+if election.is_tampered(pdb):
+ print(f'Error: Election {election_id} has been tampered with. Cannot proceed.')
+ sys.exit(1)
+```
+
+**Data Flow:**
+- **Source:** `election.tally_issue()` → decrypted vote results and voter identities
+- **Sink:** `stdout` (unprotected, ephemeral)
+- **Missing Control:** No persistent audit log of who ran the tally, when, and what results were produced; no immutable record of tampering detection
+
+**Proof of Concept:**
+
+```bash
+# Run tally, capture results, then discard - no audit trail remains
+python tally.py --election-id abc123 > /tmp/results.txt
+cat /tmp/results.txt # view results
+rm /tmp/results.txt # destroy evidence
+
+# Tampering event goes to stdout only - easily suppressed:
+python tally.py --election-id abc123 > /dev/null 2>&1
+# No record that tampering was ever detected
+```
+
+**Impact:**
+
+1. Election results can be output, observed, and then the record discarded with no trace
+2. A tampering detection event — the most critical security event in the system — produces only a `print()` to stdout that may never reach persistent storage
+3. No forensic trail of tally operations: who ran them, what parameters were used, what results were produced
+4. The domain requirement that *"tally operations create audit trails"* is not met
+
+**Remediation:**
+
+```python
+def main(spy_on_open, election_id, issue_id, db_fname, output_format):
+ # ... existing code ...
+
+ # Log tally initiation to audit trail
+ _LOGGER.info(
+ f'Tally initiated for election[E:{election_id}] by operator'
+ )
+
+ # Check for tampering - LOG this critical event
+ pdb = steve.persondb.PersonDB.open(db_fname)
+ if election.is_tampered(pdb):
+ _LOGGER.critical(
+ f'TAMPERING DETECTED: Election[E:{election_id}] '
+ f'integrity check failed. Tally aborted.'
+ )
+ print(f'Error: Election {election_id} has been tampered with.',
+ file=sys.stderr)
+ sys.exit(1)
+
+ _LOGGER.info(
+ f'Election[E:{election_id}] integrity verified. Proceeding with tally.'
+ )
+
+ # After tallying, log completion
+ tally_election(election, issue_id, output_format)
+ _LOGGER.info(
+ f'Tally completed for election[E:{election_id}]'
+ )
+```
+
+---
+
+### Medium Severity
+
+#### ASVS-1642-SEV-003 — Debug print() Statements Expose Form Data to Unprotected Stream
+
+**Severity:** MEDIUM
+
+**Location:**
+- **File:** `v3/server/pages.py`
+- **Lines:** 447, 467
+- **Function:** `do_add_issue_endpoint()`, `do_edit_issue_endpoint()`
+
+**Related Functions Checked:**
+- `do_add_issue_endpoint()` — issue creation
+- `do_edit_issue_endpoint()` — issue modification
+- Form data handling
+- Logging framework usage
+
+**Description:**
+
+Production code contains `print('FORM:', form)` statements that dump raw request form data to `stdout`. When running under a process manager (systemd, Hypercorn, Docker), `stdout` is typically captured alongside `stderr` into log files — but these `print()` calls bypass the structured logging framework entirely, have no log level classification, and their output is mixed with tally results in the same unprotected stream.
+
+**Vulnerable Code:**
+
+```python
+# do_add_issue_endpoint() - line 447
+async def do_add_issue_endpoint(election):
+ result = await basic_info()
+ form = edict(await quart.request.form)
+ print('FORM:', form) # <-- debug output to stdout
+
+# do_edit_issue_endpoint() - line 467
+async def do_edit_issue_endpoint(election, issue):
+ result = await basic_info()
+ form = edict(await quart.request.form)
+ print('FORM:', form) # <-- debug output to stdout
+```
+
+**Data Flow:**
+- **Source:** User-submitted form data (issue titles, descriptions)
+- **Sink:** `stdout` — unprotected, unstructured, no access control
+- **Missing Control:** Form data should flow through the structured logging framework at DEBUG level (and not at all in production)
+
+**Proof of Concept:**
+
+```python
+# These print() statements always execute regardless of log level
+# If stdout is captured to a shared log file, form data is exposed to
+# anyone with read access to that file
+```
+
+**Impact:**
+
+1. Form data (issue titles, descriptions, potentially election configuration) is written to an unstructured, unprotected output stream
+2. If `stdout` is captured to a shared log file, this data is accessible to anyone who can read that file
+3. These `print()` statements cannot be suppressed by adjusting log levels — they always execute
+
+**Remediation:**
+
+```python
+# Replace print() with structured logging at DEBUG level
+_LOGGER.debug(f'Add issue form data for election[E:{election.eid}]: '
+ f'title={form.title}')
+# Or better, remove entirely in production code
+```
+
+---
+
+## Positive Security Patterns
+
+1. **Structured log format**: The logging format `[{asctime}|{levelname}|{name}] {message}` provides timestamp, severity, and source module — good for parsing and SIEM ingestion once logs are properly stored.
+
+2. **Consistent security event logging**: `pages.py` logs all state-changing operations with user identity (`User[U:{uid}]`), resource identifiers (`election[E:{eid}]`, `issue[I:{iid}]`), and action type — providing high-signal forensic data.
+
+3. **No vote content in logs**: Vote submission logs record voter ID and issue ID but never the vote content (`votestring`), preserving ballot secrecy.
+
+4. **Tamper detection at tally time**: `tally.py` checks `election.is_tampered(pdb)` before proceeding, and `election.py` has comprehensive anti-tamper mechanisms via `opened_key` verification.
+
+5. **Error abstraction**: `pages.py` shows generic flash messages to users (`'Error submitting vote'`) while logging detailed error information, preventing information disclosure.
+
+---
+
+## Architecture Observations
+
+1. **Dual output streams**: The application uses both `logging` (to stderr) and `print()` (to stdout) for security-relevant output. This split means a single log collection strategy cannot capture all security events.
+
+2. **No log configuration externalization**: There is no configuration file or environment variable to control log destination, level, or format in production. The logging setup is hardcoded in `main.py`.
+
+3. **CLI tool gap**: The `tally.py` CLI tool is arguably the most security-sensitive component (it decrypts and reveals votes), yet it has the weakest audit logging — using `print()` for results and having no record of who initiated the tally or what was produced.
+
+4. **Election system context amplifies risk**: In a standard web application, unprotected logs are a medium concern. In an election system where log integrity directly supports the legitimacy of election results, this becomes a high-priority issue.
+
+---
+
+## Recommendations Summary
+
+### Immediate Actions (Priority 1)
+
+| Action | Effort | Impact | Timeline |
+|--------|--------|--------|----------|
+| Configure file-based logging with restricted permissions (0640) and append-only semantics | Medium | Addresses core write-protection requirement | 1-2 weeks |
+| Add remote log forwarding (syslog/SIEM) for immutability | Medium | Prevents local tampering of logs | 1-2 weeks |
+
+### Short-term Actions (Priority 2)
+
+| Action | Effort | Impact | Timeline |
+|--------|--------|--------|----------|
+| Replace all `print()` in `tally.py` with structured `_LOGGER` calls; log tally initiation, completion, and tampering detection as CRITICAL events | Low | Creates proper audit trail for tallying | 1 week |
+| Remove `print('FORM:', form)` debug statements from production code | Low | Eliminates unstructured data leakage | 1 week |
+| Externalize logging configuration (config file or env vars) to support deployment-specific log protection | Medium | Enables infrastructure-level log protection | 2-3 weeks |
+
+### Long-term Actions (Priority 3)
+
+| Action | Effort | Impact | Timeline |
+|--------|--------|--------|----------|
+| Implement log integrity verification (e.g., hash chains or HMAC per entry) for critical audit events | High | Provides tamper-evidence for logs themselves | 1-2 months |
+
+---
+
+## Appendix: Files Analyzed
+
+<details>
+<summary>Click to expand file list (4 files)</summary>
+
+1. `v3/server/bin/tally.py` (relevance: 10)
+2. `v3/server/main.py` (relevance: 10)
+3. `v3/server/pages.py` (relevance: 10)
+4. `v3/steve/election.py` (relevance: 10)
+
+</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