Fix: Security, reliability, and code quality improvements from PR review

Critical Security Fixes:
- Fix command injection vulnerability in Windows shims (beadboard.cmd, bb.cmd)
  - Added path validation to block traversal (.. and root-relative paths)
  - Added quotes around env var to prevent command injection

Reliability Fixes:
- Fix agent cache null safety bug
  - Fixed callBdAgentShow() to check for cache misses (null check, expiration)
  - Fixed getCachedAgent to properly return entry.data or null
- Fix null body crashes in mail ack route
  - Added null check before casting body to object
  - Returns 400 error instead of 500 for invalid requests

BD Compliance Fixes:
- Fix read-issues to use BD audit record path
  - Ensures all writes go through bd audit record
  - Maintains watcher/SSE parity and Dolt commit tracking

Code Quality Fixes:
- Fix path canonicalization violations
  - Use canonicalizeWindowsPath() and windowsPathKey() from pathing module
  - Prevents Windows edge cases and ensures machine-reproducible paths
- Fix typo: mobile-fronted → mobile-frontend
- Pin GitHub Actions tags
  - softprops/action-gh-release@v1 → specific commit hash
- Register pr14 test in package.json (already registered)

Testing:
- Refactor broad exception handlers in Python scripts
  - Replace except Exception: with specific exceptions
  - Allows KeyboardInterrupt and SystemExit to propagate correctly
  - All tests passing
This commit is contained in:
zenchantlive 2026-03-05 16:33:10 -08:00
parent d54e4f3311
commit ce4700849b
15 changed files with 2995 additions and 756 deletions

View file

@ -12,54 +12,54 @@ import re
def read_chunk(chunk_id: str, chunk_store) -> Optional[Dict[str, Any]]:
"""
Read a chunk by ID.
Args:
chunk_id: The chunk ID to read
chunk_store: ChunkStore instance
Returns:
Chunk data dict or None if not found
"""
# Validate chunk_id format - reject path traversal attempts
if chunk_id is None:
return None
# Check for path traversal patterns
if '..' in chunk_id or '/' in chunk_id or '\\' in chunk_id:
if ".." in chunk_id or "/" in chunk_id or "\\" in chunk_id:
return None
# Only allow alphanumeric, hyphens, and underscores
if not re.match(r'^[a-zA-Z0-9_-]+$', chunk_id):
if not re.match(r"^[a-zA-Z0-9_-]+$", chunk_id):
return None
try:
chunk = chunk_store.get_chunk(chunk_id)
if chunk is None:
return None
# Convert Chunk dataclass to dict
return {
'id': chunk.id,
'content': chunk.content,
'tokens': chunk.tokens,
'type': chunk.type,
'metadata': chunk.metadata,
'links': chunk.links,
'tags': chunk.tags,
"id": chunk.id,
"content": chunk.content,
"tokens": chunk.tokens,
"type": chunk.type,
"metadata": chunk.metadata,
"links": chunk.links,
"tags": chunk.tags,
}
except Exception:
except (AttributeError, TypeError, KeyError, ValueError):
return None
def search_chunks(query: str, chunk_store, limit: int = 10) -> List[str]:
"""
Search for chunks matching query.
Args:
query: Search query string
chunk_store: ChunkStore instance
limit: Maximum results to return
Returns:
List of matching chunk IDs
"""
@ -68,37 +68,37 @@ def search_chunks(query: str, chunk_store, limit: int = 10) -> List[str]:
# In production, this could use embeddings or more sophisticated search
query_lower = query.lower()
words = set(query_lower.split())
all_chunks = chunk_store.list_chunks()
results = []
for chunk_id in all_chunks:
chunk = chunk_store.get_chunk(chunk_id)
if chunk is None:
continue
content_lower = chunk.content.lower()
# Check if any query word appears in content
if any(word in content_lower for word in words):
results.append(chunk_id)
if len(results) >= limit:
break
return results
except Exception:
except (AttributeError, TypeError, KeyError, ValueError):
return []
def list_chunks_by_tag(tags, chunk_store) -> List[str]:
"""
List all chunks with given tag(s).
Args:
tags: Single tag string or list of tags to search for
chunk_store: ChunkStore instance
Returns:
List of chunk IDs with the tag(s)
"""
@ -109,19 +109,21 @@ def list_chunks_by_tag(tags, chunk_store) -> List[str]:
elif isinstance(tags, list):
return chunk_store.list_chunks(tags=tags)
return []
except Exception:
except (AttributeError, TypeError, KeyError, ValueError):
return []
def get_linked_chunks(chunk_id: str, chunk_store, link_type: Optional[str] = None) -> List[Dict[str, Any]]:
def get_linked_chunks(
chunk_id: str, chunk_store, link_type: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
Get chunks linked to the given chunk.
Args:
chunk_id: Source chunk ID
chunk_store: ChunkStore instance
link_type: Optional link type filter (e.g., 'context_of', 'follows', 'related_to')
Returns:
List of linked chunk data dicts
"""
@ -129,22 +131,22 @@ def get_linked_chunks(chunk_id: str, chunk_store, link_type: Optional[str] = Non
chunk = chunk_store.get_chunk(chunk_id)
if chunk is None:
return []
linked = []
for link in chunk.links:
# Filter by link type if specified
if link_type and link.get('type') != link_type:
if link_type and link.get("type") != link_type:
continue
target_id = link.get('target_id')
target_id = link.get("target_id")
if target_id:
target_chunk = read_chunk(target_id, chunk_store)
if target_chunk:
# Include link metadata
target_chunk['_link_type'] = link.get('type', 'unknown')
target_chunk['_link_strength'] = link.get('strength', 0.5)
target_chunk["_link_type"] = link.get("type", "unknown")
target_chunk["_link_strength"] = link.get("strength", 0.5)
linked.append(target_chunk)
return linked
except Exception:
except (AttributeError, TypeError, KeyError, ValueError):
return []