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 00:08:25 UTC

(tooling-agents) branch main updated: ASVS L3 audit: 11.1.3 (vote_encryption_and_crypto)

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 b8eb073  ASVS L3 audit: 11.1.3 (vote_encryption_and_crypto)
b8eb073 is described below

commit b8eb073a8169141bc53359a7590f8c8d0fce819c
Author: ASF Tooling Team <ro...@apache.org>
AuthorDate: Wed Apr 22 02:08:20 2026 +0200

    ASVS L3 audit: 11.1.3 (vote_encryption_and_crypto)
---
 .../d0aa7e9/vote_encryption_and_crypto/11.1.3.md   | 585 +++++++++++++++++++++
 1 file changed, 585 insertions(+)

diff --git a/repos/apache/ASVS/reports/steve/v3/d0aa7e9/vote_encryption_and_crypto/11.1.3.md b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/vote_encryption_and_crypto/11.1.3.md
new file mode 100644
index 0000000..1e0da7e
--- /dev/null
+++ b/repos/apache/ASVS/reports/steve/v3/d0aa7e9/vote_encryption_and_crypto/11.1.3.md
@@ -0,0 +1,585 @@
+# ASVS 11.1.3 Security Audit Report
+
+## Executive Summary
+
+- **Repository:** apache/steve
+- **Audit Date:** Apr 22, 2026
+- **Auditor:** Tooling Agents
+- **ASVS Requirement:** 11.1.3 — Verify that cryptographic discovery mechanisms are employed to identify all instances of cryptography in the system, including encryption, hashing, and signing operations.
+  - **Level:** 3
+  - **Section:** Cryptographic Inventory and Documentation
+  - **Section Description:** Applications need to be designed with strong cryptographic architecture to protect data assets according to their classification. Encrypting everything is wasteful; not encrypting anything is legally negligent.
+- **Files Analyzed:** 4 relevant out of 4 total, 0 skipped
+
+### Finding Summary
+
+| Severity | Count | Finding IDs |
+|----------|-------|-------------|
+| Critical | 0 | - |
+| High | 1 | ASVS-1113-HIGH-001 |
+| Medium | 3 | ASVS-1113-MED-002, ASVS-1113-MED-003, ASVS-1113-MED-004 |
+| Low | 0 | - |
+| **Total** | **4** | |
+
+### Key Findings
+
+The audited codebase contains a well-structured cryptographic module (`crypto.py`) but has **no discovery mechanism, inventory manifest, or automated tooling** to track cryptographic usage. This absence has already allowed concrete inconsistencies to develop: a production Argon2 variant mismatch, incorrect HKDF domain-separation labels, and a non-constant-time cryptographic comparison.
+
+---
+
+## Security Controls Inventory
+
+### Cryptographic Primitives Actually Present
+
+| Primitive | Location | Purpose | Algorithm/Mode | Coverage Status |
+|-----------|----------|---------|----------------|-----------------|
+| CSPRNG | `crypto.py:gen_salt()` | Salt generation | `secrets.token_bytes(16)` | ✓ Implemented, ❌ Not inventoried |
+| CSPRNG | `crypto.py:create_id()` | ID generation | `secrets.token_hex(5)` | ✓ Implemented, ❌ Not inventoried |
+| CSPRNG | `crypto.py:shuffle()` | Vote order randomization | `secrets.randbelow()` (Fisher-Yates) | ✓ Implemented, ❌ Not inventoried |
+| Hash | `crypto.py:gen_opened_key()` | Pre-processing election data | BLAKE2b (64-byte digest) | ✓ Implemented, ❌ Not inventoried |
+| KDF | `crypto.py:_hash()` | Key/token derivation | **Argon2d** (Type.D), 2 passes, 64MB, parallelism=4 | ⚠ Implemented (wrong variant), ❌ Not inventoried |
+| KDF | `crypto.py:_b64_vote_key()` | Vote key stretching | HKDF-SHA256 | ⚠ Implemented (wrong label), ❌ Not inventoried |
+| Symmetric Enc | `crypto.py:create_vote()` | Vote encryption | Fernet (AES-128-CBC + HMAC-SHA256) | ✓ Implemented, ❌ Not inventoried |
+| Symmetric Dec | `crypto.py:decrypt_votestring()` | Vote decryption | Fernet | ✓ Implemented, ❌ Not inventoried |
+| Comparison | `election.py:is_tampered()` | Tamper detection | Python `!=` (non-constant-time) | ❌ Insecure implementation |
+
+### Cryptographic Discovery Mechanism Status: **ABSENT**
+
+| Expected Mechanism | Present? | Details |
+|-------------------|----------|---------|
+| Automated crypto scanning tool (e.g., Cryptosense, custom SAST rule) | ❌ | No configuration or integration found |
+| Cryptographic inventory manifest/file | ❌ | No `crypto-inventory.json`, `CRYPTO.md`, or equivalent |
+| CI/CD crypto detection pipeline | ❌ | Not evidenced in provided code |
+| Code annotations/decorators marking crypto usage | ❌ | Crypto functions lack inventory markers |
+| Runtime crypto usage logging | ❌ | No instrumentation in crypto.py |
+| Periodic crypto assessment documentation | ❌ | No assessment records |
+
+---
+
+## Critical File Review
+
+### `v3/steve/crypto.py` — Cryptographic Module
+
+| Function | Crypto Operation | Algorithm | Inventoried? | Issues Found |
+|----------|------------------|-----------|--------------|--------------|
+| `gen_salt()` | RNG | `secrets.token_bytes(16)` | ❌ | None |
+| `gen_opened_key()` | Hash + KDF | BLAKE2b → Argon2d | ❌ | Argon2d variant |
+| `gen_vote_token()` | KDF | Argon2d | ❌ | Argon2d variant |
+| `_b64_vote_key()` | KDF | HKDF-SHA256 | ❌ | Wrong info label |
+| `create_vote()` | Encrypt | Fernet (AES-128-CBC) | ❌ | None |
+| `decrypt_votestring()` | Decrypt | Fernet | ❌ | None |
+| `_hash()` | KDF | Argon2d | ❌ | Type.D not Type.ID |
+| `shuffle()` | RNG | `secrets.randbelow()` | ❌ | None |
+| `create_id()` | RNG | `secrets.token_hex(5)` | ❌ | None |
+| `benchmark_argon2()` | KDF | Argon2id | ❌ | Inconsistent with `_hash()` |
+
+### `v3/steve/election.py` — Crypto Usage Points
+
+| Method | Crypto Functions Called | Crypto Consistency Check |
+|--------|------------------------|--------------------------|
+| `open()` | `gen_salt()`, `gen_opened_key()` | ✓ Consistent |
+| `add_salts()` | `gen_salt()` | ✓ Consistent |
+| `add_vote()` | `gen_vote_token()`, `create_vote()` | ✓ Consistent |
+| `tally_issue()` | `gen_vote_token()`, `decrypt_votestring()`, `shuffle()` | ✓ Consistent |
+| `has_voted_upon()` | `gen_vote_token()` | ✓ Consistent |
+| `is_tampered()` | `gen_opened_key()` | ⚠ Non-constant-time comparison |
+| `create()` | `create_id()` | ✓ Consistent |
+| `add_issue()` | `create_id()` | ✓ Consistent |
+
+---
+
+## Findings
+
+### HIGH SEVERITY
+
+#### ASVS-1113-HIGH-001: Absence of Cryptographic Discovery Mechanism
+
+**Severity:** HIGH  
+**Gap Type:** Type A — Required control completely absent  
+**ASVS Requirement:** 11.1.3 — "Verify that cryptographic discovery mechanisms are employed to identify all instances of cryptography in the system"
+
+**Location:**
+- **Files:** All files in codebase
+- **Functions:** N/A (systemic gap)
+- **Lines:** N/A
+
+**Related Functions Checked:**
+- All functions in `v3/steve/crypto.py`
+- All cryptographic usage points in `v3/steve/election.py`
+- Build/CI configuration files (not present)
+
+**Description:**
+
+The system has no automated or documented mechanism to discover, inventory, or track cryptographic usage across the codebase. While `crypto.py` centralizes most cryptographic operations (a positive pattern), there is no process to verify that:
+
+1. All crypto usage goes through `crypto.py`
+2. The algorithms and parameters used match an approved inventory
+3. New crypto additions are detected and assessed
+4. Configuration drift is identified
+
+**Evidence of Impact — Inconsistencies Already Present:**
+
+The absence of discovery mechanisms has allowed three concrete inconsistencies to develop (detailed in subsequent findings):
+- Argon2d used in production while Argon2id is used in benchmarks (same file, lines 79 vs 119)
+- HKDF `info` parameter labels XChaCha20 while actual cipher is Fernet/AES-128-CBC (line 62-65)
+- Non-constant-time cryptographic comparison in tamper detection (election.py line 264)
+
+**Vulnerable Code Pattern:**
+
+```python
+# No crypto_inventory.json, CRYPTO.md, or equivalent exists
+# No SAST rules or CI checks for crypto usage
+# No annotations linking crypto operations to an approved list
+# crypto.py has no self-documenting inventory mechanism
+```
+
+**Data Flow:**
+
+Not applicable — this is a systemic gap affecting all cryptographic operations.
+
+**Proof of Concept:**
+
+The following inconsistencies exist in production code without detection:
+
+1. **Argon2 variant mismatch** (lines 79 vs 119 in crypto.py)
+2. **HKDF label mismatch** (lines 62-68 in crypto.py)
+3. **Timing-vulnerable comparison** (line 264 in election.py)
+
+None of these would pass a cryptographic inventory review.
+
+**Impact:**
+
+Without discovery mechanisms, cryptographic drift goes undetected. The inconsistencies found in this audit (wrong Argon2 variant, wrong HKDF label, non-constant-time comparison) demonstrate real consequences of this gap. As the system evolves, undiscovered crypto usage outside `crypto.py` or parameter changes within it will not be caught.
+
+This creates:
+- **Compliance risk:** Cannot demonstrate ASVS 11.1.3 compliance
+- **Security drift:** Algorithm/parameter changes go unreviewed
+- **Audit inefficiency:** Manual discovery required for each assessment
+- **PQC unreadiness:** No tracking mechanism for quantum-vulnerable primitives
+
+**Remediation:**
+
+**1. Create a cryptographic inventory manifest:**
+
+```json
+// crypto-inventory.json
+{
+  "version": "1.0",
+  "last_reviewed": "2024-01-15",
+  "primitives": [
+    {
+      "id": "CRYPT-001",
+      "algorithm": "Argon2id",
+      "variant": "Type.ID",
+      "purpose": "Key/token derivation",
+      "location": "v3/steve/crypto.py::_hash()",
+      "parameters": {
+        "time_cost": 2,
+        "memory_cost": 65536,
+        "parallelism": 4,
+        "hash_len": 32
+      },
+      "approved": true,
+      "pqc_status": "not_applicable"
+    },
+    {
+      "id": "CRYPT-002",
+      "algorithm": "Fernet (AES-128-CBC + HMAC-SHA256)",
+      "purpose": "Vote encryption/decryption",
+      "location": "v3/steve/crypto.py::create_vote(), decrypt_votestring()",
+      "approved": true,
+      "pqc_status": "aes128_quantum_safety_reduced"
+    },
+    {
+      "id": "CRYPT-003",
+      "algorithm": "HKDF-SHA256",
+      "purpose": "Vote key derivation from token",
+      "location": "v3/steve/crypto.py::_b64_vote_key()",
+      "parameters": {
+        "length": 32,
+        "info": "fernet_vote_key"
+      },
+      "approved": true
+    }
+  ]
+}
+```
+
+**2. Integrate automated crypto scanning in CI:**
+
+```yaml
+# .github/workflows/crypto-audit.yml
+- name: Scan for cryptographic usage
+  run: |
+    # Detect crypto imports not in crypto.py
+    grep -rn "import hashlib\|import hmac\|from cryptography\|import argon2" \
+      --include="*.py" | grep -v "crypto.py" | grep -v "test_" && exit 1 || true
+    # Verify inventory matches actual usage
+    python scripts/verify_crypto_inventory.py
+```
+
+**3. Add decorator-based crypto registration:**
+
+```python
+from functools import wraps
+
+_CRYPTO_REGISTRY = {}
+
+def crypto_operation(op_id, algorithm, purpose):
+    def decorator(func):
+        _CRYPTO_REGISTRY[op_id] = {
+            'algorithm': algorithm,
+            'purpose': purpose,
+            'function': func.__qualname__,
+            'module': func.__module__,
+        }
+        @wraps(func)
+        def wrapper(*args, **kwargs):
+            return func(*args, **kwargs)
+        return wrapper
+    return decorator
+
+@crypto_operation('CRYPT-001', 'Argon2id', 'key_derivation')
+def _hash(data: bytes, salt: bytes) -> bytes:
+    ...
+```
+
+---
+
+### MEDIUM SEVERITY
+
+#### ASVS-1113-MED-002: Argon2d (Type.D) Used in Production Instead of Recommended Argon2id
+
+**Severity:** MEDIUM  
+**Gap Type:** Undiscovered cryptographic configuration weakness
+
+**Location:**
+- **File:** `v3/steve/crypto.py`
+- **Function:** `_hash()`
+- **Line:** 79
+- **Benchmark reference:** Same file, `benchmark_argon2()`, line 119
+
+**Related Functions Checked:**
+- `gen_opened_key()` (calls `_hash()`)
+- `gen_vote_token()` (calls `_hash()`)
+- `benchmark_argon2()` (uses different variant)
+
+**Description:**
+
+The production code uses Argon2d (Type.D) while the benchmark code in the same file uses Argon2id (Type.ID). This inconsistency suggests the variant choice was not a deliberate security decision.
+
+**Vulnerable Code:**
+
+```python
+# Production code (line 79) - uses Argon2d
+def _hash(data: bytes, salt: bytes) -> bytes:
+    return argon2.low_level.hash_secret_raw(
+        secret=data,
+        salt=salt,
+        time_cost=2,
+        memory_cost=65536,
+        parallelism=4,
+        hash_len=32,
+        type=argon2.low_level.Type.D,  # ← Argon2d
+    )
+
+# Benchmark code (line 119) - uses Argon2id
+argon2.low_level.hash_secret_raw(
+    ...
+    type=argon2.low_level.Type.ID,  # ← Argon2id
+)
+```
+
+**Data Flow:**
+
+```
+Election data → _hash() [Argon2d] → opened_key (tamper detection)
+Voter/issue ID → _hash() [Argon2d] → vote_token (vote secrecy)
+```
+
+- `_hash()` is called by `gen_opened_key()` (tamper detection key) and `gen_vote_token()` (voter/issue token)
+- Both are security-critical: opened_key protects election integrity, vote_token protects vote secrecy
+
+**Proof of Concept:**
+
+Argon2d's data-dependent memory access patterns are vulnerable to cache-timing attacks. An attacker with local access could:
+
+1. Monitor CPU cache behavior during `_hash()` execution
+2. Extract information about the secret data being hashed
+3. Potentially reconstruct vote tokens or election keys
+
+**Impact:**
+
+Argon2d uses data-dependent memory access patterns, making it vulnerable to side-channel attacks (cache-timing). RFC 9106 and OWASP both recommend Argon2id which combines side-channel resistance (from Argon2i) with GPU/ASIC resistance (from Argon2d). 
+
+The inconsistency between production code (Type.D) and benchmark code (Type.ID) in the same file suggests this was not a deliberate security decision — exactly the type of issue a cryptographic inventory and discovery process would catch and prevent.
+
+**Remediation:**
+
+```python
+def _hash(data: bytes, salt: bytes) -> bytes:
+    return argon2.low_level.hash_secret_raw(
+        secret=data,
+        salt=salt,
+        time_cost=2,
+        memory_cost=65536,
+        parallelism=4,
+        hash_len=32,
+        type=argon2.low_level.Type.ID,  # Argon2id per RFC 9106
+    )
+```
+
+**⚠ Migration Note:** Changing the Argon2 variant will change all derived keys. This requires re-opening elections or a migration strategy for existing opened_key and vote_token values.
+
+---
+
+#### ASVS-1113-MED-003: HKDF Domain Separation Label Mismatches Actual Cipher
+
+**Severity:** MEDIUM  
+**Gap Type:** Undiscovered cryptographic configuration drift
+
+**Location:**
+- **File:** `v3/steve/crypto.py`
+- **Function:** `_b64_vote_key()`
+- **Lines:** 60-68
+
+**Related Functions Checked:**
+- `create_vote()` (uses Fernet, not XChaCha20)
+- `decrypt_votestring()` (uses Fernet, not XChaCha20)
+
+**Description:**
+
+The HKDF `info` parameter is set to `b'xchacha20_key'` but the actual cipher in use is Fernet (AES-128-CBC + HMAC-SHA256). This creates a mismatch between the domain separation label and the actual cryptographic usage.
+
+**Vulnerable Code:**
+
+```python
+def _b64_vote_key(vote_token: bytes, salt: bytes) -> bytes:
+    "Key-stretch the vote_token. (ref: PBKDF)"
+
+    ### still using Fernet now, but will switch soon. Leaving comments.
+    keymaker = hkdf.HKDF(
+        algorithm=hashes.SHA256(),
+        length=32,  # 32-byte key for XChaCha20-Poly1305  ← WRONG: used for Fernet
+        salt=salt,
+        info=b'xchacha20_key',  # ← Domain separation label does not match actual cipher
+    )
+    vote_key = keymaker.derive(vote_token)
+    return base64.urlsafe_b64encode(vote_key)
+```
+
+**Data Flow:**
+
+```
+vote_token → HKDF [info=b'xchacha20_key'] → vote_key → Fernet encryption
+                                                          (NOT XChaCha20)
+```
+
+**Proof of Concept:**
+
+If XChaCha20-Poly1305 is later added with the same HKDF `info` label:
+
+```python
+# Future code using same info parameter
+vote_key_xchacha = hkdf.derive(vote_token)  # info=b'xchacha20_key'
+vote_key_fernet = hkdf.derive(vote_token)   # info=b'xchacha20_key'
+
+# Result: Both ciphers would use IDENTICAL keys from same token
+# This violates cryptographic key separation principles
+```
+
+**Impact:**
+
+The HKDF `info` parameter provides domain separation — it ensures keys derived for different purposes are cryptographically independent. Using `b'xchacha20_key'` when the actual cipher is Fernet (AES-128-CBC + HMAC-SHA256) creates two risks:
+
+1. **Future collision risk:** If XChaCha20-Poly1305 is later added (as comments suggest), and HKDF is used with the same `info` label, both ciphers would derive identical keys from the same token — violating key separation.
+2. **Audit confusion:** The code self-documents an algorithm (XChaCha20) that is not the one in use, making security review unreliable without a separate crypto inventory to cross-reference.
+
+This is direct evidence that a cryptographic discovery and inventory mechanism would catch configuration drift between intended and actual cryptographic usage.
+
+**Remediation:**
+
+```python
+def _b64_vote_key(vote_token: bytes, salt: bytes) -> bytes:
+    keymaker = hkdf.HKDF(
+        algorithm=hashes.SHA256(),
+        length=32,  # Fernet key: 16 bytes signing + 16 bytes encryption
+        salt=salt,
+        info=b'fernet_vote_key_v1',  # Matches actual cipher in use
+    )
+    vote_key = keymaker.derive(vote_token)
+    return base64.urlsafe_b64encode(vote_key)
+```
+
+**⚠ Migration Note:** Changing the `info` parameter will change all derived vote encryption keys. Existing encrypted votes will become undecryptable. This change requires a coordinated migration.
+
+---
+
+#### ASVS-1113-MED-004: Non-Constant-Time Comparison in Tamper Detection
+
+**Severity:** MEDIUM  
+**Gap Type:** Type C — Cryptographic control called but result comparison is insecure
+
+**Location:**
+- **File:** `v3/steve/election.py`
+- **Function:** `is_tampered()`
+- **Line:** 264
+
+**Related Functions Checked:**
+- `_all_metadata()` (provides stored opened_key)
+- `gather_election_data()` (provides current election data)
+- `crypto.gen_opened_key()` (computes fresh opened_key)
+
+**Description:**
+
+The tamper detection function uses Python's `!=` operator to compare cryptographic keys. This operator is not constant-time and leaks timing information about the position of the first differing byte.
+
+**Vulnerable Code:**
+
+```python
+def is_tampered(self, pdb):
+    md = self._all_metadata()
+    edata = self.gather_election_data(pdb)
+    opened_key = crypto.gen_opened_key(edata, md.salt)
+
+    # The computed key should be unchanged.
+    return opened_key != md.opened_key  # ← Python != is NOT constant-time
+```
+
+**Data Flow:**
+
+```
+gather_election_data() → crypto.gen_opened_key() → Python != comparison → timing leak
+                                                     ↓
+                                                  md.opened_key
+```
+
+**Proof of Concept:**
+
+```python
+import time
+
+# Attacker submits controlled modifications
+for byte_position in range(32):
+    for byte_value in range(256):
+        tampered_data = modify_election_data_at(byte_position, byte_value)
+        
+        start = time.perf_counter()
+        result = election.is_tampered(pdb)
+        elapsed = time.perf_counter() - start
+        
+        # Timing differences reveal position of first mismatch
+        # Allows incremental reconstruction of opened_key
+```
+
+**Impact:**
+
+Python's `!=` operator on `bytes` objects short-circuits at the first differing byte, creating a timing side-channel. While the Argon2 computation dominates execution time (reducing practical exploitability), an attacker with local access could potentially:
+
+1. Submit controlled modifications to election data
+2. Measure response time differences across multiple tamper checks
+3. Incrementally reconstruct the stored `opened_key`
+
+**Mitigating factors:** Currently only called from CLI (`tally.py`), where timing measurement is harder. However, if tamper checks are added to web handlers (which would be the correct security improvement), this becomes a network-observable timing oracle.
+
+**Remediation:**
+
+```python
+import hmac
+
+def is_tampered(self, pdb):
+    md = self._all_metadata()
+    edata = self.gather_election_data(pdb)
+    opened_key = crypto.gen_opened_key(edata, md.salt)
+
+    # Constant-time comparison prevents timing side-channels
+    return not hmac.compare_digest(opened_key, md.opened_key)
+```
+
+---
+
+## Positive Security Patterns
+
+1. **Centralized Cryptography Module:** All cryptographic operations are concentrated in `crypto.py`, creating a single source of truth for crypto logic. This is excellent architectural practice and makes discovery easier (though not automated).
+
+2. **CSPRNG Throughout:** The system consistently uses the `secrets` module for all random value generation (salts, IDs, shuffling), avoiding `random` module pitfalls.
+
+3. **Per-Voter Salts:** Each `mayvote` entry gets a unique 16-byte salt via `crypto.gen_salt()`, preventing vote correlation even with database access.
+
+4. **Memory-Hard KDF:** Argon2 with 64MB memory cost and 2 passes provides strong resistance against brute-force attacks on vote tokens and election keys.
+
+5. **Cryptographic Shuffling:** Vote ordering is randomized via Fisher-Yates with `secrets.randbelow()` before tallying, preventing database-order information leakage.
+
+6. **Tamper Detection Architecture:** The `opened_key` mechanism captures a cryptographic snapshot of election state at opening time, enabling detection of post-open modifications.
+
+7. **Schema-Level Crypto Constraints:** The database schema enforces exact byte lengths for cryptographic fields (`salt` CHECK 16 bytes, `opened_key` CHECK 32 bytes, `vote_token` CHECK 32 bytes).
+
+---
+
+## Architecture Observations
+
+1. **Crypto Centralization is Strong but Unverified:** The `crypto.py` module is well-designed as a single crypto module, but without discovery tooling, there's no guarantee that future changes won't introduce crypto operations in other modules.
+
+2. **Planned Algorithm Migration Stalled:** The HKDF `info=b'xchacha20_key'` and comments indicate a planned migration from Fernet to XChaCha20-Poly1305 that has not been executed. A crypto inventory would track this as a pending migration task.
+
+3. **Key Derivation Chain Depth:** The system uses a multi-layer derivation chain (election data → BLAKE2b → Argon2 → opened_key → Argon2 → vote_token → HKDF → Fernet key). This is well-designed but complex enough to require formal documentation for audit purposes.
+
+4. **PQC Readiness:** The current primitives (AES-128, SHA-256, Argon2) have varying quantum resistance. AES-128 is reduced to ~64-bit security under Grover's algorithm. A crypto inventory should track PQC migration readiness for each primitive.
+
+---
+
+## Recommendations Summary
+
+### Immediate Actions (0-30 days)
+
+| Priority | Action | Effort | Impact | Addresses Finding |
+|----------|--------|--------|--------|-------------------|
+| 1 | Create a formal cryptographic inventory manifest documenting all primitives, parameters, locations, and approval status | Low | High | ASVS-1113-HIGH-001 |
+| 2 | Replace `!=` with `hmac.compare_digest()` in `is_tampered()` | Low | Medium | ASVS-1113-MED-004 |
+
+### Short-Term Actions (1-3 months)
+
+| Priority | Action | Effort | Impact | Addresses Finding |
+|----------|--------|--------|--------|-------------------|
+| 3 | Add CI/CD crypto scanning to detect new crypto usage outside `crypto.py` | Medium | High | ASVS-1113-HIGH-001 |
+| 4 | Change `_hash()` from Argon2d (Type.D) to Argon2id (Type.ID) per RFC 9106 | Low (code), High (migration) | Medium | ASVS-1113-MED-002 |
+| 5 | Correct HKDF `info` parameter to match actual cipher | Low (code), High (migration) | Medium | ASVS-1113-MED-003 |
+
+### Long-Term Actions (3-12 months)
+
+| Priority | Action | Effort | Impact | Addresses Finding |
+|----------|--------|--------|--------|-------------------|
+| 6 | Document PQC migration plan for AES-128 → AES-256 and assess Argon2/BLAKE2b quantum impact | Medium | High | ASVS-1113-HIGH-001 (future-proofing) |
+| 7 | Implement decorator-based crypto registration system | Medium | Medium | ASVS-1113-HIGH-001 |
+| 8 | Conduct quarterly cryptographic inventory reviews | Low | Medium | ASVS-1113-HIGH-001 (ongoing) |
+
+---
+
+## Appendix: Files Analyzed
+
+<details>
+<summary>Click to expand file list (4 files)</summary>
+
+1. **`v3/schema.sql`** (Relevance: 10/10)
+   - Database schema defining cryptographic field constraints
+   - Enforces byte lengths for salts, keys, and tokens
+
+2. **`v3/server/bin/tally.py`** (Relevance: 10/10)
+   - CLI entry point for election tallying
+   - Calls `is_tampered()` with non-constant-time comparison
+
+3. **`v3/steve/crypto.py`** (Relevance: 10/10)
+   - Central cryptographic module
+   - Contains all primitive implementations
+   - Source of Argon2d/HKDF configuration issues
+
+4. **`v3/steve/election.py`** (Relevance: 10/10)
+   - Election business logic
+   - All cryptographic usage points
+   - Contains non-constant-time comparison vulnerability
+
+</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