BirdLense Hub Security Analysis
Русский
Last updated: March 2026
Summary
| Priority |
Count |
Actions |
| Critical |
5 |
Path traversal, default auth, secrets in API |
| High |
8 |
Secret storage, rate limiting, Docker root |
| Medium |
9 |
Session timeout, CORS, dependencies |
| Low |
6 |
Documentation, migrations |
Automated scanning: GitHub CodeQL runs in CI (Python + TypeScript UI). See CODEQL.md.
1. Authentication and Authorization
| Risk |
Description |
Recommendation |
| Critical |
API has no authentication by default. Endpoints /api/ui/* are accessible without checks. |
Add mandatory auth for production (API key, JWT, or reverse proxy with auth). |
| ~~Critical~~ Fixed |
PROCESSOR_SECRET not set — Processor API was open. |
In production, blocks when empty. Deploy writes to .env. |
| Critical |
MCP has no authentication when mcp.token and MCP_TOKEN are empty. |
Set MCP_TOKEN when mcp.enabled=true. |
| High |
Settings password (settings_password) is optional. When empty — settings and system operations are unprotected. |
Require password in production. |
| High |
Settings session: session.permanent = True, no timeout. |
Add session timeout (15–30 min). |
| Medium |
Endpoints /api/ui/system/* (logs, metrics, purge, scan) protected only by settings_check_access(). |
Ensure mandatory settings_password. |
2. Secrets
| Risk |
Description |
Recommendation |
| ~~Critical~~ Fixed |
Default FLASK_SECRET_KEY. |
In BIRDLENSE_ENV=production env is required, else RuntimeError. Deploy writes to .env. |
| ~~Critical~~ Fixed |
GET /api/ui/settings returned full config with secrets. |
Secrets are masked (***), placeholder on save does not overwrite real value. |
| High |
user_config.yaml stores secrets in plain text: telegram_bot_token, mqtt.password, secrets.openweather_api_key, homeassistant.token, settings_password, mcp.token. |
Store in env or secret manager; do not write to YAML. |
| High |
OpenAPI describes telegram_bot_token, secrets.openweather_api_key in Settings schema. |
Add x-sensitive: true, do not expose in examples. |
| Medium |
settings_password in plain text. In default_config: "Consider hashing for production". |
Store hash (bcrypt/argon2). |
| Low |
.env in .gitignore, deploy script does not commit it. |
Keep as is. |
Operator runbook: SECRETS_ROTATION.md — full inventory, rotation steps, verification, rollback, emergency note template (issue #119).
3. Path Traversal (nginx)
| Risk |
Description |
Recommendation |
| ~~Critical~~ Fixed |
location /data/ with alias /app/data/ — request /data/../.env could read /app/.env. |
Added check if ($request_uri ~* "\.\.") { return 403; } in all nginx configs. |
| High |
/data/recordings/ accessible without authentication. Path YYYY/MM/DD/HHMMSS/video.mp4 is predictable. |
Add access check via API with auth or restrict by IP. |
Test: curl -I "http://YOUR_HOST:8085/data/../.env" — if vulnerable, returns 200.
4. API
| Risk |
Description |
Recommendation |
| High |
No rate limiting. |
Add Flask-Limiter or similar. |
| Medium |
CORS: supports_credentials: True, origins via CORS_ORIGINS. When empty — localhost only. |
Document CORS setup for external access. |
| Medium |
Input validation is partial. |
Extend validation (schemas, sizes) for mutating endpoints. |
| ~~Medium~~ Fixed |
X-Real-IP / X-Forwarded-For were trusted without a trusted proxy boundary. |
Proxy headers are used only when TRUSTED_PROXY=1; otherwise rate limiting uses remote_addr. |
| ~~Medium~~ Fixed |
Web Push subscription could enable web_push.enabled without settings access. |
POST /api/ui/push/subscribe now requires settings_check_access(). |
| ~~High~~ Fixed |
webhook.url could target loopback / private IPs and be abused as SSRF. |
Only public http/https targets are allowed; private ranges are blocked. |
5. Database
| Risk |
Description |
Recommendation |
| Low |
SQLAlchemy ORM, parameterized queries. |
Continue using ORM. |
| Low |
SQLite: app/data/db/birdlense.db, not exposed. |
Keep as is. |
| Medium |
Migrations in app.py — static strings. |
Use Alembic. |
6. File System
| Risk |
Description |
Recommendation |
| Medium |
scan_recordings checks year.isdigit(), regex for timestamp. |
Additionally verify path stays inside recordings_dir(). |
| Low |
processor_routes.py: VIDEO_PATH_RE strictly limits video_path. |
Keep as is. |
7. Network
| Risk |
Description |
Recommendation |
| Medium |
Port 8085 exposed. |
Use reverse proxy (nginx/traefik) with TLS. |
| Low |
Gunicorn — 127.0.0.1:8000, MCP — 127.0.0.1:8001. External access only via nginx. |
Keep as is. |
8. Dependencies
| Risk |
Description |
Recommendation |
| Medium |
Vulnerabilities not checked automatically. |
Regularly: pip audit, safety check, npm audit. |
| Low |
Versions in requirements.txt pinned. |
Update based on audit results. |
8.1 Git history secret scan (maintainer hygiene)
- Command:
bash scripts/security/scan_git_history_secrets.sh
- Tool: Gitleaks in Docker image
zricethezav/gitleaks:latest
- Report path:
.artifacts/gitleaks-history.json
Current baseline (Mar 2026): scan of full git history completed with no leaks found.
9. Docker
| Risk |
Description |
Recommendation |
| High |
Container runs as root (no USER in Dockerfile). |
Add non-privileged user and USER. |
| Medium |
Base image ultralytics/ultralytics — heavy. |
Consider multi-stage with minimal runtime. |
| Low |
No --privileged, --cap-add. |
Do not add. |
10. Configuration
| Risk |
Description |
Recommendation |
| High |
user_config.yaml contains sensitive fields. |
Store in env or secret manager. |
| Low |
deploy.sh excludes user_config.yaml on sync. |
Keep as is. |
Quick Steps for Production
- ~~Set secrets~~ ✅ Deploy via
deploy.local.sh writes PROCESSOR_SECRET, FLASK_SECRET_KEY, BIRDLENSE_ENV=production.
- Settings password: set
general.settings_password.
- ~~Path traversal~~ ✅ Nginx: block
\.\., %2e%2e. image_path in notify: _is_safe_image_path.
- Restrict access to
/data/recordings/ (auth or IP).
- ~~Rate limiting~~ ✅
POST /api/ui/settings/verify-password: 5 failed attempts per 60 s per client IP → 429 + Retry-After; success clears the counter. IP from X-Real-IP / X-Forwarded-For behind nginx — see ACCESS_CONTROL.
- Docker: run container as non-privileged user.
- ~~Mask secrets~~ ✅
GET /api/ui/settings returns *** for sensitive fields.
- Secret rotation: follow SECRETS_ROTATION.md (prod ops).
To report a vulnerability, create a GitHub Security Advisory or contact the maintainers. See Security policy in the repository root.