Replace shell=True with list-based subprocess execution to prevent command injection via malicious user input. Changes: - tools/transcription_tools.py: Use shlex.split() + shell=False - tools/environments/docker.py: List-based commands with container ID validation Fixes CVE-level vulnerability where malicious file paths or container IDs could inject arbitrary commands. CVSS: 9.8 (Critical) Refs: V-001 in SECURITY_AUDIT_REPORT.md
10 KiB
SECURITY FIXES CHECKLIST
20+ Specific Security Fixes Required
This document provides a detailed checklist of all security fixes identified in the comprehensive audit.
CRITICAL FIXES (Must implement immediately)
Fix 1: Remove shell=True from subprocess calls
File: tools/terminal_tool.py
Line: ~457
CVSS: 9.8
# BEFORE
subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ...)
# AFTER
# Validate command first
if not is_safe_command(exec_command):
raise SecurityError("Dangerous command detected")
subprocess.Popen(cmd_list, shell=False, ...) # Pass as list
Fix 2: Implement path sandbox validation
File: tools/file_operations.py
Lines: 409-420
CVSS: 9.1
# BEFORE
def _expand_path(self, path: str) -> str:
if path.startswith('~'):
return os.path.expanduser(path)
return path
# AFTER
def _expand_path(self, path: str) -> Path:
safe_root = Path(self.cwd).resolve()
expanded = Path(path).expanduser().resolve()
if not str(expanded).startswith(str(safe_root)):
raise PermissionError(f"Path {path} outside allowed directory")
return expanded
Fix 3: Environment variable sanitization
File: tools/code_execution_tool.py
Lines: 434-461
CVSS: 9.3
# BEFORE
_SAFE_ENV_PREFIXES = ("PATH", "HOME", "USER", ...)
_SECRET_SUBSTRINGS = ("TOKEN", "SECRET", ...)
# AFTER
_ALLOWED_ENV_VARS = frozenset([
"PATH", "HOME", "USER", "LANG", "LC_ALL",
"TERM", "SHELL", "PWD", "PYTHONPATH"
])
child_env = {k: v for k, v in os.environ.items()
if k in _ALLOWED_ENV_VARS}
Fix 4: Secure sudo password handling
File: tools/terminal_tool.py
Line: 275
CVSS: 9.0
# BEFORE
exec_command = f"printf '%s\\n' {shlex.quote(sudo_stdin.rstrip())} | {exec_command}"
# AFTER
# Use file descriptor passing instead of command line
with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
f.write(sudo_stdin)
pass_file = f.name
os.chmod(pass_file, 0o600)
exec_command = f"cat {pass_file} | {exec_command}"
# Clean up after execution
Fix 5: Connection-level URL validation
File: tools/url_safety.py
Lines: 50-96
CVSS: 9.4
# AFTER - Add to is_safe_url()
# After DNS resolution, verify IP is not in private range
def _validate_connection_ip(hostname: str) -> bool:
try:
addr = socket.getaddrinfo(hostname, None)
for a in addr:
ip = ipaddress.ip_address(a[4][0])
if ip.is_private or ip.is_loopback or ip.is_reserved:
return False
return True
except:
return False
HIGH PRIORITY FIXES
Fix 6: MCP OAuth token validation
File: tools/mcp_oauth.py
Lines: 66-89
CVSS: 8.8
# AFTER
async def get_tokens(self):
data = self._read_json(self._tokens_path())
if not data:
return None
# Add schema validation
if not self._validate_token_schema(data):
logger.error("Invalid token schema, deleting corrupted tokens")
self.remove()
return None
return OAuthToken(**data)
Fix 7: API Server SQL injection prevention
File: gateway/platforms/api_server.py
Lines: 98-126
CVSS: 8.5
# AFTER
import uuid
def _validate_response_id(self, response_id: str) -> bool:
"""Validate response_id format to prevent injection."""
try:
uuid.UUID(response_id.split('-')[0], version=4)
return True
except (ValueError, IndexError):
return False
Fix 8: CORS strict validation
File: gateway/platforms/api_server.py
Lines: 324-328
CVSS: 8.2
# AFTER
if "*" in self._cors_origins:
logger.error("Wildcard CORS not allowed with credentials")
return None # Reject wildcard with credentials
Fix 9: Require explicit API key
File: gateway/platforms/api_server.py
Lines: 360-361
CVSS: 8.1
# AFTER
if not self._api_key:
logger.error("API server started without authentication")
return web.json_response(
{"error": "Server authentication not configured"},
status=500
)
Fix 10: CDP URL validation
File: tools/browser_tool.py
Lines: 195-208
CVSS: 8.4
# AFTER
def _resolve_cdp_override(self, cdp_url: str) -> str:
parsed = urlparse(cdp_url)
if parsed.scheme not in ('ws', 'wss', 'http', 'https'):
raise ValueError("Invalid CDP scheme")
if parsed.hostname not in self._allowed_cdp_hosts:
raise ValueError("CDP host not in allowlist")
return cdp_url
Fix 11: Skills guard normalization
File: tools/skills_guard.py
Lines: 82-484
CVSS: 7.8
# AFTER - Add to scan_skill()
def normalize_for_scanning(content: str) -> str:
"""Normalize content to detect obfuscated threats."""
# Normalize Unicode
content = unicodedata.normalize('NFKC', content)
# Normalize case
content = content.lower()
# Remove common obfuscation
content = content.replace('\\x', '')
content = content.replace('\\u', '')
return content
Fix 12: Docker volume validation
File: tools/environments/docker.py
Line: 267
CVSS: 8.7
# AFTER
_BLOCKED_PATHS = ['/var/run/docker.sock', '/proc', '/sys', '/dev']
for vol in volumes:
if any(blocked in vol for blocked in _BLOCKED_PATHS):
raise SecurityError(f"Volume mount {vol} blocked")
volume_args.extend(["-v", vol])
Fix 13: Secure error messages
File: Multiple files
CVSS: 7.5
# AFTER - Add to all exception handlers
try:
operation()
except Exception as e:
logger.error(f"Error: {e}", exc_info=True) # Full details for logs
raise UserError("Operation failed") # Generic for user
Fix 14: OAuth state validation
File: tools/mcp_oauth.py
Line: 186
CVSS: 7.6
# AFTER
code, state = await _wait_for_callback()
stored_state = storage.get_state()
if not hmac.compare_digest(state, stored_state):
raise SecurityError("OAuth state mismatch - possible CSRF")
Fix 15: File operation race condition fix
File: tools/file_operations.py
CVSS: 7.4
# AFTER
import fcntl
def safe_file_access(path: Path):
fd = os.open(path, os.O_RDONLY)
try:
fcntl.flock(fd, fcntl.LOCK_SH)
# Perform operations on fd, not path
return os.read(fd, size)
finally:
fcntl.flock(fd, fcntl.LOCK_UN)
os.close(fd)
Fix 16: Add rate limiting
File: gateway/platforms/api_server.py
CVSS: 7.3
# AFTER - Add middleware
from aiohttp_limiter import Limiter
limiter = Limiter(
rate=100, # requests
per=60, # per minute
key_func=lambda req: req.remote
)
@app.middleware
async def rate_limit_middleware(request, handler):
if not limiter.is_allowed(request):
return web.json_response(
{"error": "Rate limit exceeded"},
status=429
)
return await handler(request)
Fix 17: Secure temp file creation
File: tools/code_execution_tool.py
Line: 388
CVSS: 7.2
# AFTER
import tempfile
import os
fd, tmpdir = tempfile.mkstemp(prefix="hermes_sandbox_", suffix=".tmp")
os.chmod(tmpdir, 0o700) # Owner only
os.close(fd)
# Use tmpdir securely
MEDIUM PRIORITY FIXES
Fix 18: Expand dangerous patterns
File: tools/approval.py
Lines: 40-78
CVSS: 6.5
Add patterns:
(r'\bcurl\s+.*\|\s*sh\b', "pipe remote content to shell"),
(r'\bwget\s+.*\|\s*bash\b', "pipe remote content to shell"),
(r'python\s+-c\s+.*import\s+os', "python os import"),
(r'perl\s+-e\s+.*system', "perl system call"),
Fix 19: Credential file permissions
File: tools/credential_files.py, tools/mcp_oauth.py
CVSS: 6.4
# AFTER
def _write_json(path: Path, data: dict) -> None:
path.write_text(json.dumps(data, indent=2), encoding="utf-8")
path.chmod(0o600)
# Verify permissions were set
stat = path.stat()
if stat.st_mode & 0o077:
raise SecurityError("Failed to set restrictive permissions")
Fix 20: Log sanitization
File: Multiple logging statements
CVSS: 5.8
# AFTER
from agent.redact import redact_sensitive_text
# In all logging calls
logger.info(redact_sensitive_text(f"Processing {user_input}"))
ADDITIONAL FIXES (21-32)
Fix 21: XXE Prevention
File: PowerPoint XML processing
Add:
from defusedxml import ElementTree as ET
# Use defusedxml instead of standard xml
Fix 22: YAML Safe Loading Audit
File: hermes_cli/config.py
Audit all yaml.safe_load calls for custom constructors.
Fix 23: Prototype Pollution Fix
File: scripts/whatsapp-bridge/bridge.js
Use Map instead of Object for user-controlled keys.
Fix 24: Subagent Isolation
File: tools/delegate_tool.py
Implement filesystem namespace isolation.
Fix 25: Secure Session IDs
File: gateway/session.py
Use secrets.token_urlsafe(32) instead of uuid4.
Fix 26: Binary Integrity Checks
File: tools/tirith_security.py
Require GPG signature verification.
Fix 27: Debug Output Redaction
File: tools/debug_helpers.py
Apply redact_sensitive_text to all debug output.
Fix 28: Security Headers
File: gateway/platforms/api_server.py
Add:
"Content-Security-Policy": "default-src 'self'",
"Strict-Transport-Security": "max-age=31536000",
Fix 29: Version Information Minimization
File: Version endpoints
Return minimal version information publicly.
Fix 30: Dead Code Removal
File: Multiple
Remove unused imports and functions.
Fix 31: Token Encryption at Rest
File: hermes_cli/auth.py
Use OS keychain or encrypt auth.json.
Fix 32: Input Length Validation
File: All tool entry points
Add MAX_INPUT_LENGTH checks everywhere.
IMPLEMENTATION VERIFICATION
Testing Requirements
- All fixes have unit tests
- Security regression tests pass
- Fuzzing shows no new vulnerabilities
- Penetration test completed
- Code review by security team
Sign-off Required
- Security Team Lead
- Engineering Manager
- QA Lead
- DevOps Lead
Last Updated: March 30, 2026
Next Review: After all P0/P1 fixes completed