Back to skill

Security audit

CleanApp Report Submission

Security checks for vulnerabilities and agentic risk

Overview

The skill has a legitimate CleanApp report-submission purpose, but its helper code can execute unintended local commands and its configurable endpoint can send the API token and reports outside the documented CleanApp HTTPS endpoint.

Install only if you trust the publisher and can keep CLEANAPP_API_TOKEN scoped and revocable. Prefer the Python bulk ingester with --dry-run, --no-media, and --approx-location or --no-location, avoid the shell helper until fixed, and do not set CLEANAPP_BASE_URL to any non-CleanApp or non-HTTPS endpoint.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/submit_report.sh:132
Finding
Command Injection in the Single-Item Shell Helper<![CDATA[ ## Vulnerability Details **File Location**: `scripts/submit_report.sh:132-157`; identical vulnerable code is present in `openclaw-skill/scripts/submit_report.sh:132-157` **Vulnerability Type**: Shell command injection caused by malformed heredoc argument placement **Risk Level**: High ### Vulnerable Code ```bash payload="$( python3 - <<PY import json import sys payload = { "items": [ { "source_id": sys.argv[1], "title": sys.argv[2], "description": sys.argv[3], "collected_at": sys.argv[4], "agent_id": sys.argv[5], "agent_version": sys.argv[6], "source_type": sys.argv[7], } ] } lat = sys.argv[8] lng = sys.argv[9] if lat.strip() and lng.strip(): payload["items"][0]["lat"] = float(lat) payload["items"][0]["lng"] = float(lng) print(json.dumps(payload, ensure_ascii=False, separators=(",", ":"))) PY "${SOURCE_ID}" "${TITLE}" "${DESCRIPTION}" "${COLLECTED_AT}" "${AGENT_ID}" "${AGENT_VERSION}" "${SOURCE_TYPE}" "${LAT}" "${LNG}" )" ``` The coordinate-rounding blocks at `scripts/submit_report.sh:109-121` and the corresponding packaged copy use the same malformed pattern. ### Technical Analysis Arguments for a command that consumes a heredoc must appear on the command line before the heredoc redirection. In this implementation, the heredoc terminator ends the `python3 -` command, and the following expanded values are parsed by Bash as a separate command. As a result: 1. The Python process does not receive the expected `sys.argv[1:]` values and normally raises an `IndexError`. 2. In ordinary non-POSIX Bash configurations, `errexit` is not inherited inside command substitutions. Execution can therefore continue after the Python failure. 3. Bash treats the attacker-controlled `SOURCE_ID` value as the executable name and the remaining user-controlled fields as its arguments. This permits arbitrary command execution in common Bash configurations. It also makes the documented single-item helper functio ...[truncated 1454 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Move all Python arguments before the heredoc redirection and quote the heredoc delimiter: ```bash payload="$( python3 - \ "$SOURCE_ID" \ "$TITLE" \ "$DESCRIPTION" \ "$COLLECTED_AT" \ "$AGENT_ID" \ "$AGENT_VERSION" \ "$SOURCE_TYPE" \ "$LAT" \ "$LNG" <<'PY' import json import sys payload = { "items": [{ "source_id": sys.argv[1], "title": sys.argv[2], "description": sys.argv[3], "collected_at": sys.argv[4], "agent_id": sys.argv[5], "agent_version": sys.argv[6], "source_type": sys.argv[7], }] } lat = sys.argv[8] lng = sys.argv[9] if lat.strip() and lng.strip(): payload["items"][0]["lat"] = float(lat) payload["items"][0]["lng"] = float(lng) print(json.dumps(payload, ensure_ascii=False, separators=(",", ":"))) PY )" ``` Apply the same correction to both coordinate-rounding blocks: ```bash LAT="$( python3 - "$LAT" "$APPROX_DECIMALS" <<'PY' import sys print(round(float(sys.argv[1]), int(sys.argv[2]))) PY )" ``` Additional hardening should include: - Applying the fix to both copies of the shell helper. - Validating latitude, longitude, decimal count, source type, timestamp, and identifier lengths before use. - Adding shell regression tests with values such as `/bin/sh`, `-c`, spaces, newlines, command substitutions, and shell metacharacters. - Running `shellcheck` in continuous integration. - Preferably replacing the duplicate shell implementation with a call to the already structured Python implementation to reduce attack surface. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
ingest.py:57
Finding
Bearer Token and Report Data Can Be Sent to an Arbitrary Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `ingest.py:57-63,70-71,105-110` and `scripts/submit_report.sh:14,84,177-183`; identical behavior is present under `openclaw-skill/` **Vulnerability Type**: Unrestricted destination selection for authenticated sensitive-data transmission **Risk Level**: Medium ### Vulnerable Code Python implementation: ```python def post_json(url: str, token: str, payload: Dict[str, Any], timeout_sec: int) -> Tuple[int, str]: data = json.dumps(payload).encode("utf-8") req = urllib.request.Request(url, data=data, method="POST") req.add_header("content-type", "application/json") req.add_header("authorization", f"Bearer {token}") req.add_header("user-agent", "cleanapp-ingest-skill/1.0.1") with urllib.request.urlopen(req, timeout=timeout_sec) as resp: body = resp.read().decode("utf-8", errors="replace") return int(resp.status), body ``` ```python ap.add_argument("--base-url", default=os.environ.get("CLEANAPP_BASE_URL", "https://live.cleanapp.io"), help="Base URL for CleanApp report-listener (default: https://live.cleanapp.io)") ``` ```python payload = {"items": items} url = args.base_url.rstrip("/") + "/v1/reports:bulkIngest" if args.dry_run: out = { "url": url, "ts": utc_now_iso(), "items": items, } print(json.dumps(out, indent=2, ensure_ascii=False)) return 0 status, body = post_json(url, token, payload, timeout_sec=args.timeout) ``` Shell implementation: ```bash BASE_URL="${CLEANAPP_BASE_URL:-https://live.cleanapp.io}" ``` ```bash --base-url) BASE_URL="$2"; shift 2 ;; ``` ```bash echo "Submitting 1 item to ${url} (source_id=${SOURCE_ID})" resp="$( curl -sS -w "\n%{http_code}" \ -X POST "${url}" \ -H "authorization: Bearer ${CLEANAPP_API_TOKEN}" \ -H "content-type: application/json" \ -d "${payload}" )" ``` ### Technical Analysis Both implementations accept a base URL from a command-line argument or envi ...[truncated 2234 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Enforce a strict destination policy before attaching the bearer token: 1. Parse the base URL with `urllib.parse.urlsplit`. 2. Require the `https` scheme. 3. Allowlist the intended production hostname, such as `live.cleanapp.io`. 4. Reject embedded usernames or passwords, fragments, query strings, unexpected paths, and unapproved ports. 5. Construct the endpoint from validated URL components rather than string concatenation. 6. Apply equivalent checks in the shell helper. 7. Do not forward authorization headers across host-changing redirects; preferably disable redirects or validate every redirect target. 8. If development or self-hosted endpoints are required, use a separate explicitly named development token and require an explicit opt-in configuration. Example Python validation: ```python from urllib.parse import urlsplit def validated_base_url(value: str) -> str: parsed = urlsplit(value) if parsed.scheme != "https": raise SystemExit("base URL must use HTTPS") if parsed.hostname != "live.cleanapp.io": raise SystemExit("untrusted CleanApp API host") if parsed.username or parsed.password: raise SystemExit("credentials are not allowed in the base URL") if parsed.port not in (None, 443): raise SystemExit("unexpected API port") if parsed.query or parsed.fragment: raise SystemExit("query strings and fragments are not allowed") if parsed.path not in ("", "/"): raise SystemExit("base URL must not contain a path") return "https://live.cleanapp.io" ``` The same policy must be applied to the duplicated files under `openclaw-skill/`. Tests should verify rejection of HTTP URLs, lookalike domains, user-info host confusion, nonstandard ports, and cross-origin redirects. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (5)

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This skill sends user-provided items, potentially including sensitive fields like media and precise location unless optional flags are set, to a remote service without any explicit interactive warning or confirmation at the point of transmission. In a CLI ingestion tool, this can lead to unintended disclosure because the default behavior is to upload data immediately once invoked, and the privacy-reducing options are optional rather than enforced by default.

External Transmission

Medium
Category
Data Exfiltration
Content
Example:

```bash
curl -fsS -H 'content-type: application/json' \\
  -d '{"name":"cleanapp-agent001","owner_type":"openclaw"}' \\
  https://live.cleanapp.io/v1/fetchers/register
```
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
Example:

```bash
curl -fsS -H 'content-type: application/json' \\
  -d '{"name":"cleanapp-agent001","owner_type":"openclaw"}' \\
  https://live.cleanapp.io/v1/fetchers/register
```
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
echo "Submitting 1 item to ${url} (source_id=${SOURCE_ID})"
resp="$(
  curl -sS -w "\n%{http_code}" \
    -X POST "${url}" \
    -H "authorization: Bearer ${CLEANAPP_API_TOKEN}" \
    -H "content-type: application/json" \
Confidence
70% 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
echo "Submitting 1 item to ${url} (source_id=${SOURCE_ID})"
resp="$(
  curl -sS -w "\n%{http_code}" \
    -X POST "${url}" \
    -H "authorization: Bearer ${CLEANAPP_API_TOKEN}" \
    -H "content-type: application/json" \
Confidence
70% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.