Back to skill

Security audit

Kuaidi100 Package Tracker

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but its public webhook can accept weakly authenticated package updates that may change local package state and trigger Google Calendar changes.

Review this before installing. Use a high-entropy webhook token, set webhook.signatureMode to strict, configure a callback salt, and avoid exposing the webhook publicly unless needed. Be aware that package numbers, notes, delivery status, and latest tracking context may be stored locally and sent to Kuaidi100 or Google Calendar as part of normal operation.

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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
tracker_core.py:169
Finding
Webhook callbacks are accepted without valid signatures by default<![CDATA[ ## Vulnerability Details **File Location**: `tracker_core.py:169-174, 428-458`; `index.ts:42, 64, 173-206`; `README.md:61, 103-106` **Vulnerability Type**: Fail-open webhook authentication **Risk Level**: High ### Vulnerable Code ```python sig = raw.get("sign") or raw.get("signature") or raw.get("saltSign") if not sig: # If kuaidi100 didn't include sign, accept (token-in-path still gates ingress) return True, "no_signature_in_payload" ``` ```python ok, reason = _verify_push_signature(push_data) # Fail-closed option: require signature to be present and valid. # We treat "no_signature_in_payload" as failure in strict mode. if KUAIDI100_SIGNATURE_MODE == "strict" and (not ok or reason.startswith("no_signature")): return { "error": "signature_invalid", "signature_verified": bool(ok), "signature_reason": reason, } parsed = parse_push_payload(push_data) if not parsed: return {"error": "could_not_parse_push", "raw": push_data} parsed["signature_verified"] = bool(ok) parsed["signature_reason"] = reason number = parsed["number"] state = load_state() existing = state["packages"].get(number, {}) parsed["note"] = existing.get("note", "") parsed["subscribed"] = existing.get("subscribed", True) state["packages"][number] = parsed save_state(state) if parsed["today_delivery"] and not parsed["is_completed"]: cal_result = _do_sync_calendar(state) ``` ```typescript KUAIDI100_SIGNATURE_MODE: (webhook.signatureMode || "soft").trim(), ``` ### Technical Analysis Webhook authenticity verification is fail-open under the default `soft` signature mode. `_verify_push_signature` explicitly treats a missing signature as successful, while invalid signatures are only rejected when the administrator has selected `strict` mode. Consequently, callback data can proceed to parsing, persistent state replacement, and automatic Google Calendar synchronization even when its origin has not been cryptographically authentica ...[truncated 1972 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Change the default signature mode from `soft` to `strict`. 2. Reject every callback with a missing or invalid signature before parsing, persistence, or Calendar synchronization. 3. Require a non-empty, cryptographically random webhook token before registering a publicly reachable route. 4. Validate configuration at startup and refuse webhook activation if neither strict signature verification nor another strong authentication mechanism is available. 5. Return an appropriate `401` or `403` response for authentication failures rather than always acknowledging the callback as successful. 6. Use a dedicated callback salt rather than silently falling back to the path token. 7. Document secret rotation procedures and recommend high-entropy tokens. 8. Add tests covering missing signatures, malformed signatures, invalid salts, empty tokens, and unauthorized attempts to trigger Calendar synchronization. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.ts:88
Finding
Unbounded webhook body buffering permits memory-exhaustion denial of service<![CDATA[ ## Vulnerability Details **File Location**: `index.ts:88-95, 193-194` **Vulnerability Type**: Unbounded HTTP request-body allocation **Risk Level**: Medium ### Vulnerable Code ```typescript function readBody(req: IncomingMessage): Promise<string> { return new Promise((resolve, reject) => { let data = ""; req.on("data", (chunk: Buffer) => { data += chunk.toString(); }); req.on("end", () => resolve(data)); req.on("error", reject); }); } ``` ```typescript const body = await readBody(req); const result = await callCore(cfg(), "handle_push", { push_body: body }) as any; ``` ### Technical Analysis The webhook handler concatenates every received chunk into a JavaScript string without enforcing a maximum byte count. This allows a remote client to make the Node.js process allocate memory proportional to the request size. The request-count rate limiter does not address this issue because a single request can contain an arbitrarily large body. Moreover, the implementation notes that clients behind a tunnel can share the same observed proxy address, making the limiter a coarse control rather than a reliable per-client defense. After buffering, the body is serialized again into the Python subprocess arguments, introducing additional memory and CPU overhead. Extremely large arguments may also exceed operating-system command-line limits and generate repeated subprocess failures. ### Attack Path 1. An attacker reaches the public webhook route. 2. The attacker opens one or more HTTP POST requests. 3. Each request streams a very large body or sends data slowly over an extended period. 4. `readBody` continuously appends received chunks without checking cumulative size. 5. The Node.js process consumes increasing memory and CPU. 6. If the request completes, the oversized body is also serialized and passed toward a Python subprocess, causing additional resource consumption or an argument-size failure. 7. Concurrent requests can exhaust availa ...[truncated 490 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a small maximum webhook body size appropriate for Kuaidi100 payloads, such as 64 KB or another documented upper bound. 2. Track raw bytes rather than JavaScript character count. 3. Immediately return HTTP `413 Payload Too Large` and destroy or drain the request when the limit is exceeded. 4. Reject requests with an oversized `Content-Length` before reading the body, while still enforcing a streamed limit because that header can be absent or false. 5. Configure header, request, and idle timeouts to mitigate slow-body attacks. 6. Apply authentication before expensive parsing and subprocess execution wherever the framework permits. 7. Add concurrency controls and byte-aware rate limiting. 8. Avoid passing large webhook bodies through command-line arguments; use bounded standard input or an authenticated local IPC mechanism. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
tracker_core.py:57
Finding
Package tracking state is stored without explicitly restrictive file permissions<![CDATA[ ## Vulnerability Details **File Location**: `tracker_core.py:30-33, 57-65` **Vulnerability Type**: Insecure local storage permissions **Risk Level**: Low ### Vulnerable Code ```python STATE_FILE = Path(os.environ.get( "TRACKER_STATE_FILE", os.path.expanduser("~/.openclaw/workspace/package-tracker-state.json"), )) ``` ```python def load_state() -> dict: if STATE_FILE.exists(): try: return json.loads(STATE_FILE.read_text()) except Exception: pass return {"packages": {}} def save_state(state: dict) -> None: STATE_FILE.parent.mkdir(parents=True, exist_ok=True) STATE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2)) ``` ### Technical Analysis The state file contains tracking numbers, user notes, carrier information, timestamps, delivery status, and latest tracking context. The code creates the parent directory and writes the file without explicitly setting restrictive permission modes. Actual permissions therefore depend on the process umask and any pre-existing directory or file permissions. On a system with a permissive umask, or where the configured state path points into a shared directory, other local users may be able to read or alter this information. The write is also non-atomic. A crash during `write_text` can leave a partial file, after which `load_state` silently returns an empty package state. ### Attack Path 1. The OpenClaw process runs with a permissive umask or uses a state path in a directory accessible to other local users. 2. The Skill creates or updates `package-tracker-state.json` without enforcing owner-only permissions. 3. Another local account reads the file to obtain tracking and delivery metadata. 4. If write access is also available, that account modifies package state or replaces the file. 5. Subsequent Skill operations consume the modified state and may expose incorrect package information or use it during Calendar synchronization. ### Im ...[truncated 433 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the state directory with owner-only mode `0700`. 2. Create the state file with mode `0600`, and explicitly correct permissions on pre-existing files where appropriate. 3. Validate that the configured state path is not a symbolic link and is owned by the expected user before writing. 4. Write state atomically by creating a secure temporary file in the same directory, applying mode `0600`, flushing and syncing it, and then replacing the destination with `os.replace`. 5. Avoid silently treating every read or parse error as empty state; log a sanitized error and preserve recoverable data. 6. Document that the custom state path must reside in a private directory. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (29)

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
            method="POST",
        )
        with urllib.request.urlopen(req, timeout=20) as resp:
            return json.loads(resp.read().decode())
    except Exception as e:
        return {"error": str(e)}
Confidence
90% confidence
Finding
The code sends tracking numbers and callback configuration to Kuaidi100, an external service, as part of subscription setup. In this skill's context that may be functional, but it is still a privacy-relevant external transmission and there is no evidence in this file of consent or data-minimization around sharing package identifiers and webhook details.

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = urllib.request.Request(url, data=json.dumps(body).encode(), headers=headers, method="POST")
        action = "created"

    with urllib.request.urlopen(req, timeout=15) as resp:
        result = json.loads(resp.read())
        return {"action": action, "event_id": result.get("id"), "summary": body["summary"]}
Confidence
90% confidence
Finding
The code automatically uploads package details, including notes and delivery context, into Google Calendar whenever a shipment is inferred to be out for delivery. This creates a real privacy risk because potentially sensitive shipment metadata is transmitted to a third party and persisted in calendar entries without any confirmation gate in this component.

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
            method="POST",
        )
        with urllib.request.urlopen(req, timeout=20) as resp:
            return json.loads(resp.read().decode())
    except Exception as e:
        return {"error": str(e)}
Confidence
90% confidence
Finding
The Kuaidi100 subscription request is sent to an endpoint fully configurable by the KUAIDI100_SUB_ENDPOINT environment variable, and the request body includes sensitive API material and webhook registration data. If that environment variable is tampered with, the skill can exfiltrate credentials or send privileged subscription requests to an attacker-controlled host, making this more dangerous because the code handles third-party API secrets.

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=15) as resp:
        return json.loads(resp.read())["access_token"]
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=15) as resp:
        return json.loads(resp.read())["access_token"]
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=15) as resp:
        return json.loads(resp.read())["access_token"]
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=15) as resp:
        return json.loads(resp.read())["access_token"]
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=15) as resp:
        return json.loads(resp.read())["access_token"]
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README explains subscription to Kuaidi100 push updates and webhook processing, but it does not plainly disclose that tracking numbers and shipment status data are sent to Kuaidi100 and received through a public webhook workflow. This can expose personal shipment metadata to external services and infrastructure, which is especially relevant because package tracking numbers and delivery states can reveal user habits, location patterns, and expected deliveries.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README explains subscription to Kuaidi100 push updates and webhook processing, but it does not plainly disclose that tracking numbers and shipment status data are sent to Kuaidi100 and received through a public webhook workflow. This can expose personal shipment metadata to external services and infrastructure, which is especially relevant because package tracking numbers and delivery states can reveal user habits, location patterns, and expected deliveries.

