Back to skill

Security audit

Cloudflare Agent Tunnel

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it advertises, but it can persistently expose a local OpenClaw service to the internet and runs the tunnel as a root system service without enough access-control guidance.

Install only if you intend to administer a VPS and deliberately publish the selected OpenClaw agent port through Cloudflare. Before using it, require application authentication or Cloudflare Access, verify the local port is the intended OpenClaw service, avoid quick tunnels for sensitive services, and prefer a dedicated non-root cloudflared user with hardened systemd settings and documented cleanup/revocation steps.

Vulnerability Patterns
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (2)

T06 · System Persistence

Error
Location
scripts/tunnel-setup.sh:97
Finding
Reboot-Persistent Cloudflare Tunnel Runs with Unnecessary Root Privileges<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tunnel-setup.sh:97-117` **Additional Locations**: `SKILL.md:96-118`, `references/custom-domains.md:135-154` **Vulnerability Type**: System-level persistence and violation of least privilege **Risk Level**: High ### Complete Code Snippet ```bash # Create systemd service SERVICE_NAME="cloudflared-${AGENT}" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" cat > "$SERVICE_FILE" << EOF [Unit] Description=Cloudflare Tunnel for OpenClaw agent: ${AGENT} After=network.target [Service] Type=simple User=root ExecStart=/usr/bin/cloudflared tunnel --no-autoupdate --config ${CONFIG_FILE} run Restart=always RestartSec=5 [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable "$SERVICE_NAME" systemctl restart "$SERVICE_NAME" ``` The documented equivalent in `SKILL.md` also explicitly enables the persistent service: ```bash systemctl daemon-reload systemctl enable cloudflared-koda systemctl start cloudflared-koda systemctl is-active cloudflared-koda ``` The multi-agent instructions in `references/custom-domains.md` enable several persistent services: ```bash systemctl daemon-reload systemctl enable cloudflared-koda cloudflared-alex cloudflared-jordan systemctl start cloudflared-koda cloudflared-alex cloudflared-jordan ``` ### Technical Analysis The setup writes a system-level unit into `/etc/systemd/system`, enables it for startup under `multi-user.target`, and configures `Restart=always`. The resulting Cloudflare tunnel survives completion of the Skill, automatically starts after reboot, and reconnects whenever it exits. Cross-session persistence is relevant to the declared permanent-tunnel feature and is openly documented rather than concealed. Nevertheless, the implementation exceeds minimum necessary privileges because `cloudflared` is explicitly run as `root`. A tunnel process generally only needs permission to read its own configuration and credential file and ...[truncated 1834 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create a dedicated system account with no interactive login: ```bash useradd --system --home /var/lib/cloudflared --create-home \ --shell /usr/sbin/nologin cloudflared ``` 2. Store each tunnel credential outside `/root`, make it readable only by the dedicated account, and use restrictive permissions: ```bash install -d -o root -g cloudflared -m 0750 /etc/cloudflared install -o root -g cloudflared -m 0640 \ /root/.cloudflared/TUNNEL_UUID.json \ /etc/cloudflared/TUNNEL_UUID.json ``` 3. Run the service as the dedicated account: ```ini [Service] User=cloudflared Group=cloudflared ExecStart=/usr/bin/cloudflared tunnel --no-autoupdate --config /etc/cloudflared/openclaw-koda.yml run ``` 4. Add systemd sandboxing and privilege restrictions: ```ini NoNewPrivileges=true PrivateTmp=true PrivateDevices=true ProtectSystem=strict ProtectHome=true ProtectKernelTunables=true ProtectKernelModules=true ProtectControlGroups=true RestrictSUIDSGID=true LockPersonality=true CapabilityBoundingSet= AmbientCapabilities= ReadOnlyPaths=/etc/cloudflared ``` 5. Ask for explicit operator confirmation before writing or enabling a persistent service. Offer a foreground or non-enabled service mode where cross-reboot persistence is unnecessary. 6. Use `systemctl enable --now` only after configuration validation succeeds, and document complete cleanup: ```bash systemctl disable --now cloudflared-koda rm -f /etc/systemd/system/cloudflared-koda.service systemctl daemon-reload systemctl reset-failed ``` 7. Document credential revocation and tunnel deletion as part of uninstalling the Skill-created resources. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tunnel-setup.sh:76
Finding
Local OpenClaw Service Is Published to the Internet Without Requiring Access Control<![CDATA[ ## Vulnerability Details **File Location**: `scripts/tunnel-setup.sh:76-92` **Additional Location**: `SKILL.md:67-92` **Vulnerability Type**: Unsafe public exposure of a local service **Risk Level**: High ### Complete Code Snippet ```bash CREDS_FILE="${CREDS_DIR}/${TUNNEL_UUID}.json" # Write config file cat > "$CONFIG_FILE" << EOF tunnel: ${TUNNEL_UUID} credentials-file: ${CREDS_FILE} ingress: - hostname: ${DOMAIN} service: http://localhost:${PORT} - service: http_status:404 EOF echo "Config written: ${CONFIG_FILE}" # Route DNS (adds CNAME record in Cloudflare dashboard) echo "Routing DNS: ${DOMAIN} → ${TUNNEL_NAME}..." cloudflared tunnel route dns "$TUNNEL_NAME" "$DOMAIN" || { echo "" echo "⚠ DNS routing failed — you may need to add this CNAME manually in Cloudflare:" echo " Name: $(echo $DOMAIN | cut -d. -f1)" echo " Target: ${TUNNEL_UUID}.cfargotunnel.com" echo " Proxy: ON (orange cloud)" } ``` ### Technical Analysis The generated ingress rule forwards requests for the selected public hostname directly to `http://localhost:${PORT}`. The script then creates, or instructs the operator to create, a public DNS record for that tunnel. The workflow does not require or validate an authentication mechanism for the OpenClaw endpoint. It also does not configure a Cloudflare Access policy, identity-aware proxy rule, mutual TLS requirement, or another deny-by-default access layer before publishing the hostname. The documented `allowedOrigins` setting controls browser-origin behavior such as CORS; it is not an authentication or authorization boundary. Adding the public hostname to `allowedOrigins` therefore does not prevent unauthorized clients from directly reaching the service. The tunnel avoids opening an inbound VPS firewall port, but that does not make the application private. Cloudflare accepts internet traffic for the hostname and relays it through the outbound tunnel to the localhost service. ### Attack Path 1. An o ...[truncated 1455 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require explicit confirmation that the selected local service is intended for public routing. Clearly state that a Cloudflare Tunnel is publicly reachable unless an access policy or application authentication restricts it. 2. Verify that the target port belongs to the expected OpenClaw process before creating DNS or starting the service. Reject unexpected listeners rather than forwarding an arbitrary localhost port. 3. Require strong OpenClaw authentication and authorization before publishing the hostname. Do not treat `allowedOrigins` as an access-control mechanism. 4. Configure Cloudflare Access with a deny-by-default policy before activating the DNS route. Restrict access to approved identities, groups, service tokens, or device posture rules. 5. Consider mutual TLS or Cloudflare Access service tokens for non-browser and machine-to-machine endpoints. 6. Bind the OpenClaw origin exclusively to a loopback address and verify that it is not listening on public interfaces: ```bash ss -lntp | grep ":${PORT}" ``` 7. Separate tunnel creation from publication. Create and validate the tunnel first, configure access controls second, and only then create the public DNS record and start the persistent service. 8. Add a post-deployment test that unauthenticated requests are denied. Abort or disable the service if the public endpoint returns protected application content without authentication. 9. Validate `DOMAIN` and `PORT` strictly before writing configuration. Accept only valid hostnames and numeric ports in the range `1-65535`. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (12)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
# Delete a tunnel
cloudflared tunnel delete openclaw-koda
systemctl disable cloudflared-koda && rm /etc/systemd/system/cloudflared-koda.service
```
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Credential Access

High
Category
Privilege Escalation
Content
# Install cloudflared if missing
if ! command -v cloudflared &>/dev/null; then
  echo "Installing cloudflared..."
  curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
  echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" \
    | tee /etc/apt/sources.list.d/cloudflared.list
  apt-get update -qq && apt-get install -y cloudflared
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Install cloudflared if missing
if ! command -v cloudflared &>/dev/null; then
  echo "Installing cloudflared..."
  curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
  echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" \
    | tee /etc/apt/sources.list.d/cloudflared.list
  apt-get update -qq && apt-get install -y cloudflared
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Install cloudflared if missing
if ! command -v cloudflared &>/dev/null; then
  echo "Installing cloudflared..."
  curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
  echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" \
    | tee /etc/apt/sources.list.d/cloudflared.list
  apt-get update -qq && apt-get install -y cloudflared
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
# Install cloudflared if missing
if ! command -v cloudflared &>/dev/null; then
  echo "Installing cloudflared..."
  curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
  echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main" \
    | tee /etc/apt/sources.list.d/cloudflared.list
  apt-get update -qq && apt-get install -y cloudflared
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill contains extensive shell commands that install packages, create privileged config files, modify systemd units, and change firewall state, yet it declares no tool restrictions. In an agent setting, missing scope increases the chance the skill could be invoked with broader-than-necessary execution privileges, enabling unintended system modification.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill tells the operator to change firewall policy with `ufw deny 18789` but does not clearly warn about validating the management path first or checking existing remote-access rules. Firewall changes on a VPS can accidentally cut off required access paths or break the application if the tunnel is not yet confirmed working.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The quick-tunnel path publishes a localhost service to a public Cloudflare URL with no authentication, access control, or prominent warning about external exposure. In the context of an agent skill designed to expose local agent ports, this can unintentionally make sensitive local services internet-accessible, especially if users assume localhost implies private access.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This shell script writes configuration under /etc, creates a systemd unit, installs packages via apt-get, and enables/restarts a root-run service. Although the actions are echoed as they occur, the script lacks a clear upfront warning that it will modify system configuration and persist a background service, which is a safety-relevant disclosure for a code file.

Session Persistence

Medium
Category
Rogue Agent
Content
EOF

systemctl daemon-reload
systemctl enable "$SERVICE_NAME"
systemctl restart "$SERVICE_NAME"
sleep 3
systemctl status "$SERVICE_NAME" --no-pager | head -10
Confidence
80% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
EOF

systemctl daemon-reload
systemctl enable "$SERVICE_NAME"
systemctl restart "$SERVICE_NAME"
sleep 3
systemctl status "$SERVICE_NAME" --no-pager | head -10
Confidence
80% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
EOF

systemctl daemon-reload
systemctl enable "$SERVICE_NAME"
systemctl restart "$SERVICE_NAME"
sleep 3
systemctl status "$SERVICE_NAME" --no-pager | head -10
Confidence
80% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Static analysis

No suspicious patterns detected.