Back to skill

Security audit

Cloudflare Manager

Security checks for vulnerabilities and agentic risk

Overview

This Cloudflare skill mostly does what it says, but it also exposes broad, under-documented Cloudflare setting changes and privileged host configuration writes that merit careful review before installation.

Install only if you are comfortable giving this skill Cloudflare zone mutation rights and sudo-backed control over cloudflared on the host. Use a narrowly scoped Cloudflare token, prefer dry-run first, review commands before execution, and avoid granting sudo until the missing sudoers guidance, config validation, backups, rollback, and update-setting scoping are addressed.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Unpinned Third-Party Dependencies Create Supply-Chain Risk## Vulnerability Details **File Location**: `requirements.txt:1-2`; `scripts/install.sh:12-13` **Vulnerability Type**: Unpinned executable dependencies **Risk Level**: Medium ### Vulnerable Code `requirements.txt:1-2`: ```text requests PyYAML ``` `scripts/install.sh:12-13`: ```bash "$VENV_DIR/bin/pip" install -U pip "$VENV_DIR/bin/pip" install -r "$SKILL_ROOT/requirements.txt" ``` ### Technical Analysis The installation process retrieves the latest available versions of `pip`, `requests`, `PyYAML`, and their transitive dependencies from the configured Python package index. Neither exact versions nor package hashes are specified. Consequently, the code installed by this Skill can change after the Skill itself has been reviewed. An unconditional pip upgrade further expands the mutable supply-chain surface. If a package-index account, upstream release, configured mirror, or dependency is compromised, a future installation could receive malicious or otherwise unsafe code. Python packages may execute code during package installation and are subsequently imported by `scripts/cf_manager.py`. Therefore, a compromised dependency could execute with the privileges of the user running the setup or manager script. ### Attack Path 1. An attacker compromises an upstream dependency release, package-index account, or package mirror used by pip. 2. The attacker publishes a malicious version that still satisfies the unrestricted dependency declarations. 3. A user or agent invokes `bash scripts/install.sh` as documented by the Skill. 4. Pip resolves and downloads the attacker-controlled release because no reviewed version or hash is enforced. 5. Malicious code executes during installation or when `requests` or `yaml` is imported at runtime. 6. The payload operates with the installing or invoking user's permissions and may access that user's files and environment variables. ### Impact Assessment Successful exploitat ...[truncated 488 chars]
Remediation
## Remediation Suggestions 1. Pin all direct and transitive dependencies to reviewed versions in a lock file. 2. Generate and enforce cryptographic hashes for every downloaded distribution, for example: ```bash "$VENV_DIR/bin/pip" install --require-hashes -r "$SKILL_ROOT/requirements.lock" ``` 3. Remove the unconditional `pip install -U pip` operation or pin pip to a reviewed version with an enforced hash. 4. Generate the lock file using a controlled dependency-resolution process and commit it to the project. 5. Prefer an explicitly trusted package index and prevent fallback to untrusted extra indexes. 6. Periodically scan pinned packages for known vulnerabilities and update them through a reviewed change process. 7. Run installation and runtime operations as an unprivileged, dedicated account without unnecessary access to Cloudflare credentials.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/cf_manager.py:144
Finding
Privileged Cloudflared Configuration Write Is Not Validated## Vulnerability Details **File Location**: `scripts/cf_manager.py:144-149` **Vulnerability Type**: Unchecked privileged file write and non-atomic configuration replacement **Risk Level**: Low ### Vulnerable Code ```python # Write back using sudo tee via subprocess process = subprocess.Popen(['sudo', 'tee', CONFIG_PATH], stdin=subprocess.PIPE, stdout=subprocess.PIPE) process.communicate(input=yaml_str.encode()) # Restart cloudflared subprocess.run(['sudo', 'systemctl', 'restart', 'cloudflared'], check=True) ``` ### Technical Analysis The program writes directly to `/etc/cloudflared/config.yml` through a privileged `sudo tee` process but does not inspect its return code. `process.communicate()` waits for completion but does not raise an exception when `tee` exits unsuccessfully. As a result, the application may proceed to restart `cloudflared` after a failed write. The target is also overwritten directly rather than through a protected temporary file and atomic rename. An interruption, storage failure, or partial write can therefore leave the active configuration truncated or malformed. No cloudflared configuration validation is performed before the service restart. Although `yaml.safe_load` and structured YAML serialization prevent direct shell-command injection in this path, they do not ensure that the resulting configuration is semantically valid for cloudflared. ### Attack Path 1. An authorized user invokes `update-ingress` with a hostname and service value. 2. The manager serializes the modified configuration. 3. The privileged `tee` operation fails, is interrupted, or only partially writes the active configuration due to a permission, storage, or operating-system error. 4. The process exit status is ignored. 5. The manager invokes `sudo systemctl restart cloudflared`. 6. Cloudflared restarts with stale, incomplete, or invalid configuration and may fail to start or expose routes differently from the req ...[truncated 528 chars]
Remediation
## Remediation Suggestions 1. Check the privileged writer's exit status, preferably using: ```python subprocess.run( ["sudo", "tee", CONFIG_PATH], input=yaml_str, text=True, stdout=subprocess.DEVNULL, check=True, ) ``` 2. Do not overwrite the live configuration directly. Write to a root-controlled temporary file in the same filesystem, set restrictive ownership and permissions, and atomically replace the destination only after validation succeeds. 3. Validate the candidate configuration before replacement and restart, for example with the appropriate `cloudflared tunnel ingress validate` command. 4. Preserve a known-good backup and restore it automatically if validation or service restart fails. 5. Verify that `cloudflared` becomes healthy after restart rather than treating successful execution of `systemctl restart` as sufficient. 6. Restrict sudo authorization to narrowly defined helper operations. The documentation references `references/sudoers.example`, but that file is absent from the reviewed project and should be supplied with a least-privilege policy. 7. Log failures without exposing credentials or other sensitive configuration values.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (31)

