Back to skill

Security audit

jf-open-pro-cloud-record

Security checks for vulnerabilities and agentic risk

Overview

This skill appears intended for legitimate JFTech cloud video access, but it sends sensitive device credentials to an environment-selected API host without restricting that host to documented JF endpoints.

Review before installing if this skill will run with real camera credentials. Only set JF_ENDPOINT to an official JFTech regional host, avoid running it in environments where other users or wrappers can alter environment variables, and treat playback/download URLs, app secrets, device tokens, serial numbers, and alarm history as sensitive data.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cloud_record.py:29
Finding

Unrestricted API Endpoint Can Redirect Sensitive Device Credentials

Content
View full analysis

Vulnerability Details

File Location: scripts/cloud_record.py, lines 29–30, 33–44, 58–73, 94–112, and 131–144
Vulnerability Type: Unvalidated credential destination
Risk Level: Medium

Vulnerable Code

python
# API base address (switchable through an environment variable)
JF_ENDPOINT = os.getenv("JF_ENDPOINT", "api-cn.jftechws.com")
JF_BASE_URL = f"https://{JF_ENDPOINT}/gwp/v3"


def get_headers(uuid: str, app_key: str, app_secret: str, move_card: int) -> Dict[str, str]:
    """Generate request headers containing the signature and timestamp."""
    time_millis = get_time_millis()
    signature = generate_signature(uuid, app_key, app_secret, time_millis, move_card)

    return {
        "Content-Type": "application/json; charset=UTF-8",
        "uuid": uuid,
        "appKey": app_key,
        "timeMillis": time_millis,
        "signature": signature,
        "X-Request-Id": os.urandom(16).hex()
    }

The environment-controlled endpoint is subsequently used for every authenticated request:

python
url = f"{JF_BASE_URL}/rtc/device/getVideoList/{device_token}"
headers = get_headers(uuid, app_key, app_secret, move_card)

body = {
    "startTime": start_time,
    "stopTime": stop_time,
    "sn": sn,
    "channel": channel,
    "pageStart": page_start,
    "pageSize": page_size
}

if events:
    body["events"] = events

response = requests.post(url, headers=headers, json=body, timeout=30)
python
url = f"{JF_BASE_URL}/rtc/device/getVideoUrl/{device_token}"
headers = get_headers(uuid, app_key, app_secret, move_card)

body = {
    "channel": channel,
    "fileFormat": file_format
}

if video_id:
    body["videoId"] = video_id

if start_time and stop_time:
    body["startTime"] = start_time
    body["stopTime"] = stop_time

if multi_video:
    body["multiVideo"] = "1"

