This document outlines security best practices for the Research-Agent project, covering input validation, credential management, API security, and deployment considerations.
Implementation: src/validators.py
def validate_topic(topic: str) -> str:
"""Validate and sanitize research topic."""
# Remove dangerous characters
topic = re.sub(r'[<>"\']', '', topic)
# Length limits
if len(topic) < 3:
raise ValueError("Topic too short")
if len(topic) > 500:
raise ValueError("Topic too long")
return topic.strip()Protections:
- ✅ XSS prevention (removes
<script>,<iframe>) - ✅ SQL injection prevention (parameterized queries)
- ✅ Length limits (3-500 characters)
- ✅ Special character filtering
Restrictions:
# Allowed extensions
ALLOWED_EXTENSIONS = {'.pdf', '.txt', '.md'}
# Size limit
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
# Path traversal prevention
safe_path = os.path.abspath(file_path)
if not safe_path.startswith(os.path.abspath(upload_dir)):
raise ValueError("Invalid file path")Validation Function:
def validate_file_upload(filename: str, file_size: int) -> tuple[bool, str]:
"""Validate uploaded file."""
# Check extension
ext = Path(filename).suffix.lower()
if ext not in ALLOWED_EXTENSIONS:
return False, f"File type {ext} not allowed"
# Check size
if file_size > MAX_FILE_SIZE:
return False, "File too large"
# Sanitize filename
safe_name = "".join(c for c in filename if c.isalnum() or c in '._- ')
if not safe_name or safe_name.startswith('.'):
return False, "Invalid filename"
return True, ""NEVER commit credentials to git:
# .gitignore (verify these are present)
.env
*.key
*.pem
secrets/
*.env.localUse .env file:
# .env (NEVER commit this file)
OLLAMA_BASE_URL=http://localhost:11434
TAVILY_API_KEY=your_key_here
GITHUB_TOKEN=your_token_here
EMAIL_PASSWORD=your_app_password_hereProvide template:
# env.example (commit this)
OLLAMA_BASE_URL=http://localhost:11434
TAVILY_API_KEY=your_tavily_key
GITHUB_TOKEN=your_github_token
EMAIL_PASSWORD=your_email_app_passwordBest Practices:
- Rotate keys every 90 days
- Use separate keys for dev/staging/prod
- Revoke immediately if exposed
- Monitor API usage for anomalies
Key Sources:
- Tavily: https://tavily.com/
- GitHub: https://github.com/settings/tokens
- Gmail App Password: https://myaccount.google.com/apppasswords
- YouTube: https://console.cloud.google.com/
Prevent credential commits:
#!/bin/bash
# .git/hooks/pre-commit
if git diff --cached --name-only | grep -q "\.env$"; then
echo "❌ Error: Attempting to commit .env file"
exit 1
fi
if git diff --cached | grep -qE "(api_key|password|token|secret).*=.*[a-zA-Z0-9]{20,}"; then
echo "❌ Error: Possible credential in commit"
exit 1
fiAlways use parameterized queries:
# ✅ CORRECT
cursor.execute('SELECT * FROM sessions WHERE id = ?', (session_id,))
# ❌ WRONG
cursor.execute(f'SELECT * FROM sessions WHERE id = {session_id}')def save_session(topic: str, persona: str, state: dict):
"""Save session with sanitized inputs."""
# Sanitize inputs
topic = validate_topic(topic)
persona = persona if persona in ALLOWED_PERSONAS else "general"
# Serialize safely
state_json = json.dumps(state, default=str)
# Parameterized insert
cursor.execute(
'INSERT INTO sessions (topic, persona, state_json) VALUES (?, ?, ?)',
(topic, persona, state_json)
)# Restrict database file permissions
chmod 600 research_sessions.db
# In production, use separate DB user with limited privilegesImplementation with slowapi:
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@limiter.limit("10/minute")
def research_endpoint(request):
"""Rate-limited research endpoint."""
# ... research logicOptions:
- API Keys: Simple, good for service-to-service
- JWT Tokens: Stateless, scalable
- OAuth2: For user authentication
Example API Key Implementation:
def verify_api_key(api_key: str) -> bool:
"""Verify API key from request header."""
valid_keys = os.getenv("VALID_API_KEYS", "").split(",")
return api_key in valid_keys
@app.before_request
def check_auth():
api_key = request.headers.get("X-API-Key")
if not verify_api_key(api_key):
abort(401, "Invalid API key")from flask_cors import CORS
# Restrict origins in production
CORS(app, origins=[
"https://yourdomain.com",
"https://app.yourdomain.com"
])Remove potentially harmful content:
def sanitize_content(content: str, max_length: int = 50000) -> str:
"""Sanitize research content."""
# Remove scripts
content = re.sub(r'<script.*?</script>', '', content, flags=re.IGNORECASE | re.DOTALL)
# Remove javascript: URLs
content = re.sub(r'javascript:', '', content, flags=re.IGNORECASE)
# Truncate if too long
if len(content) > max_length:
content = content[:max_length] + "..."
return content.strip()Validate and sanitize user inputs before LLM:
def safe_llm_prompt(user_input: str) -> str:
"""Create safe prompt from user input."""
# Remove prompt injection attempts
dangerous_patterns = [
r'ignore previous instructions',
r'disregard.*above',
r'new instructions:',
]
for pattern in dangerous_patterns:
if re.search(pattern, user_input, re.IGNORECASE):
raise ValueError("Invalid input detected")
return user_inputDockerfile best practices:
# Use specific versions, not 'latest'
FROM python:3.10-slim
# Run as non-root user
RUN useradd -m -u 1000 appuser
USER appuser
# Don't expose unnecessary ports
EXPOSE 8501
# Use secrets for sensitive data
RUN --mount=type=secret,id=env_file \
cat /run/secrets/env_file > .envDocker Compose:
services:
research-agent:
# Resource limits
deploy:
resources:
limits:
cpus: '2'
memory: 4G
# Read-only root filesystem
read_only: true
# Drop capabilities
cap_drop:
- ALL
# Use secrets
secrets:
- env_file
secrets:
env_file:
file: .envFirewall rules:
# Allow only necessary ports
ufw allow 8501/tcp # Streamlit
ufw allow 11434/tcp # Ollama (if external)
ufw enableUse internal networks:
# docker-compose.yml
networks:
internal:
internal: true # No external access
services:
research-agent:
networks:
- internal
- default # External accessUse reverse proxy (nginx):
server {
listen 443 ssl http2;
server_name research.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:8501;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}import logging
security_logger = logging.getLogger('security')
def log_security_event(event_type: str, details: dict):
"""Log security-relevant events."""
security_logger.warning(
f"Security Event: {event_type}",
extra={
'event_type': event_type,
'timestamp': datetime.now().isoformat(),
**details
}
)
# Usage
log_security_event('invalid_input', {
'user_ip': request.remote_addr,
'input': sanitized_input
})from collections import defaultdict
from datetime import datetime, timedelta
failed_attempts = defaultdict(list)
def check_rate_limit(ip_address: str) -> bool:
"""Check if IP has too many failed attempts."""
now = datetime.now()
cutoff = now - timedelta(minutes=15)
# Clean old attempts
failed_attempts[ip_address] = [
t for t in failed_attempts[ip_address] if t > cutoff
]
# Check limit
if len(failed_attempts[ip_address]) >= 5:
log_security_event('rate_limit_exceeded', {'ip': ip_address})
return False
return True- Never commit
.envfiles - Use parameterized SQL queries
- Validate all user inputs
- Sanitize file uploads
- Use environment variables for secrets
- Add pre-commit hooks
- Test XSS prevention
- Test SQL injection prevention
- Test file upload restrictions
- Test rate limiting
- Test authentication (if implemented)
- Use HTTPS/TLS
- Configure firewall rules
- Set resource limits
- Run as non-root user
- Enable security logging
- Set up monitoring/alerts
- Rotate API keys quarterly
- Review security logs weekly
- Update dependencies monthly
- Security audit annually
- Incident response plan documented
-
Immediate Actions:
# Revoke exposed credentials # Generate new keys # Update .env file # Restart services
-
Git History Cleanup:
# Remove from history git filter-branch --force --index-filter \ "git rm --cached --ignore-unmatch .env" \ --prune-empty --tag-name-filter cat -- --all # Force push (if necessary) git push origin --force --all
-
Notification:
- Notify team members
- Check API usage logs for abuse
- Document incident
- Assess severity (Critical/High/Medium/Low)
- Create private issue (don't disclose publicly yet)
- Develop and test fix
- Deploy fix to production
- Disclose responsibly after fix is deployed
Last Updated: February 13, 2026 Review Schedule: Quarterly