Proximity Backend - Security Analysis Report
Executive Summary
Framework: Django 5.0.1 with Django Ninja REST API Key Components:
- JWT Authentication (djangorestframework-simplejwt)
- Proxmox Integration (proxmoxer library)
- Celery Task Queue
- SSH Command Execution (paramiko)
- Encrypted Database Fields (Fernet)
Overall Risk Level: MEDIUM-HIGH
The application handles sensitive infrastructure operations (container deployment, SSH execution) with several security concerns that require immediate attention.
1. Framework and Key Dependencies
Primary Framework
- Django 5.0.1: Modern Python web framework
- Django Ninja 1.1.0: Modern async REST API framework
- Django REST Framework 3.15.1: Additional REST capabilities
Authentication & Security
- djangorestframework-simplejwt 5.3.1: JWT token management
- dj-rest-auth 6.0.0: Authentication endpoints
- django-allauth 0.61.1: User registration and accounts
- bcrypt 4.1.2: Password hashing
- cryptography 41.0.0+: Encryption library
- PyJWT 2.8.0: JWT token creation/validation
Infrastructure Integration
- proxmoxer 2.0.1: Proxmox API client
- paramiko 3.4.0: SSH client for remote command execution
- requests 2.31.0: HTTP library
Async & Task Processing
- celery 5.3.6: Distributed task queue
- redis 5.0.1: Broker and cache
- channels 4.0.0: WebSocket support
- daphne 4.1.2: ASGI server
Monitoring
- sentry-sdk 1.39.2: Error tracking and monitoring
2. Overall Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Frontend (Vue.js/JavaScript) │
└──────────────────────┬──────────────────────────────────────────┘
│ HTTPS/WebSocket
▼
┌─────────────────────────────────────────────────────────────────┐
│ Django Ninja REST API (Port 8000/8765) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ JWT Cookie Authentication (proximity-auth-cookie) │ │
│ │ Global Auth: JWTCookieAuthenticator │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐ │
│ │ Core API │ │ Apps API │ │ Proxmox API │ │
│ │ /core/ │ │ /apps/ │ │ /proxmox/ │ │
│ │ Settings │ │ Deploy/Start │ │ Hosts/Nodes │ │
│ │ Health │ │ Stop/Delete │ │ Connection Tests │ │
│ └─────────────┘ │ Logs │ └─────────────────────┘ │
│ └──────────────┘ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Database (SQLite/PostgreSQL via dj-database-url) │ │
│ │ - Users (with AbstractUser) │ │
│ │ - Applications (deployed containers) │ │
│ │ - ProxmoxHosts/Nodes (with EncryptedCharField) │ │
│ │ - Backups, DeploymentLogs, SystemSettings │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
├─ Celery Task Queue ──┬─ Redis Broker
│ └─ Result Backend
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Proxmox API (HTTPS, Port 8006) │
│ - Create/Delete/Clone LXC containers │
│ - Execute commands via 'pct exec' (SSH → Proxmox node) │
│ - Manage storage, backups, snapshots │
│ │
│ SSH to Proxmox Node (Port 22) │
│ - Execute 'pct exec' for in-container commands │
│ - Configure LXC settings directly │
│ - Docker installation and application deployment │
└─────────────────────────────────────────────────────────────────┘Key Data Flows
Deployment Flow:
- API → Celery Task → ProxmoxService → Create LXC → SSH exec inside container
Authentication Flow:
- Login endpoint → JWT token in HttpOnly cookie + body
- JWTCookieAuthenticator validates token on each request
- User filtering: non-admin users see only their own apps
Backup/Restore Flow:
- Application state backed up via Proxmox vzdump
- Async restore operations via Celery
3. Top 10 Security Concerns (Priority Order)
CRITICAL
1. SSH Command Injection via Unquoted Arguments
File: /Users/fab/GitHub/proximity/backend/apps/proxmox/services.py (line 1078) Severity: CRITICAL (RCE)
# VULNERABLE CODE:
pct_command = f"pct exec {vmid} -- {command}"
# Example: command="whoami; rm -rf /" → "pct exec 100 -- whoami; rm -rf /"Issue: The command parameter in execute_in_container() is directly interpolated into the shell command without proper escaping. An attacker controlling the command parameter (via API) could execute arbitrary shell commands on the Proxmox node.
Attack Scenario:
# Malicious deployment config with:
docker_setup_command = "sh -c 'whoami; cat /etc/proxmox/pve/authkey.pub' > /tmp/leak.txt"
# Proxmox node will execute this!Impact: Complete compromise of Proxmox node and all containers
Mitigation: Use shlex.quote() or pass commands as array to exec_command() instead of shell string.
2. Plaintext Password Storage in Database
Files:
/Users/fab/GitHub/proximity/backend/apps/proxmox/api.py(lines 46, 52)/Users/fab/GitHub/proximity/backend/apps/proxmox/services.py(line 77) Severity: CRITICAL (Credential Disclosure)
Issue: Multiple TODOs indicate passwords are NOT being encrypted before saving:
# From api.py line 46-52:
# TODO: Encrypt password before saving
host = ProxmoxHost.objects.create(
password=payload.password, # TODO: Encrypt
)
# From services.py line 77:
password=host.password, # TODO: Decrypt passwordAnalysis: While EncryptedCharField is defined in apps/core/fields.py, the ProxmoxHost model DOES use it:
password = EncryptedCharField(max_length=500)However, the TODOs suggest the decryption may not work properly at retrieval time. The encryption logic uses Fernet with SHA256 hash of SECRET_KEY, which is reasonable but has issues (see below).
Actual Risk:
- Passwords are encrypted at database level (good)
- BUT: TODOs indicate developers are unsure if it's working (red flag)
- SECRET_KEY is in
.envfile and committed to repo - If SECRET_KEY is compromised, all passwords can be decrypted
Impact: If database is breached + SECRET_KEY leaked, all Proxmox credentials compromised
3. Hardcoded Credentials in .env File
File: /Users/fab/GitHub/proximity/backend/.envSeverity: CRITICAL (Credential Disclosure)
PROXMOX_PASSWORD=invaders # HARDCODED!
JWT_SECRET_KEY=atc3w3PjkWZHyWThMspZ4zcZ0v9ZnByVioVv2v_T1io # EXPOSED!
LXC_ROOT_PASSWORD=invaders # HARDCODED!
SENTRY_DSN=https://dbee00d4782d131ab54ffe60b16d969b@o149725.ingest.us.sentry.io/4510189390266368 # PUBLICImpact: Any commit history exposure reveals credentials. The JWT_SECRET_KEY allows forging authentication tokens. Proxmox credentials allow infrastructure access.
4. Insecure SSH Implementation (Password Auth, No Key Verification)
File: /Users/fab/GitHub/proximity/backend/apps/proxmox/services.py (lines 990-1004) Severity: CRITICAL (MITM Attacks)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # ⚠️ DANGEROUS!
ssh.connect(
hostname=host,
port=port,
username=username,
password=password, # Plaintext password transmission risk
allow_agent=False,
look_for_keys=False
)Issues:
- No host key verification:
AutoAddPolicy()accepts ANY host key - enables MITM attacks - Password authentication: Less secure than key-based auth
- Password in memory: Not cleared after use
- No timeout on initial connection: Could hang indefinitely
Attack: Attacker on network can MITM SSH connection and execute arbitrary commands:
Attacker ←→ Proxmox Node Setup
Attacker intercepts: "pct exec 100 -- apt-get update"
Attacker injects: "pct exec 100 -- apt-get update && malicious-command"Impact: Complete compromise of container environments
5. Unsafe Shell Command Construction in Docker Setup
File: /Users/fab/GitHub/proximity/backend/apps/applications/docker_setup.py (line 191) Severity: HIGH (Command Injection)
# Line 191:
f"sh -c \"mkdir -p /root && echo '{compose_yaml_escaped}' > /root/docker-compose.yml\""
# Single quote escaping may be insufficient:
compose_yaml_escaped = compose_yaml.replace("'", "'\\''")
# If compose_yaml contains certain patterns, could still break out:
# compose_yaml = "version: 3\nservices: ' && malicious"Issue: While the code attempts quote escaping, it's error-prone. YAML is embedded directly in shell string.
Better approach: Use base64 encoding or pass via heredoc
HIGH SEVERITY
6. DEBUG Mode Enabled in Development
File: /Users/fab/GitHub/proximity/backend/.env + /proximity/settings.pySeverity: HIGH (Information Disclosure)
DEBUG=true # In .env
DEBUG = os.getenv('DEBUG', 'False') == 'True' # In settings.pyRisks when DEBUG=True:
- Full stack traces exposed in error pages
- Database query logging enabled
- Secret settings potentially visible
- Static file serving without caching headers
Note: Good that default is False, but .env shows it's enabled locally
7. Insufficient CORS Configuration
File: /Users/fab/GitHub/proximity/backend/proximity/settings.py (lines 123-127) Severity: HIGH (CSRF/XSS)
CORS_ALLOWED_ORIGINS = os.getenv(
'CORS_ALLOWED_ORIGINS',
'http://localhost:5173' # Only localhost
).split(',')
CORS_ALLOW_CREDENTIALS = True # Cookies sent with CORS requestsIssues:
- Not explicitly restrictive: Depends entirely on env variable
- No validation of env value: Could accidentally include malicious origins
- CORS_ALLOW_CREDENTIALS=True: Allows cross-origin cookie theft
Current state is reasonable for development, but needs hardening for production.
8. Authentication Bypasses in API Endpoints
File: /Users/fab/GitHub/proximity/backend/apps/backups/api.py (line 34) Severity: HIGH (Authorization Bypass)
# Line 34:
router = Router() # TODO: Add auth=AuthBearer() when implemented
# Authentication is applied globally via:
# api = NinjaAPI(..., auth=JWTCookieAuthenticator())
# But this suggests alternative auth methods aren't implementedIssue:
- Comment suggests auth was planned but incomplete
- Backup APIs rely on global auth only
- No secondary authorization checks in some endpoints
Actual Status: APIs ARE protected by global JWT auth, but pattern is concerning
9. Weak Password Generation for Container Root
File: /Users/fab/GitHub/proximity/backend/apps/applications/tasks.py (line ~147) Severity: HIGH (Weak Credentials)
# From grep results - line indicates:
# Generate root password (TODO: Use encryption service from v1.0)
# TODO: Encrypt this
# This suggests:
# 1. Root password generation may be weak
# 2. Password not encrypted for storage
# 3. Password stored in LXC template in plain...
# Code shows:
app.lxc_root_password = root_password # TODO: Encrypt thisIssue: Container root passwords not properly encrypted in database
10. Missing Input Validation on Node Selection
File: /Users/fab/GitHub/proximity/backend/apps/applications/api.pySeverity: MEDIUM (Logic Error)
# API accepts 'node' parameter from user request
# But ProxmoxNode doesn't validate if user has access to that node
# Could allow deploying to unauthorized nodes
# Line 21-22 shows filtering by host, but:
# User can specify ANY node on ANY host they have access toMitigation: Validate node belongs to allowed hosts
4. Additional Security Issues (Medium/Low)
SSL/TLS Configuration
File: /Users/fab/GitHub/proximity/backend/proximity/settings.py (lines 342-343)
CSRF_COOKIE_SECURE = False # Development setting
SESSION_COOKIE_SECURE = False # Development settingIssue: Not production-ready. Needs HTTPS in production.
Database Query Logging
File: /Users/fab/GitHub/proximity/backend/apps/applications/api.py (line 45)
# DEBUG: Log received payloadIssue: Debug logging of payloads could expose sensitive data
Sentry DSN Exposed
File: /Users/fab/GitHub/proximity/backend/.env (line 53)
SENTRY_DSN=https://dbee00d4782d131ab54ffe60b16d969b@o149725.ingest.us.sentry.io/...Issue: Public DSN allows anyone to send errors to project
SQL Injection Risk (Low)
Status: Uses Django ORM, safe from SQL injection
- Good use of select_related() for performance
- All database access through ORM
Deserialization Risk (Low)
Status: YAML loading
- File:
apps/applications/docker_setup.py - Uses
yaml.dump()(safe) notyaml.load()(unsafe) - Safe implementation
5. File Structure Overview
/Users/fab/GitHub/proximity/backend/
├── proximity/ # Django project settings
│ ├── settings.py # Main configuration
│ ├── urls.py # URL routing
│ ├── auth.py # JWT authentication
│ ├── celery.py # Celery configuration
│ ├── wsgi.py # WSGI application
│ └── asgi.py # ASGI application
│
├── apps/ # Django applications
│ ├── core/ # User models, encryption, base settings
│ │ ├── models.py # User, SystemSettings
│ │ ├── fields.py # EncryptedCharField, EncryptedTextField
│ │ ├── encryption.py # EncryptionManager (Fernet)
│ │ ├── auth.py # (empty, uses dj-rest-auth)
│ │ ├── api.py # Health, system info, settings management
│ │ ├── middleware.py # Sentry context enrichment
│ │ └── migrations/
│ │
│ ├── proxmox/ # Proxmox infrastructure integration
│ │ ├── models.py # ProxmoxHost, ProxmoxNode
│ │ ├── services.py # ProxmoxService (SSH execution!)
│ │ ├── api.py # Proxmox API endpoints (hosts/nodes)
│ │ ├── schemas.py # Request/response schemas
│ │ ├── mock_service.py # Testing mock
│ │ └── migrations/
│ │
│ ├── applications/ # Application deployment management
│ │ ├── models.py # Application, DeploymentLog
│ │ ├── api.py # CRUD operations, lifecycle
│ │ ├── services.py # ApplicationService, reconciliation
│ │ ├── tasks.py # Celery deployment tasks
│ │ ├── docker_setup.py # Docker installation via SSH
│ │ ├── port_manager.py # Port allocation
│ │ ├── schemas.py # API schemas
│ │ └── management/
│ │ └── commands/ # Cleanup commands
│ │
│ ├── backups/ # Backup management
│ │ ├── models.py # Backup model
│ │ ├── api.py # Backup CRUD endpoints
│ │ ├── tasks.py # Async backup tasks
│ │ └── schemas.py
│ │
│ ├── catalog/ # Application catalog
│ │ ├── models.py # Catalog, CatalogApp
│ │ ├── api.py # Search, list, reload
│ │ ├── services.py # Catalog loading from filesystem
│ │ └── schemas.py
│ │
│ └── monitoring/ # Monitoring
│ └── models.py # Monitoring data
│
├── tests/ # Test suite
│ ├── test_*.py # Integration tests
│ └── conftest.py # Pytest configuration
│
├── requirements.txt # Python dependencies
├── manage.py # Django CLI
├── db.sqlite3 # SQLite database (dev)
├── .env # Environment variables (EXPOSED!)
├── Dockerfile # Container image
└── entrypoint.sh # Docker entrypoint6. Key Security Characteristics
Authentication
- Method: JWT tokens in HttpOnly cookies + optional body
- Rotation: Refresh tokens rotated on every refresh
- Expiration: Access tokens 60 minutes, refresh tokens 7 days
- Global Protection: All endpoints protected by JWTCookieAuthenticator
- User Isolation: Non-admin users see only their own applications
Authorization
- Model: Role-based (is_staff for admin)
- Implementation: Manual checks in API endpoints
- Granularity: User ownership checks for applications
- Risk: Some endpoints missing explicit permission checks
Password Management
- Hashing: Uses Django's built-in validators + bcrypt
- Storage: User passwords properly hashed
- Proxmox credentials: Encrypted with Fernet (but TODO comments suggest issues)
- Container root: Encrypted in database but unclear if working
Encryption
- Algorithm: Fernet (symmetric) from cryptography library
- Key: SHA256 hash of SECRET_KEY (first 32 bytes)
- Implementation: Custom EncryptionManager class
- Risk: SECRET_KEY in .env means if exposed, all encrypted data can be decrypted
Logging
- Level: INFO by default, DEBUG in development
- Output: Console (StreamHandler)
- Sentry: Integrated for error tracking
- Risk: Debug logging may expose sensitive data; Sentry DSN is public
7. Recommendations (Priority Order)
Immediate (Critical)
Fix SSH Command Injection:
- Use
shlex.quote()for command arguments - Or better: Pass commands as array to paramiko
- Test with various payloads
- Use
Verify Password Encryption:
- Test that encrypted passwords are being properly decrypted
- Add unit tests for encryption/decryption round-trip
- Document why TODOs say "encrypt" when EncryptedCharField is used
Secure SSH Implementation:
- Implement SSH key-based authentication instead of passwords
- Add host key verification (known_hosts file or strict mode)
- Add SFTP for secure file transfers instead of shell commands
- Example:python
ssh.get_transport().get_security_options().key_types = ['ssh-ed25519'] ssh.load_host_keys(os.path.expanduser('~/.ssh/known_hosts')) ssh.connect(..., look_for_keys=True) # Use keys, not passwords
Remove Hardcoded Credentials:
- Regenerate all exposed credentials
- Move to secure secret management (HashiCorp Vault, AWS Secrets Manager)
- Use environment variables only, no defaults in code
- Implement credential rotation
Short Term (High Priority)
Input Validation Hardening:
- Validate all user input for API endpoints
- Use Pydantic for request validation (already using in some endpoints)
- Sanitize command arguments passed to SSH
CORS & CSRF Hardening:
- Restrict CORS_ALLOWED_ORIGINS to specific domains only
- Set CSRF_COOKIE_SECURE = True in production
- Consider CSRF token middleware even for API
Implement Authorization Layer:
- Create permission classes for Django Ninja
- Example:python
class IsAppOwner(BasePermission): def has_permission(self, request): return request.user.is_authenticated def has_object_permission(self, request, obj): return obj.owner == request.user or request.user.is_staff - Apply to all sensitive endpoints
Remove Debug Code:
- Remove "sentry-debug" endpoint (line 46 in api.py)
- Remove debug logging of payloads
- Disable DEBUG mode in production config
Medium Term
Add Rate Limiting:
- Use django-ratelimit or similar
- Rate limit login endpoints (prevent brute force)
- Rate limit deployment endpoints (prevent resource exhaustion)
Implement Audit Logging:
- Log all sensitive operations (deploy, delete, configuration changes)
- Include user, timestamp, IP address
- Use immutable log storage
- Example: All changes to ProxmoxHost should be logged
Add Request Signing:
- For Celery tasks crossing process boundaries
- Prevent tampering with task parameters
Security Headers:
- Add security middleware:python
SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_SECURITY_POLICY = {...} X_FRAME_OPTIONS = 'DENY' SECURE_HSTS_SECONDS = 31536000 SECURE_HSTS_INCLUDE_SUBDOMAINS = True
- Add security middleware:
8. Testing & Validation
Manual Testing Checklist
- [ ] Attempt command injection in deployment APIs
- [ ] Test SSH with malicious commands
- [ ] Attempt authorization bypass on backup endpoints
- [ ] Try accessing other users' applications
- [ ] Test CORS with unauthorized origins
- [ ] Verify encrypted fields decrypt properly
Automated Testing
- [ ] Add unit tests for encryption/decryption
- [ ] Add integration tests for Proxmox operations
- [ ] Add security tests for input validation
- [ ] Run SAST tools (bandit, semgrep)
- [ ] Run dependency check (safety, pip-audit)
Vulnerability Scanning
# Check dependencies
pip-audit --desc
# Check code security
bandit -r apps/ -f json
# Check for common patterns
semgrep --config p/security-auditConclusion
The Proximity backend has strong foundational security with proper JWT authentication, role-based authorization, and encrypted sensitive fields. However, it has CRITICAL vulnerabilities in SSH command execution and authentication that must be fixed immediately before production use.
The three main security gaps are:
- Command injection via unescaped SSH commands (CRITICAL RCE)
- Plaintext credential exposure in .env (CRITICAL)
- Insecure SSH implementation without host verification (CRITICAL MITM)
All other issues are important but less immediately dangerous. Priority should be fixing these three issues, then implementing the "Short Term" recommendations before any production deployment.