Missing User Warnings

Medium
Confidence
80% confidence
Finding
The skill defines and uses a local state file for package-tracker data, which implies file writes to the user's workspace state area. In this JavaScript file, there is no confirmation prompt or direct user-facing warning that package data will be stored locally.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
Webhook handling automatically calls the Python core to process push data and may sync delivery reminders to Google Calendar, transmitting package-related data to an external service. Although the plugin description mentions auto-adding reminders, this code path provides no explicit user-facing warning at the time of automatic transmission.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
Several tool descriptions specify only Chinese example utterances such as 'Use when user says: 给我加个快递单号' and similar phrases. This creates a natural-language locale bias without explicit user opt-in or a documented justification that the skill is restricted to Chinese-language interactions.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The list_packages tool uses only Chinese example requests for invocation guidance, reinforcing a Chinese-only interaction model. Without explicit opt-in or documented regional scope, this conflicts with language/locale neutrality expectations.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The sync_to_calendar tool description provides only Chinese trigger examples. This indicates a forced locale in natural-language guidance without user choice or an explicit, justified locale constraint.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The remove_tracking_number tool description again restricts example user phrasing to Chinese only. This is a natural-language locale policy concern unless the skill explicitly documents a Chinese-only scope.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill persists package data locally, including tracking numbers, notes, delivery status, and raw status text, without any disclosure in this file. While local persistence is common, undisclosed storage of potentially sensitive shipment information increases privacy risk, especially in shared environments or when the default path is under a user home directory.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This code transmits package metadata and user notes to Google Calendar, which is a third-party service, without any visible user warning or approval flow in this file. Because notes may contain arbitrary personal information, the privacy impact is higher than a generic calendar sync.

