Back to skill

Security audit

Sf Express

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent SF Express helper, but it handles sensitive shipment data with automatic local retention and under-scoped network destinations that users should review before installing.

Install only if you are comfortable with shipment identifiers and tracking details being sent to the endpoint you configure and retained locally in plaintext history. Prefer a trusted HTTPS endpoint, avoid shared machines, clear history when finished, and update the pinned dependencies before relying on the skill for sensitive shipments.

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

T09 · Insecure Skill Coding Practices

Warning
Location
sfexpress.py:308
Finding

Shipment details are persisted in a plaintext SQLite database

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
sfexpress.py:188
Finding

Tracking numbers can be disclosed to arbitrary configured or redirected hosts

Content
View full analysis
TrackingResult: """Query a user-configured live endpoint. The endpoint may contain `{tracking_number}` or accept `tracking_number` as a query parameter. The response must be JSON with either a top-level `events` list or a `data.events` list. """ if "{tracking_number}" in endpoint: url = endpoint.replace("{tracking_number}", tracking_number) else: separator = "&" if "?" in endpoint else "?" url = endpoint + separator + urlencode({"tracking_number": tracking_number}) self._validate_live_endpoint_url(url) try: payload = await asyncio.to_thread(self._fetch_live_json, url) except Exception as exc: raise RuntimeError(f"真实顺丰查询端点请求失败: {exc}") from exc data = payload.get("data", payload) if isinstance(payload, dict) else {} events_raw = data.get("events") if isinstance(data, dict) else None if not isinstance(events_raw, list): raise RuntimeError("真实查询端点返回格式无效:缺少 events 列表") events: List[TrackingEvent] = [] for item in events_raw: if not isinstance(item, dict): continue events.append(TrackingEvent( time=str(item.get("time") or item.get("timestamp") or ""), description=str(item.get("description") or item.get("desc") or item.get("status") or ""), location=str(item.get("location") or ""), status=str(item.get("status") or data.get("status") or "unknown"), )) if not events: raise RuntimeError("真实查询端点返回格式无效:events 为空") return TrackingResult( tracking_number=str(data.get("tracking_number") or tracking_number), product_name=str(data.get("p ...[truncated 3519 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
security.py:60
Finding

Unvalidated storage filenames permit path traversal outside the secure directory

Content
View full analysis
dict: """加载加密JSON文件""" filepath = self.base_dir / filename if not filepath.exists(): return {} with open(filepath, 'r', encoding='utf-8') as f: encrypted = f.read() json_str = self.decrypt(encrypted) return json.loads(json_str) def save_file(self, filename: str, content: str): """保存加密文件""" filepath = self.base_dir / filename encrypted = self.encrypt(content) with open(filepath, 'w', encoding='utf-8') as f: f.write(encrypted) os.chmod(filepath, 0o600) def load_file(self, filename: str) -> str: """加载加密文件""" filepath = self.base_dir / filename if not filepath.exists(): return "" with open(filepath, 'r', encoding='utf-8') as f: encrypted = f.read() return self.decrypt(encrypted) def delete(self, filename: str): """删除文件""" filepath = self.base_dir / filename if filepath.exists(): filepath.unlink() ``` ### Technical Analysis The helper joins a caller-provided filename to `self.base_dir` but does not reject absolute paths, parent-directory components such as `..`, or symbolic-link traversal. In Python, joining a `Path` with an absolute second operand discards the original base directory. Relative traversal components can likewise resolve outside the intended storage directory. `save_json()` and `save_file()` can overwrite an accessible target with encrypted content, while `delete()` can ...[truncated 1584 chars]
Remediation
View remediation
Path: if Path(filename).name != filename: raise ValueError("Only simple filenames are allowed") base = self.base_dir.resolve() candidate = (base / filename).resolve() if candidate.parent != base: raise ValueError("Invalid storage path") return candidate ``` 3. Use the validated helper consistently for save, load, and delete operations. 4. Reject symbolic-link targets and use no-follow file-opening semantics where supported. 5. Create new files atomically with restrictive permissions rather than writing first and applying `chmod` afterward. 6. Add tests for absolute paths, nested traversal, encoded separators, symbolic links, and platform-specific path syntax. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
security.py:28
Finding

Incorrect Fernet key encoding prevents encrypted storage initialization

Content
View full analysis
bytes: """获取或创建加密密钥""" if self.key_file.exists(): with open(self.key_file, 'rb') as f: return base64.urlsafe_b64decode(f.read()) # 生成新密钥 kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=os.urandom(16), iterations=480000, ) key = base64.urlsafe_b64encode(kdf.derive(os.urandom(32))) # 保存密钥 (权限 600) with open(self.key_file, 'wb') as f: f.write(key) os.chmod(self.key_file, 0o600) return base64.urlsafe_b64decode(key) ``` The returned value is subsequently used as follows: ```python self._key = self._get_or_create_key() self._cipher = Fernet(self._key) ``` ### Technical Analysis `cryptography.fernet.Fernet` expects a URL-safe base64-encoded representation of exactly 32 key bytes. The code correctly derives 32 bytes and base64-encodes them for storage, but then decodes the encoded key before returning it. The existing-key branch makes the same mistake. As a result, `Fernet(self._key)` receives raw 32-byte key material rather than the required encoded representation and can raise an initialization error. This is an availability and reliability defect in security-sensitive functionality, rather than a cryptographic confidentiality bypass. The key derivation also uses random input as if it were a password but does not retain a distinct salt. Since both inputs are random and the resulting key is stored locally, PBKDF2 adds complexity without providing password-based key recovery or stronger separation from the stored key file. ### Attack Path 1. A user invokes `privacy info`, `privacy clear`, or `privacy export`, causing lazy initialization of `SecureStorage`. 2. `_get_or_create_key()` create ...[truncated 730 chars]
Remediation
View remediation
bytes: if self.key_file.exists(): key = self.key_file.read_bytes().strip() Fernet(key) # Validate before returning. return key key = Fernet.generate_key() fd = os.open( self.key_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600, ) with os.fdopen(fd, "wb") as f: f.write(key) return key ``` Additional hardening should include: 1. Validate existing key length and encoding and report a clear recovery procedure if corruption is detected. 2. Create the key atomically with mode `0600` to eliminate the interval between file creation and `chmod`. 3. Set the secure directory to mode `0700`. 4. Do not silently regenerate a key when encrypted files already exist, because doing so would make existing data unrecoverable. 5. Add tests covering first initialization, subsequent initialization, encryption/decryption round trips, corrupted keys, and privacy-clear behavior. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Known Vulnerable Dependency: cryptography==46.0.5 — 11 advisory(ies): GHSA-537c-gmf6-5ccf (Vulnerable OpenSSL included in cryptography wheels); CVE-2026-69247 (cryptography: PKCS#7 EnvelopedData decryption exposes a Bleichenbacher oracle th); CVE-2026-69249 (python-cryptography: Duplicate self-signed intermediates can cause exponential p) +8 more

Critical
Category
Supply Chain
Confidence
98% confidence
Finding

The file pins cryptography to a version reported by the scanner as having multiple published advisories, including critical issues affecting bundled OpenSSL and cryptographic processing. In a shipping skill that may persist user data and could handle tracking, contact, or subscription-related information, use of a vulnerable crypto library can undermine confidentiality, integrity, or trust decisions if the affected features are reachable.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: pillow==12.1.0 — 16 advisory(ies): CVE-2026-55379 (Pillow `BdfFontFile`: `Image.new()` called without `_decompression_bomb_check()`); CVE-2026-55798 (Pillow: WindowsViewer.get_command() OS command injection via unescaped shell pat); CVE-2026-54060 (Pillow: `FontFile.compile()`: `Image.new()` called without `_decompression_bomb_) +13 more

High
Category
Supply Chain
Confidence
95% confidence
Finding

The file pins pillow to a version flagged with numerous advisories, including decompression-bomb handling issues and, on some platforms, command-injection-related behavior. Even though this skill is focused on SF Express workflows, it depends on qrcode and may process or generate images; if untrusted image content or platform-specific viewer features are used, vulnerable image-handling code can lead to denial of service or worse.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises runtime behavior that can use environment variables, local file persistence, network access, and a bundled CLI, but it does not declare any explicit tool scope or permission boundaries. That makes the effective authority of the skill ambiguous and increases the risk of overbroad execution, unintended filesystem writes, or outbound requests when integrated into an agent runtime.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
86% confidence
Finding

The skill explicitly states that it may persist shipment history, subscriptions, saved addresses, exports, and a local encryption key under the user's home directory. Even though privacy controls are documented, storing logistics history and address-related data locally creates privacy and data-retention risk, especially if persistence occurs by default or without clear per-action consent.

Content

Scanner excerpt · SKILL.md (reported line 64)May include surrounding context.

md
## Local Persistence

When the local CLI/runtime code is used, this skill may create and persist local data under:

- `~/.openclaw/data/sf-express/sfexpress.db`
  - stores query history

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The module docstring and class docstring are written only in Chinese, and the rest of the user-facing natural-language comments/docstrings follow the same pattern. For a generally reusable skill file, this imposes a specific language without any opt-in, fallback, or stated region-specific justification, matching the language/locale policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

When a live endpoint is configured, the code transmits tracking numbers to that endpoint automatically during queries without a user-facing disclosure in the query flow. Even with HTTPS validation, tracking numbers are sensitive shipment identifiers, and sending them to arbitrary configured services can expose user logistics data to third parties unexpectedly.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill persists full tracking results to local SQLite history, including fields such as sender and receiver, without an explicit consent step or just-in-time notice. Shipment metadata can contain personal or sensitive location information, so silent retention increases privacy risk if the local account, filesystem, or backups are later accessed by others.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The manifest describes shipment tracking, anomaly triage, guidance, outlet lookup, and estimation, with a brief note that local data may be persisted for history and subscriptions. This file also exposes a dedicated privacy-control surface that inspects storage details, exports local storage metadata, and clears both SQLite tables and encrypted storage files, which is broader behavior than the operational shipment-help scope described in the manifest.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The skill lazily imports a SecureStorage component, enumerates stored files, reports storage metadata, and wipes all secure files. For an SF Express assistant whose stated purpose is shipment-related assistance, direct secure-storage administration is not an obvious requirement, especially since the manifest only mentions persistence for history and subscriptions.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

This markdown file contains extensive user-facing instructions, prompts, and expected outputs in Chinese, which effectively biases the skill toward a specific language experience. Under the stated policy, forcing a specific language without user opt-in can be a natural-language policy violation unless the locale constraint is clearly documented and justified.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
93% confidence
Finding

The file’s top-level description and user-facing CLI strings are written in Chinese, effectively forcing a specific language for users. Under the stated policy, this is a natural-language locale constraint unless the skill offers language choice or clearly documents a justified region-specific limitation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.