Tainted flow: 'url' from os.getenv (line 84, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
url = f"https://api.cloudflare.com/client/v4/zones/{CLOUDFLARE_ZONE_ID}/dns_records"
        params = {"per_page": 100}
        response = requests.get(url, headers=self.headers, params=params)
        return response.json()

    def add_dns(self, record_type, name, content, proxied=True, comment=""):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 84, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
if self.dry_run:
            return {"status": "dry-run", "action": "add_dns", "data": data}
            
        response = requests.post(url, headers=self.headers, json=data)
        return response.json()

    def update_dns(self, record_id, record_type, name, content, proxied=True, comment=""):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 84, credential/environment) → requests.put (network output)

Critical
Category
Data Flow
Content
if self.dry_run:
            return {"status": "dry-run", "action": "update_dns", "data": data}

        response = requests.put(url, headers=self.headers, json=data)
        return response.json()

    def delete_dns(self, record_id):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'url' from os.getenv (line 84, credential/environment) → requests.patch (network output)

Critical
Category
Data Flow
Content
if self.dry_run:
            return {"status": "dry-run", "action": "update_setting", "data": data}

        response = requests.patch(url, headers=self.headers, json=data)
        return response.json()

    def update_ingress(self, hostname, service):
Confidence
90% confidence
Finding
Unlike the DNS-specific operations, `update_setting` permits modification of arbitrary Cloudflare zone settings via a free-form `setting_id` and user-supplied value. This broadens the skill's authority beyond the advertised scope and can disable protections or weaken zone security if misused.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The documented behavior does not accurately match the described purpose: the skill claims Zero Trust and tunnel management, but appears to only edit local ingress config and also perform undeclared zone-setting changes. This mismatch is dangerous because operators and higher-level agents may authorize use based on incomplete or incorrect expectations, enabling broader Cloudflare or local-system changes than intended.

Missing User Warnings

High
Confidence
96% confidence
Finding
The code overwrites `/etc/cloudflared/config.yml` and restarts `cloudflared` immediately, without explicit warning, diff review, rollback, or confirmation. In context, this can instantly expose internal services, break existing ingress routing, or cause downtime on the host.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill exposes meaningful capabilities through installation commands, shell invocation, environment-secret use, file reads, network access, and privileged local changes, but it does not declare an explicit tool/permission scope. That weakens policy enforcement and reviewability, making it easier for an agent to invoke sensitive operations without clear guardrails or operator awareness.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
- **Safety**: Use `--dry-run` to preview configuration changes before application.

## Security & Permissions
- **Sudo Usage**: The `update-ingress` command requires `sudo` to write to system directories and restart the `cloudflared` service.
- **Least Privilege**: Configure restricted sudo access using the pattern in `references/sudoers.example`.
- **Token Isolation**: Ensure API tokens are scoped narrowly to specific zones and permissions.
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
## Security & Permissions
- **Sudo Usage**: The `update-ingress` command requires `sudo` to write to system directories and restart the `cloudflared` service.
- **Least Privilege**: Configure restricted sudo access using the pattern in `references/sudoers.example`.
- **Token Isolation**: Ensure API tokens are scoped narrowly to specific zones and permissions.

## Reference
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
## Security & Permissions
- **Sudo Usage**: The `update-ingress` command requires `sudo` to write to system directories and restart the `cloudflared` service.
- **Least Privilege**: Configure restricted sudo access using the pattern in `references/sudoers.example`.
- **Token Isolation**: Ensure API tokens are scoped narrowly to specific zones and permissions.

## Reference
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

External Transmission