Natural-Language Policy Violations

Medium
Confidence
86% confidence
Finding
Several tool descriptions specify activation examples only in Chinese, such as 'Use when user says' followed exclusively by Chinese phrases. This creates a natural-language policy concern because the skill appears to assume or require a specific language without explicit user opt-in or a documented locale limitation.

Natural-Language Policy Violations

Medium
Confidence
89% confidence
Finding
The manifest text says the skill tracks "Chinese domestic packages" and the schedule configuration defaults the timezone to "Asia/Shanghai" without presenting this as a user choice. Under the policy rule, forcing a specific locale is a natural-language policy concern unless it is clearly documented as a justified region-specific tool or offered as opt-in.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The plugin exposes a public webhook and documents a default signature mode of "soft," which effectively allows callbacks to be accepted without strict signature validation. For an internet-reachable endpoint that can influence package state and trigger downstream actions like calendar reminder creation, this weak authentication increases the risk of spoofed requests, event injection, and abuse of the exposed callback surface.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The code defaults `TIMEZONE_NAME` to `Asia/Shanghai`, imposing a specific locale setting when the user has not explicitly chosen one. This matches the policy category for language/locale constraints because the skill behavior is region-specific by default rather than opt-in.

Tainted flow: 'STATE_FILE' from os.environ.get (line 31, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
def save_state(state: dict) -> None:
    STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
    STATE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2))


# ── Kuaidi100 subscribe API ────────────────────────────────────────────────────
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'STATE_FILE' from os.environ.get (line 31, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Content
def save_state(state: dict) -> None:
    STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
    STATE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2))


# ── Kuaidi100 subscribe API ────────────────────────────────────────────────────
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

External Transmission

Medium
Category
Data Exfiltration
Content
# ── Kuaidi100 subscribe API ────────────────────────────────────────────────────
# Docs: https://api.kuaidi100.com/document/subscribeApi
# Sign = MD5(param + key + customer).upper()
# Endpoint: POST https://poll.kuaidi100.com/poll
# Form fields: schema=json, param=<json>, sign=<sign>, key=<customer>
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
dist/index.js:34

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
index.ts:66