response = requests.post(url, headers=headers, json=body, timeou
...[truncated 3494 chars]
Remediation
View remediation

Remediation Suggestions

  1. Replace the raw hostname setting with a constrained region selector, such as CN, AS, EU, or NA, and map each value internally to its approved hostname.
  2. If JF_ENDPOINT must remain supported, validate it against an exact allowlist:
    • api-cn.jftechws.com
    • api-as.jftechws.com
    • api-eu.jftechws.com
    • api-na.jftechws.com
  3. Reject values containing URL schemes, paths, query strings, fragments, user information, custom ports, IP literals, trailing-dot variants, or unapproved subdomains.
  4. Construct the URL only after validation and verify that the parsed hostname exactly matches the selected allowlisted hostname.
  5. Do not automatically follow redirects to unapproved hosts. Configure redirect handling explicitly and validate the destination of every redirect before transmitting or retransmitting authentication data.
  6. Avoid placing the device token in the URL where possible. If the API contract permits, transmit it in an authorization header because URLs are more likely to be retained in proxy and server logs.
  7. Use short-lived, narrowly scoped device tokens and enforce timestamp expiration and replay prevention on signed requests.
  8. Ensure deployment configuration prevents untrusted users or components from modifying the Skill process environment.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (13)

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

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The request URL is derived from JF_ENDPOINT, an environment-controlled value, and the code then sends sensitive authentication headers including uuid, appKey, signature, and device access context to that destination. If an attacker can influence the environment, they can redirect requests to an attacker-controlled host and exfiltrate credentials and device metadata through a seemingly legitimate HTTPS POST.

Content

Scanner excerpt · scripts/cloud_record.py (reported line 73)May include surrounding context.

python
if events:
        body["events"] = events
    
    response = requests.post(url, headers=headers, json=body, timeout=30)
    result = response.json()
    
    if result.get("code") != 2000:

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

Critical
Category
Data Flow
Confidence
94% confidence
Finding

This call uses the same environment-derived base URL and transmits signed authentication material and device/video query parameters to whatever host JF_ENDPOINT names. An attacker who can set or poison that environment variable can capture access tokens, signatures, device identifiers, and video lookup parameters, enabling unauthorized surveillance access or replay of API interactions.

Content

Scanner excerpt · scripts/cloud_record.py (reported line 112)May include surrounding context.

python
if multi_video:
        body["multiVideo"] = "1"
    
    response = requests.post(url, headers=headers, json=body, timeout=30)
    result = response.json()
    
    if result.get("code") != 2000:

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

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The alarm-query request also posts sensitive headers and device-related data to a URL constructed from an environment variable. In skill environments where deployment configuration may be user-controlled or indirectly influenced, this becomes a practical SSRF-style exfiltration path for cloud API credentials and alarm history metadata.

Content

Scanner excerpt · scripts/cloud_record.py (reported line 144)May include surrounding context.

python
if alarm_event:
        body["alarmEvent"] = alarm_event
    
    response = requests.post(url, headers=headers, json=body, timeout=30)
    result = response.json()
    
    if result.get("code") != 2000:

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| `SKILL.md` | 技能文档 |

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill documentation describes use of environment variables containing credentials and device tokens, and references networked scripts that access cloud video/alarm APIs, but it does not declare any explicit tool scope such as allowed tools or permissions. That omission weakens reviewability and least-privilege controls, increasing the chance that an agent executes with broader env and network access than users expect.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The skill handles highly sensitive material: cloud video playback/download URLs, alarm history, device identifiers, device tokens, and application secrets. The documentation explains how to retrieve and expose this data but provides no privacy, retention, masking, or safe-handling guidance, which raises the risk of credential leakage and unauthorized access to surveillance footage or alarm data.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/cloud_record.py (reported line 73)May include surrounding context.

python
if events:
        body["events"] = events
    
    response = requests.post(url, headers=headers, json=body, timeout=30)
    result = response.json()
    
    if result.get("code") != 2000:

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/cloud_record.py (reported line 112)May include surrounding context.

python
if events:
        body["events"] = events
    
    response = requests.post(url, headers=headers, json=body, timeout=30)
    result = response.json()
    
    if result.get("code") != 2000:

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/cloud_record.py (reported line 144)May include surrounding context.

python
if events:
        body["events"] = events
    
    response = requests.post(url, headers=headers, json=body, timeout=30)
    result = response.json()
    
    if result.get("code") != 2000:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

This code performs multiple outbound HTTPS requests carrying device-related metadata and authenticated headers, including uuid, appKey, signature, device token in the URL, and query parameters for video/alarm retrieval. While the script prints operational status, it does not clearly disclose to the user that sensitive device activity data and credentials-derived authentication material are being sent to a remote cloud service.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The module docstring and user-facing descriptions are presented only in Chinese, which can impose a fixed language on users without opt-in. The policy allows locale constraints when explicitly documented and justified, but that justification or user choice is not present here.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

SQP-3 applies to all file types and covers language or locale policy violations. This file's natural-language content, including the module docstring and function documentation, forces a single language without offering an opt-in or explaining that the skill is intentionally region-specific.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

generate_signature 的文档在 L149 明确说明 move_card 是“移动取模基数”,类型注解也在 L133 指定为 int。但测试代码在 L174 将 move_card 设为字符串,并在 L176 直接传入,这与函数声明和参数语义相矛盾,属于文档/意图与代码示例不一致。

Content

No source excerpt is available for this finding.