Medium
Category
Data Exfiltration
Content
def list_dns(self):
        if err := self._check_creds(): return err
        
        url = f"https://api.cloudflare.com/client/v4/zones/{CLOUDFLARE_ZONE_ID}/dns_records"
        params = {"per_page": 100}
        response = requests.get(url, headers=self.headers, params=params)
        return response.json()
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def list_dns(self):
        if err := self._check_creds(): return err
        
        url = f"https://api.cloudflare.com/client/v4/zones/{CLOUDFLARE_ZONE_ID}/dns_records"
        params = {"per_page": 100}
        response = requests.get(url, headers=self.headers, params=params)
        return response.json()
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def list_dns(self):
        if err := self._check_creds(): return err
        
        url = f"https://api.cloudflare.com/client/v4/zones/{CLOUDFLARE_ZONE_ID}/dns_records"
        params = {"per_page": 100}
        response = requests.get(url, headers=self.headers, params=params)
        return response.json()
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def list_dns(self):
        if err := self._check_creds(): return err
        
        url = f"https://api.cloudflare.com/client/v4/zones/{CLOUDFLARE_ZONE_ID}/dns_records"
        params = {"per_page": 100}
        response = requests.get(url, headers=self.headers, params=params)
        return response.json()
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
def list_dns(self):
        if err := self._check_creds(): return err
        
        url = f"https://api.cloudflare.com/client/v4/zones/{CLOUDFLARE_ZONE_ID}/dns_records"
        params = {"per_page": 100}
        response = requests.get(url, headers=self.headers, params=params)
        return response.json()
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if self.dry_run:
            return {"status": "dry-run", "action": "add_dns", "data": data}
            
        response = requests.post(url, headers=self.headers, json=data)
        return response.json()

    def update_dns(self, record_id, record_type, name, content, proxied=True, comment=""):
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
if self.dry_run:
            return {"status": "dry-run", "action": "update_dns", "data": data}

        response = requests.put(url, headers=self.headers, json=data)
        return response.json()

    def delete_dns(self, record_id):
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
DNS deletion is destructive and is executed immediately without any confirmation, preview, or safety interlock. In an agent setting, mistaken parameters or prompt manipulation could remove production DNS records and cause outages or traffic hijacking opportunities.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The skill claims to manage DNS, tunnels, and Zero Trust policies, but `update_setting` can alter arbitrary zone settings, including security-relevant controls outside that scope. Scope mismatch is dangerous because users may grant trust or credentials assuming narrower capabilities than the code actually has.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
def update_ingress(self, hostname, service):
        """
        Updates cloudflared config.
        REQUIRES: Sudo access on the host.
        Use --dry-run to preview changes without writing.
        """
        try:
Confidence
90% confidence
Finding
The function explicitly requires sudo/root-level access to modify host tunnel configuration, which materially increases the blast radius of the skill. In an agent context, any prompt-driven misuse can now impact the local system rather than just remote Cloudflare resources.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
return {"error": f"Config file not found at {CONFIG_PATH}"}

            # Read current config (requires read access, usually root/cloudflared user)
            # In a typical setup, 'cat' might need sudo if permissions are tight
            try:
                with open(CONFIG_PATH, 'r') as f:
                    config = yaml.safe_load(f)
Confidence
50% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The skill performs host-level privileged file writes and service restarts through `sudo`, which exceeds what many users would expect from a Cloudflare-management capability. This significantly raises risk because compromise or misuse of the skill can affect the local host, not just Cloudflare resources.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
with open(CONFIG_PATH, 'r') as f:
                    config = yaml.safe_load(f)
            except PermissionError:
                # Try reading via sudo cat
                cmd = ["sudo", "cat", CONFIG_PATH]
                result = subprocess.run(cmd, capture_output=True, text=True, check=True)
                config = yaml.safe_load(result.stdout)
Confidence
86% confidence
Finding
Executing `sudo cat` to read a protected system config demonstrates that the skill operates with elevated privileges on the host. While not command injection, it normalizes privileged execution in a broadly callable code path and expands the consequences of misuse.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
except PermissionError:
                # Try reading via sudo cat
                cmd = ["sudo", "cat", CONFIG_PATH]
                result = subprocess.run(cmd, capture_output=True, text=True, check=True)
                config = yaml.safe_load(result.stdout)

            if 'ingress' not in config:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
"new_content_preview": yaml_str
                }

            # Write back using sudo tee via subprocess
            process = subprocess.Popen(['sudo', 'tee', CONFIG_PATH], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
            process.communicate(input=yaml_str.encode())
Confidence
95% confidence
Finding
Using `sudo tee` to overwrite `/etc/cloudflared/config.yml` is a privileged write primitive affecting host networking and service exposure. Because the written content is derived from user-controlled inputs, this can be abused to publish or reroute internal services through Cloudflare Tunnel.

Static analysis

No suspicious patterns detected.