CloudMesh is community software. It has NOT been professionally audited or penetration-tested. The security features described below are implemented with security best practices in mind, but they are NOT guarantees. Use CloudMesh at your own risk. For production environments handling sensitive data, consult a professional security auditor.
This document describes what security measures exist in the codebase — not what has been independently verified.
| Version | Supported | Notes |
|---|---|---|
| 4.2.x | Yes | Current feature line; latest patch is 4.2.6 |
| 4.1.x | Yes | LTS; security fixes until 2027-10-20 |
| 4.0.x | No | Internal checkpoint; never published |
| 3.4.x | No | Upgrade to 4.2.6 or 4.1.2 (LTS) |
| < 3.4 | No | Upgrade to 4.2.6 or 4.1.2 (LTS) |
CloudMesh is distributed through GitHub Releases. The cloudmesh name on
PyPI belongs to a different project; do not install it from PyPI.
Always run the latest patch of the line you are on for the most recent security fixes:
cm update --pipIf you discover a security vulnerability in CloudMesh, please report it responsibly:
- Do NOT open a public GitHub issue for security vulnerabilities
- Use GitHub's private vulnerability reporting or contact the maintainer directly
- Include: description, steps to reproduce, potential impact, suggested fix
- You will receive a response within 48 hours
Do not exploit vulnerabilities on systems you do not own.
CloudMesh uses a zero-trust model by default. Every connection is authenticated, every config is encrypted, and every command is logged.
+-------------------+ HMAC Auth +------------------+
| Controller | <-----------------> | Node Agent |
| (Your Laptop) | TLS (opt.) | (Cloud Server) |
| | | |
| - Encrypted cfg | | - SPA (opt.) |
| - Key rotation | | - DDoS guard |
| - Hash chain log | | - iptables |
+-------------------+ +------------------+
All server configs, node keys, and sensitive data are encrypted at rest using Fernet symmetric encryption.
How it works:
- A 256-bit key is auto-generated and stored in
.secret.key - Config is serialized to JSON, then encrypted with Fernet
- Decryption only happens in memory
- File permissions set to 600 (owner-only) on Linux
File: cloudmesh/core/security.py — SecurityManager class
# Encryption flow:
plaintext = json.dumps(config).encode()
encrypted = fernet.encrypt(plaintext) # AES-128-CBC + HMAC-SHA256
config_path.write_bytes(encrypted)Key file: .secret.key
Config file: config.json (encrypted)
Backups: Automatic backups in backups/ before every save
Limitation: If someone gets access to your .secret.key file, they can decrypt everything. Keep it safe.
Every node has a unique 64-character hex auth key. All TCP communication is authenticated.
How it works:
- Controller sends:
{"action": "ping", "auth": "KEY"} - Node compares using:
hmac.compare_digest(received_key, stored_key) - Timing-safe comparison prevents timing attacks
- Wrong key → connection rejected immediately
File: cloudmesh/node/cloudmesh_node.py — _handle() method
Key storage:
- Controller:
.node_keys.json - Node:
.node_key
Key rotation:
cm panic # Rotate all keys (local + remote)
cm panic rotate # Rotate remote node keys only
cm panic retry-pending # Retry failed rotationsLimitation: If the auth key file is compromised, the attacker can impersonate the controller. Keys are stored in plaintext on disk.
CloudMesh uses RejectPolicy by default — unknown hosts are rejected, not silently accepted.
How it works:
- SSH connections use
StrictHostKeyChecking=yes(default) - If host key is not in
~/.ssh/known_hosts, connection is REJECTED - You must manually add the host:
ssh-keyscan -H HOST >> ~/.ssh/known_hosts - If you disable strict checking, a MITM warning is emitted
File: cloudmesh/core/ssh_util.py
# Default (secure):
ssh_cmd = ["ssh", "-o", "StrictHostKeyChecking=yes", ...]
# If disabled:
warn_host_key_disabled(host)
# WARNING: StrictHostKeyChecking disabled for 192.168.1.100.
# This connection is vulnerable to MITM attacks.The built-in REST API has multi-layer DDoS protection.
File: cloudmesh/core/ddos.py
| Layer | Protection | Config |
|---|---|---|
| Rate Limiting | 30 requests/min per IP | Sliding window |
| Connection Limits | 5 per IP, 100 total | Per-IP + global |
| IP Auto-Ban | 5 failures = 5 min ban | Persistent (survives restart) |
| Packet Validation | Max 1MB, valid JSON, has action field |
Stateless |
| Slowloris Guard | 10s header timeout, 50MB max | Per-connection |
Ban list: Stored in data/banned_ips.json
# Example DDoS protection setup:
DDoSProtection(
rate_max=30, # requests per window
rate_window=60, # window in seconds
conn_per_ip=5, # max connections per IP
conn_total=100, # max total connections
ban_threshold=5, # failures before ban
ban_duration=300, # ban duration in seconds
read_timeout=10, # slowloris timeout
)Limitation: This protects against basic DDoS. It does NOT protect against sophisticated volumetric attacks or application-layer attacks that mimic normal traffic.
Makes TCP ports invisible to port scanners. Only signed UDP knocks can open the port.
File: cloudmesh/node/cloudmesh_node.py — SpacListener class
How it works:
- Node listens on UDP port (default 9998) silently
- Controller sends a signed HMAC packet (timestamp + nonce + signature)
- Node validates HMAC with shared auth key
- If valid, node adds iptables ACCEPT rule:
-s <source_ip>(source IP binding) - TCP port opens for N seconds (default 5)
- After N seconds, iptables ACCEPT rule is removed
- TCP port closes
Security properties:
- Source IP binding: Only the knocker's IP can connect (not anyone on the internet)
- Fail-closed: If iptables DROP fails at startup, node refuses to start
- HMAC signed: Knock cannot be forged without the auth key
- Nonce replay protection: Each nonce is used only once
- Timestamp validation: Stale packets (>30s) are rejected
Starting with SPA:
cm node start --spa
cm node start --spa --spa-port 9998 # Custom UDP port
cm node start --spa --spa-window 10 # TCP stays open 10sLimitation: SPA requires root/elevated privileges for iptables. Does not work on Windows. UDP packets can theoretically be sniffed on the same network segment.
Decoy keys that trigger alerts when used. Zero false positives.
File: cloudmesh/core/advanced.py — Tripwire features
How it works:
- Plant a fake key on a honeypot server
- If anyone authenticates with that key, you get an alert
- The key is a valid-looking auth key but serves only as a trap
cm tripwire plant --node honeypot --host 10.0.0.99
cm tripwire check # Check if any tripwire was triggeredLimitation: Only detects unauthorized access if the attacker uses the tripwire key specifically. Does not protect against other attack vectors.
Rotates ALL encryption keys and node auth keys instantly.
File: cloudmesh/core/panic.py — PanicManager class
What gets rotated:
.secret.key— Fernet encryption key (backup:.secret.key.bak).node_keys.json— All node auth keys (backup:.node_keys.json.bak)- Remote nodes — Each node's auth key via
rotate_keyaction
What gets logged:
- Every panic event is recorded in
.panic_log.jsonwith timestamp and actions taken
Offline nodes:
- Rotation saved to
.panic_pending.json - Retry with
cm panic retry-pending
cm panic --dry-run # Preview without changes
cm panic # Execute immediately
cm panic rotate # Rotate remote nodes only
cm panic retry-pending # Retry failed rotationsLimitation: Panic does NOT wipe data. It only rotates keys. If an attacker already has a copy of the data, key rotation does not help. Old backups with old keys remain readable.
Splits the panic key into 3 shares using Shamir's Secret Sharing. Requires 2 of 3 shares to execute.
File: cloudmesh/core/shamir.py
How it works:
- Uses Shamir's Secret Sharing over GF(p) (Galois Field with prime modulus)
- Splits key into 3 shares, threshold is 2
- Any 2 shares can reconstruct the key
- A single compromised device cannot trigger panic alone
cm panic setup # Generate 3 shares
cm panic shares # View share status
cm panic execute --share 1 --share 2 # Execute with 2-of-3Important: Shares are shown ONCE. Write them down and store each on a separate device.
Limitation: Shares are displayed in plaintext on screen. If someone is watching or recording your screen, the shares are compromised.
Panic rotation works on remote nodes too, not just locally.
File: cloudmesh/core/panic.py — execute_panic() method
Flow:
- Controller generates new auth key for node
- Sends
rotate_keyaction to node viaNodeClient.rotate_key(new_key) - Node updates
.node_keyandself.auth_key - Old key immediately stops working
- If node is offline, rotation is saved to
.panic_pending.json
Limitation: If the network between controller and node is compromised, the new key could be intercepted during transmission (unless TLS is enabled).
Every command is logged with a SHA-256 hash chain. Any tampering breaks the chain.
File: cloudmesh/core/cmdlog.py
How it works:
Entry 1: prev=000...000, hash=SHA256(000...000 + time + cmd)
Entry 2: prev=hash1, hash=SHA256(hash1 + time + cmd)
Entry 3: prev=hash2, hash=SHA256(hash2 + time + cmd)
...
If anyone modifies entry #2, entry #3's prev_hash won't match, and verification fails.
cm cmdlog --verify
# Chain intact — all 42 entries verified.
# Chain BROKEN at entry #17 — possible tampering detected.Log file: command_log.json (max 500 entries, auto-pruned)
Limitation: The log is stored locally on the controller. If the attacker has access to the controller machine, they can delete the log entirely. This detects tampering, not deletion.
File operations on nodes prevent directory traversal attacks.
File: cloudmesh/core/sync.py, cloudmesh/core/transfer.py
# All user input is sanitized:
safe_path = shlex.quote(user_input)
# Prevents: ../../../etc/passwd
# Prevents: /etc/passwd; rm -rf /Limitation: Only applies to file operations. Does not protect against command injection in other contexts.
The REST API is read-only by default. /api/exec returns 403 until the
operator starts it with cm api --enable-exec.
# Read-only API (default)
cm api
# Enable exec explicitly
cm api --enable-exec
# Enable exec and allow only exact commands
cm api --enable-exec --allow-command "uptime" --allow-command "df -h"The existing dangerous-command blocklist remains defence in depth:
BLOCKED = ["rm -rf", "mkfs", "dd if=", ":(){ :|:& };:", "chmod 777", "> /dev/sda"]File: cloudmesh/core/advanced.py — CloudMeshAPI class
Limitation: The blocklist is simple string matching and can be bypassed
with variations. Even with --allow-command, the API is not a sandbox. The
API stays on 127.0.0.1; remote access requires an SSH tunnel.
Database passwords are never stored in config files. They use a per-command temporary file with a cleanup trap:
File: cloudmesh/core/database.py
remote_conf=$(mktemp "${TMPDIR:-/tmp}/cm_my.XXXXXX") || exit 1
trap 'rm -f "$remote_conf"' EXIT HUP INT TERM
umask 077
printf '%s' "<client config>" > "$remote_conf"
chmod 600 "$remote_conf"
mysql --defaults-extra-file="$remote_conf" -e "<sql>"The fixed /tmp/.cm_my.conf and /tmp/.cm_pgpass paths are gone. A failed
mktemp aborts instead of falling back to a predictable path.
Limitation: Password is visible in the SSH command string. If someone has
root access on the remote server, they could see it in process listings
(ps aux).
Node connections can be encrypted with TLS:
cm node start --tls-cert cert.pem --tls-key key.pem
cm node add -n NODE -H host.example -k KEY --tls --ca-file ca.pemWithout TLS, a warning is shown:
WARNING: TLS disabled — traffic is unencrypted. Use --tls-cert/--tls-key for production.
TLS is optional for compatibility with existing nodes. When enabled on both the agent and controller, the controller validates the node certificate using the system trust store or the configured CA file. Use a certificate whose subject matches the configured host.
Limitation: TLS remains opt-in. Without --tls, node traffic is unencrypted.
Multi-user support with role-based permissions.
File: cloudmesh/core/acl.py
Default roles:
admin: Full access (*)viewer: Read-only (monitor,ping,list,info)
Password storage (v2.1.0):
- bcrypt with 12 rounds by default (PBKDF2-SHA256 300k iterations as fallback if bcrypt is unavailable)
- Never stored in plaintext
import bcrypt
salt = bcrypt.gensalt(rounds=12)
pwd_hash = bcrypt.hashpw(password.encode(), salt)Brute-force lockout (v2.1.0):
- 5 consecutive failed attempts → account locked for 15 minutes
- All auth events (success/failure/lockout) are written to
data/acl_audit.log - Enabling a user resets the attempt counter and lock
Limitation: ACL is local only. It does not apply to the node agent or SSH connections. ACL only controls who can use the CLI on the controller machine.
Remote execution and job submission through the node agent are filtered against a blocklist of destructive commands.
File: cloudmesh/node/cloudmesh_node.py
Blocked patterns include: rm -rf /, mkfs, format c:, dd if=, shutdown, reboot, init 0/6, fork bombs (:(){), chmod -R 777 /, and similar destructive operations.
Blocked attempts are logged and return a Command blocked by security policy error without executing.
Upload mode validation: only w, a, wb, ab are accepted as file modes; anything else is rejected.
Firewall rules are validated before being applied.
File: cloudmesh/core/firewall.py
ALLOWED_RULES = {
"allow", "deny", "reject",
"allow 22/tcp", "allow 80/tcp", "allow 443/tcp",
"allow from", "deny from",
"limit", "delete",
"enable", "disable", "status",
"reset", "default deny incoming", "default allow outgoing",
}Unknown or dangerous rules are rejected before reaching the server.
Limitation: The allowlist is not exhaustive. Some valid rules might be rejected. You can bypass it by running iptables directly via SSH.
Webhook configurations validate input format before saving.
File: cloudmesh/core/webhooks.py
# Chat ID must be numeric:
if not chat_id.isdigit():
return "Invalid chat_id format"Plugins execute arbitrary commands. CloudMesh warns about this:
File: cloudmesh/core/plugins.py
SECURITY WARNING: plugins.json executes arbitrary commands.
NEVER import plugins.json from untrusted sources or sync it
from remote/unknown origins. Only add plugins you personally wrote.
- Local plugins emit a runtime
UserWarning - Shell execution uses
shell=True(full shell syntax, including pipes/redirects) - 60-second timeout on all plugin executions
Limitation: Plugins are NOT sandboxed. A malicious plugin can do anything the user running CloudMesh can do.
Be aware of these limitations:
| Scenario | Why |
|---|---|
| Physical access to controller | If attacker has your laptop, they have your keys and config |
| Compromised SSH private key | If private key is stolen, server is accessible without CloudMesh |
| Zero-day in dependencies | paramiko, psutil, cryptography could have undiscovered vulnerabilities |
| Social engineering | If someone tricks you into sharing keys, security features are bypassed |
| Insider threat | A malicious admin with access to the controller can do anything |
| Deleted logs | The command ledger detects tampering, but not deletion |
| Encrypted data at rest | Old backups with old keys remain readable |
| Network-level attacks | Without TLS, node communication is in plaintext |
| Application-layer DDoS | DDoS protection is basic, not designed for sophisticated attacks |
- Keep
.secret.keysafe — if lost, encrypted config is unrecoverable - Use SSH keys, not passwords — keys are more secure and convenient
- Enable SPA — makes your node ports invisible to scanners
- Enable TLS — encrypts node communication
- Run
cm doctorregularly — catches misconfigurations early - Use
cm panic --dry-runfirst — preview before rotating keys - Back up
.secret.key— encrypted config is useless without it
- Run as non-root when possible — reduces blast radius
- Enable TLS — encrypts node communication
- Enable SPA — hides TCP port from scanners
- Keep node agent updated —
cm node installreinstalls latest - Use strong auth keys — 64-char hex (auto-generated)
- Keep the API on localhost — it binds to
127.0.0.1and has no external-bind flag. - Use an SSH tunnel for remote access — not direct port exposure.
- Leave
/api/execdisabled unless a specific workflow needs it. - Prefer
--allow-commandover an unrestricted exec API.
# Safe: local only, read-only
cm api --port 8080
# Safe: explicit exec allowlist
cm api --enable-exec --allow-command "uptime"
# Safe: via SSH tunnel
ssh -L 8080:127.0.0.1:8080 user@server
# Then access at http://localhost:8080On Linux, sensitive files are created with restricted permissions:
| File | Permission | Purpose |
|---|---|---|
.secret.key |
600 (owner-only) | Encryption key |
.node_key |
600 (owner-only) | Node auth key |
config.json |
600 (owner-only) | Encrypted config |
.node_keys.json |
600 (owner-only) | Node key registry |
acl.json |
600 (owner-only) | User credentials |
Limitation: File permissions are best-effort. They do not protect against root-level access or other users with elevated privileges.
| Algorithm | Usage | Notes |
|---|---|---|
| AES-128-CBC | Fernet config encryption | Standard, well-reviewed |
| HMAC-SHA256 | Node auth, SPA packet signing | Timing-safe comparison used |
| SHA-256 | Command ledger hash chain | Standard hashing |
| Shamir's Secret Sharing over GF(p) | Panic key splitting | Mathematical, not custom crypto |
| bcrypt (PBKDF2-HMAC-SHA256 fallback) | ACL password hashing | bcrypt is used when the dependency is available |
Note: The ACL uses bcrypt when bcrypt is installed; the fallback is
PBKDF2-HMAC-SHA256 with 300,000 iterations. Neither replaces a strong,
unique password.
| Issue | Status | Impact |
|---|---|---|
| Plugin execution is not sandboxed | Accepted | Malicious plugins can do anything |
| Command ledger can be deleted (not just tampered) | Accepted | Local log deletion is not detected; the ledger is local tamper evidence, not notarization |
| Database passwords visible in SSH command | Accepted | Process listing exposure on the remote server |
| DDoS protection is basic | Accepted | Not designed for sophisticated attacks |
| Node protocol has no message replay cache | Accepted for 4.1.x | Use TLS; a protocol change is deferred |
docker exec runs the operator's command |
Accepted | The container is the trust boundary; it is the feature, not an injection |
| REST API is HTTP on loopback | Accepted | A local process with the key can call it; remote use requires an SSH tunnel |
Security patches are applied as version updates:
- v4.1.2 LTS: API exec opt-in, per-run API keys, security headers,
verified GitHub-release updates,
mktempdatabase credentials, ledger rotation checkpoint, secret-permission checks, Python 3.11 floor. - v4.0.0: BSD family support and hardening (internal checkpoint).
- v3.5.0: command-surface and dependency fixes.
- v2.0.0: SPA source IP binding, fail-closed SPA, remote panic rotation, SSH tunnel unification.
- v1.4.0: centralized SSH, TLS/SPA warnings, firewall validation, DB temporary files.
Check for updates:
cm update --pip # verified GitHub release wheel
cm doctor # health and permission checks