Security best practices, hardening guide, and security recommendations.
- Security Overview
- Token Security
- Secret Management
- Mount Mode Security
- Memory Security
- Network Security
- Access Control
- Audit Logging
- Hardening Guide
- Security Checklist
| Feature | Description | Protection Level |
|---|---|---|
| Mount Mode | Secrets in memory, not env vars | High |
| Service Tokens | Read-only, scoped access | High |
| Memory Clearing | Secure overwrite on exit | Medium |
| Audit Logging | Access tracking | Medium |
| Host Validation | Known hosts verification | Medium |
| Name Validation | RCE prevention | High |
- Credential Theft: Use Service Tokens + Mount Mode
- Memory Dumping: Enable memory clearing
- Log Exposure: Enable log redaction
- RCE: Validate secret names
- Man-in-Middle: Use host key verification
Always use Service Tokens (dp.st.*) in production:
# Good: Service Token
skill = DopplerSSHSkill(
project="my-project",
service_token="dp.st.xxx" # Read-only, scoped
)
# Avoid: Personal Token
skill = DopplerSSHSkill(
project="my-project",
service_token="dp.pt.xxx" # Full account access
)
# SecurityWarning will be issued# Via environment variable (recommended)
# In your shell profile:
export DOPPLER_TOKEN="dp.st.xxx"
# Via file (with proper permissions)
# /etc/doppler-ssh/token
chmod 600 /etc/doppler-ssh/token
# Note: Never hardcode tokens in source codeCreate scoped Service Tokens in Doppler Dashboard:
{
"name": "ssh-mcp-server",
"slug": "ssh-mcp-prod",
"scopes": [
"secrets.read",
"configs.read",
"projects.read"
],
"config_access": ["ssh-mcp-server:prod"]
}Secrets must follow this naming pattern:
SSHMCP_HOST_{HOSTNAME}_{SECRET_TYPE}
Valid secret types:
| Type | Description |
|---|---|
PASSWORD |
SSH password |
KEY_PASSPHRASE |
Private key passphrase |
SUDO_PASSWORD |
Sudo password |
SSH_KEY |
Private key content |
# Create secrets via Doppler CLI
doppler secrets set SSHMCP_HOST_PROD_DB_PASSWORD --value="secret123"
doppler secrets set SSHMCP_HOST_PROD_DB_KEY_PASSPHRASE --value="keypass"
doppler secrets set SSHMCP_HOST_PROD_DB_SUDO_PASSWORD --value="sudopass"
doppler secrets set SSHMCP_HOST_PROD_DB_SSH_KEY --value="$(cat id_rsa)"The SecurityManager validates secret names:
from doppler_ssh.core.security import SecurityManager
security = SecurityManager()
# Validates against dangerous environment variables
security.validate_secret_name("SSHMCP_HOST_PROD_PASSWORD") # OK
security.validate_secret_name("LD_PRELOAD_ATTACK") # SecurityError!Dangerous variables include:
LD_PRELOAD,LD_LIBRARY_PATHPROMPT_COMMAND,BASH_ENVPYTHONSTARTUP,PYTHONPATHNODE_OPTIONS,_JAVA_OPTIONS
| Aspect | Mount Mode | ENV Mode |
|---|---|---|
| Secrets in memory | Yes | No (env vars) |
| Process isolation | Yes | No |
| Max reads | Limited (1) | Unlimited |
| Security | Higher | Lower |
from doppler_ssh.core.config import DopplerMode
skill = DopplerSSHSkill(
project="my-project",
mode=DopplerMode.MOUNT,
mount_path="/run/secrets/ssh",
mount_max_reads=1 # Read only once
)# doppler run with mount
doppler run \
--project ssh-mcp-server \
--config prod \
--mount /run/secrets/ssh \
--mount-max-reads 1 \
-- \
python app.pyAlways clear secrets when done:
# Use secure_clear method
skill.secure_clear(host_config)
# Or use context manager
with DopplerSSHSkill(project="my-project") as skill:
host_config = skill.load_host_config("prod-db")
# Automatic cleanupfrom doppler_ssh.core.security import SecurityManager
security = SecurityManager()
# Clear individual string
security.secure_clear_string("secret")
# Clear dictionary
security.secure_clear_dict({"password": "secret", "key": "key"})
# Clear list
security.secure_clear_list(["secret1", "secret2"])# Disable caching for sensitive data
skill = DopplerSSHSkill(
project="my-project",
cache_ttl=0 # No caching
)
# Or clear cache manually
skill.clear_cache()# Doppler uses HTTPS by default
api_host = "https://api.doppler.com"
# Verify certificates
import ssl
context = ssl.create_default_context()# Use corporate proxy
import os
os.environ["HTTPS_PROXY"] = "http://proxy.example.com:8080"
os.environ["NO_PROXY"] = "localhost,127.0.0.1"# Allow only Doppler API
iptables -A OUTPUT -p tcp -d api.doppler.com --dport 443 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j DROP# Configuration file
chmod 600 /etc/doppler-ssh/config.yaml
# SSH keys
chmod 600 ~/.ssh/id_*
# Logs (read-only for app)
chmod 640 /var/log/doppler-ssh.logRun as dedicated user:
# Create service user
useradd -r -s /bin/false doppler-ssh
# Set ownership
chown -R doppler-ssh:doppler-ssh /opt/doppler-ssh
# No login capability
usermod -L doppler-ssh[Service]
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/opt/doppler-ssh /run/secrets
# Deny access to sensitive paths
RuntimeDirectory=!doppler-sshimport logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("doppler-ssh")
logger.setLevel(logging.INFO)
# Log to file
handler = logging.FileHandler("/var/log/doppler-ssh-audit.log")
handler.setFormatter(logging.Formatter("%(asctime)s %(message)s"))
logger.addHandler(handler)
# Log access
security.log_access("prod-db", "password")2024-01-15 10:30:00 Secret accessed: host=prod-db, type=password
2024-01-15 10:30:01 SSH connected: host=prod-db, user=admin
2024-01-15 10:30:05 Secret cleared: host=prod-db
# Automatic redaction in logs
security = SecurityManager()
# Mask secrets
masked = security.mask_secrets_in_logs(
"password is secret123",
{"password": "secret123"}
)
# Result: "password is se***23"# Disable unnecessary services
systemctl disable bluetooth
systemctl disable avahi-daemon
# Enable firewall
ufw enable
ufw default deny incoming
ufw allow out to api.doppler.com port 443# Use environment variable isolation
os.environ.pop("PYTHONPATH", None)
os.environ.pop("PYTHONSTARTUP", None)
# Disable dangerous features
importwarnings.filterwarnings("error", category=BytesWarning)# Validate all inputs
from doppler_ssh.core.security import SecurityManager
security = SecurityManager()
# Validate secret names
def get_secret(name: str) -> str:
security.validate_secret_name(name) # Raises if dangerous
return client.get_secret(name)
# Limit retries
MAX_RETRIES = 3# Non-root user
RUN useradd -r -u 1000 appuser
# Drop capabilities
RUN setcap cap_net_bind_service=+ep /usr/bin/python3
# Read-only filesystem
RUN chown -R appuser /app
USER appuser- Use Service Token (dp.st.*)
- Enable Mount Mode
- Set mount_max_reads = 1
- Enable cache_ttl properly
- Validate secret names
- File permissions (600)
- Service user created
- No hardcoded secrets
- Firewall configured
- TLS verification enabled
- Audit logging enabled
- Secrets cleared after use
- Log redaction enabled
- Health checks in place
- Backups tested
- Access logs monitored
- Error alerts configured
- Rate limiting enabled
- Anomaly detection active
- Immediate Actions:
# Disable token
doppler tokens revoke dp.st.xxx
# Rotate all secrets
doppler secrets set SSHMCP_HOST_PROD_DB_PASSWORD --value="newpassword"
# Kill running processes
pkill -f doppler-ssh
# Preserve logs
cp /var/log/doppler-ssh.log /backup/- Investigation:
# Check access logs
grep "2024-01-15" /var/log/doppler-ssh.log
# Check for anomalies
journalctl -u doppler-ssh --since "2024-01-15 10:00"
# Review failed auth attempts
grep "Authentication failed" /var/log/auth.log- Recovery:
# Generate new token
doppler tokens create --name "replacement-token"
# Deploy new configuration
cp /backup/config.yaml /opt/doppler-ssh/config.yaml
# Restart service
systemctl restart doppler-ssh| Standard | Implementation |
|---|---|
| SOC 2 | Audit logs + access control |
| PCI DSS | Tokenization + encryption |
| HIPAA | Audit logging + secure clearing |
| ISO 27001 | Full hardening guide |
- Enable SELinux/AppArmor
- Use hardware security keys
- Implement MFA for Doppler access
- Regular security audits
- Penetration testing
If you find a security vulnerability:
-
Do NOT create a public issue
-
Email security@example.com
-
Include:
- Description of the issue
- Steps to reproduce
- Potential impact
- Suggested fix (optional)
-
Expected response:
- Acknowledge within 24 hours
- Fix timeline within 30 days
- Public disclosure after fix