Files
trace/trace/crypto.py
Claude 2453bd4f2a Fix critical bugs and improve data integrity across codebase
This commit addresses 20 bugs discovered during comprehensive code review,
focusing on data integrity, concurrent access, and user experience.

CRITICAL FIXES:
- Fix GPG key listing to support keys with multiple UIDs (crypto.py:40)
- Implement cross-platform file locking to prevent concurrent access corruption (storage.py)
- Fix evidence detail delete logic that could delete wrong note (tui.py:2481-2497)
- Add corrupted JSON handling with user prompt and automatic backup (storage.py, tui.py)

DATA INTEGRITY:
- Fix IOC/Hash pattern false positives by checking longest hashes first (models.py:32-95)
- Fix URL pattern to exclude trailing punctuation (models.py:81, 152, 216)
- Improve IOC overlap detection with proper range tracking (models.py)
- Fix note deletion to use note_id instead of object identity (tui.py:2498-2619)
- Add state validation to detect and clear orphaned references (storage.py:355-384)

SCROLLING & NAVIGATION:
- Fix evidence detail view to support full scrolling instead of "last N" (tui.py:816-847)
- Fix filter reset index bounds bug (tui.py:1581-1654)
- Add scroll_offset validation after all operations (tui.py:1608-1654)
- Fix division by zero in scroll calculations (tui.py:446-478)
- Validate selection bounds across all views (tui.py:_validate_selection_bounds)

EXPORT & CLI:
- Fix multi-line note export with proper markdown indentation (cli.py:129-143)
- Add stderr warnings for GPG signature failures (cli.py:61, 63)
- Validate active context and show warnings in CLI (cli.py:12-44)

TESTING:
- Update tests to support new lock file mechanism (test_models.py)
- All existing tests pass with new changes

Breaking changes: None
Backward compatible: Yes (existing data files work unchanged)
2025-12-13 16:16:54 +00:00

89 lines
2.8 KiB
Python

import subprocess
import hashlib
class Crypto:
@staticmethod
def list_gpg_keys():
"""
List available GPG secret keys.
Returns a list of tuples: (key_id, user_id)
"""
try:
proc = subprocess.Popen(
['gpg', '--list-secret-keys', '--with-colons'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
stdout, stderr = proc.communicate(timeout=10)
if proc.returncode != 0:
return []
keys = []
current_key_id = None
for line in stdout.split('\n'):
fields = line.split(':')
if len(fields) < 2:
continue
# sec = secret key
if fields[0] == 'sec':
# Key ID is in field 4 (short) or we can extract from field 5 (fingerprint)
current_key_id = fields[4] if len(fields) > 4 else None
# uid = user ID
elif fields[0] == 'uid' and current_key_id:
user_id = fields[9] if len(fields) > 9 else "Unknown"
keys.append((current_key_id, user_id))
# Don't reset current_key_id - allow multiple UIDs per key
return keys
except (FileNotFoundError, subprocess.TimeoutExpired):
return [] # GPG not installed or timed out
@staticmethod
def sign_content(content: str, key_id: str = None) -> str:
"""
Signs the content using GPG.
Args:
content: The content to sign
key_id: Optional GPG key ID to use. If None, uses default key.
Returns:
The clearsigned content or empty string if GPG fails.
"""
try:
# Build command
cmd = ['gpg', '--clearsign', '--output', '-']
# Add specific key if provided
if key_id:
cmd.extend(['--local-user', key_id])
proc = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
stdout, stderr = proc.communicate(input=content, timeout=10)
if proc.returncode != 0:
# Fallback: maybe no key is found or gpg error
# In a real app we might want to log this 'stderr'
return ""
return stdout
except (FileNotFoundError, subprocess.TimeoutExpired):
return "" # GPG not installed or timed out
@staticmethod
def hash_content(content: str, timestamp: float) -> str:
data = f"{timestamp}:{content}".encode('utf-8')
return hashlib.sha256(data).hexdigest()