| Version | Supported |
|---|---|
| 1.x.x | ✅ |
If you discover a security vulnerability, please email the maintainers directly instead of opening a public issue.
Please do NOT:
- Open a public GitHub issue
- Post in discussions or forums
- Disclose publicly before a fix is available
Please DO:
- Email details to the maintainer
- Allow reasonable time for a fix
- Coordinate disclosure timing
The UI requires HTTP Basic auth. This is not a hardening option — it is the only thing standing in front of a shell-exec endpoint.
The Agent tab spawns Claude Code as the deploying user with
--dangerously-skip-permissions, and nginx binds 0.0.0.0:3080. Before this landed,
anyone who could route to the port could run arbitrary commands as that user. A VPN
ACL is not sufficient: it covers the VPN, not the LAN the machine also sits on.
| Property | Behaviour |
|---|---|
| Scope | Every route except /health (needed by the container healthcheck) |
| Storage | nginx {SHA} htpasswd line, hashed by deploy.sh before leaving the dev machine |
| Missing credential | Fails closed — empty htpasswd, nginx denies everything |
| File mode | 640 root:nginx; workers drop privileges, so 600 breaks every request |
Set SPARKY_AUTH_USER / SPARKY_AUTH_PASS in the gitignored .env.
Two related properties:
- The FastAPI backend binds
127.0.0.1:3081. It exposes container control and shell exec; on host networking0.0.0.0would publish that to the LAN and the VPN. - Config URLs resolve against
location.origin, so opening the app ashttp://user:pass@host/works. Without that, everyfetchthrows — Chrome refuses to build aRequestfrom a URL carrying credentials.
Basic auth over plain HTTP sends the credential on every request. That is acceptable inside a WireGuard/Tailscale tunnel and on a trusted LAN; it is not acceptable on an open network. Terminate TLS first if the box is ever exposed.
Recorded because the failure mode is instructive.
The SSH password was committed in plaintext — in VALIDATION_REPORT.md, and in the
two .claude/ security checklists that grepped for it literally. The tooling meant
to prevent the leak was the leak. It was public for roughly six months.
History was rewritten with git filter-repo on 2026-08-02, but that is not
sufficient on its own:
- Force-pushing orphans commits; it does not delete them. GitHub still serves them by SHA until it garbage-collects, which only GitHub Support can force.
- Forks are independent repositories. Three of them still carry the original history.
A credential that reached a public repository must be rotated, not just removed.
Scanners now read literal values from .secret-patterns (gitignored, with a
committed .example), and IP checks are range patterns rather than one hardcoded
address — so they also catch addresses nobody thought to list.
-
Never commit sensitive data
- Use
.envfiles (excluded from git) - Use environment variables
- Use SSH keys instead of passwords
- Use
-
Secure your server
- Use firewall (UFW, iptables)
- Restrict port access
- Keep system updated
- Use HTTPS in production
-
Docker Security
- Run containers as non-root when possible
- Limit Docker socket access
- Use read-only mounts where possible
- Keep images updated
-
Access Control
- Use VPN for remote access (recommended)
- Implement reverse proxy with authentication
- Use strong passwords
- Regular security audits
Never commit .env files. Use .env.example as a template:
# GOOD - Use .env.example with placeholder values
DGX_HOST=your-server-ip
DGX_PASS=your-password
# BAD - Don't commit real values
# (192.0.2.x is RFC 5737 documentation space, used here so this example is not
# itself a leak of a real internal address)
DGX_HOST=192.0.2.10
DGX_PASS=actualpassword123Instead of passwords in .env:
# Generate SSH key
ssh-keygen -t ed25519 -C "your_email@example.com"
# Copy to server
ssh-copy-id user@server
# Use deploy.sh without DGX_PASS
DGX_HOST=server-ip
DGX_USER=username
# DGX_PASS not needed with SSH keysThe backend API has these security considerations:
-
Docker Socket Access
- Required for container management
- Limits: User must be in docker group
- Risk: Can start/stop containers
-
Command Execution
- Limited to whitelisted commands
- Timeout enforced
- No shell injection possible
-
Claude Code Integration
- Runs with
--dangerously-skip-permissions - Has access to host filesystem
- Suitable for trusted environments only
- Runs with
- Firewall Rules
# Allow only specific ports
sudo ufw allow 22/tcp # SSH
sudo ufw allow 3080/tcp # Web UI (nginx; the only surface that should be reachable)
sudo ufw enable
# Do NOT open 3081. The backend binds 127.0.0.1 and exposes container control
# and shell exec; nginx reaches it over the host's own loopback.- Reverse Proxy (Production)
Use nginx or Caddy with HTTPS:
server {
listen 443 ssl http2;
server_name your-domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:3080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}- VPN Access
Recommended for remote access:
- Tailscale (easy setup)
- WireGuard (performance)
- OpenVPN (traditional)
The backend container mounts /var/run/docker.sock for container management.
Risk: Container can control Docker daemon Mitigation:
- Runs as non-root user
- Requires docker group membership
- Limited to trusted environments
Claude Code runs with autonomous permissions.
Risk: Can execute arbitrary commands Mitigation:
- Runs in backend container only
- No direct web exposure
- Suitable for private/trusted deployments
Ollama and ComfyUI reject any request whose Origin is not localhost with 403.
The obvious fix is OLLAMA_ORIGINS=*, and it is the wrong one: it accepts every
origin from every client on the box, so any page in any browser on the network can
drive the model server.
What this project does instead: nginx strips the Origin header on /ollama/
and /comfyui/. That vouches only for requests arriving through this proxy — which
is itself behind Basic auth — and leaves both services rejecting everything else.
The failure is confusing to diagnose, so it is worth naming: curl succeeds (it
sends no Origin) while the browser gets 403.
Before deploying to production:
-
.envfile not in git - Strong passwords or SSH keys
- Firewall configured
- HTTPS enabled (reverse proxy)
- VPN for remote access
- Docker images up to date
- System packages updated
- Regular backups configured
- Logs monitored
- Access limited to trusted users
Secure setup with Caddy + Tailscale:
# 1. Install Tailscale VPN
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# 2. Install Caddy
sudo apt install caddy
# 3. Configure Caddy
sudo nano /etc/caddy/CaddyfileCaddyfile:
your-tailscale-name.ts.net {
reverse_proxy localhost:3080
}
# 4. Start services
sudo systemctl restart caddy
./deploy.sh deployAccess only via Tailscale VPN: https://your-tailscale-name.ts.net
- Security patches: Released ASAP
- Dependencies: Updated monthly
- System: Keep OS and Docker updated
Monitor for security updates:
# Check outdated npm packages
npm audit
# Update packages
npm update
# Python dependencies
pip list --outdatedIf compromised:
-
Immediate Actions
- Disconnect from network
- Stop affected containers
- Change all passwords
- Revoke SSH keys
-
Investigation
- Check logs:
docker logs <container> - Review system logs:
/var/log/ - Check for unauthorized changes
- Check logs:
-
Recovery
- Restore from backup
- Update all credentials
- Apply security patches
- Review access controls
-
Prevention
- Document what happened
- Update security measures
- Improve monitoring
For security issues, contact the repository maintainer directly.