Back to skill

Security audit

jf-open-pro-device-image-flip

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its camera-flip purpose, but it needs review because it can send camera credentials to an environment-selected host and may overwrite broader camera settings.

Review before installing. Use this only in an environment you control, keep JF_ENDPOINT fixed to a trusted JFTech regional host, protect the app secret and device token, and avoid running write actions unless you are comfortable with persistent camera-configuration changes. The publisher should add endpoint allowlisting, fail closed when Camera.Param is missing, and correct the JF_MOVE_CARD example.

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
scripts/image_flip.py:23
Finding

Credential-bearing API requests can be redirected to an arbitrary host

Content
View full analysis

Vulnerability Details

File Location: scripts/image_flip.py, lines 23-24, 36-57, and 67-81
Vulnerability Type: Unrestricted network destination for sensitive authentication data
Risk Level: High

Vulnerable Code

python
# API base address, configurable 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()
    }


def get_camera_param(device_token: str, uuid: str, app_key: str,
                     app_secret: str, move_card: int,
                     channel: int = 0) -> Dict[str, Any]:
    url = f"{JF_BASE_URL}/rtc/device/getconfig/{device_token}"
    headers = get_headers(uuid, app_key, app_secret, move_card)

    body = {"Name": "Camera.Param"}
    if channel is not None:
        body["Channel"] = str(channel)

    response = requests.post(url, headers=headers, json=body, timeout=30)

Technical Analysis

JF_ENDPOINT is read directly from the process environment and interpolated into the destination URL without validation or an allowlist. Every request to this endpoint includes:

  • The device access token in the URL path
  • The platform UUID
  • The application key
  • A timestamp
  • A signature derived from the application secret
  • Camera configuration request data

The application secret is not transmitted directly. Nevertheless, the device token is a bea ...[truncated 2081 chars]

Remediation
View remediation

Remediation Suggestions

  1. Replace unrestricted endpoint selection with an exact allowlist:

    python
    ALLOWED_ENDPOINTS = {
        "api-cn.jftechws.com",
        "api-as.jftechws.com",
        "api-eu.jftechws.com",
        "api-na.jftechws.com",
    }
    
    JF_ENDPOINT = os.getenv("JF_ENDPOINT", "api-cn.jftechws.com").strip().lower()
    if JF_ENDPOINT not in ALLOWED_ENDPOINTS:
        raise ValueError("Unsupported JFTech API endpoint")
    
  2. Reject endpoints containing schemes, paths, ports, user-information components, IP literals, or trailing-dot hostname variants.

  3. Where supported by the API, move the device token from the URL path into an authorization header.

  4. Ensure logs redact device tokens, signatures, UUIDs, application keys, and request URLs containing credentials.

  5. Use short-lived, device-specific, least-privileged tokens and revoke a token immediately after suspected disclosure.

  6. Document the exact permitted regional destinations so deployment-level egress controls can enforce the same allowlist.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/image_flip.py:191
Finding

Missing camera configuration can trigger an overwrite of unrelated device settings

Content
View full analysis

Vulnerability Details

File Location: scripts/image_flip.py, lines 111-137, 191-205, 231-245, 273-288, and 318-332
Vulnerability Type: Excessive configuration write caused by unsafe fallback behavior
Risk Level: Medium

Vulnerable Code

python
def get_default_camera_param() -> Dict[str, Any]:
    """Get default camera parameter configuration."""
    return {
        "AeSensitivity": 0,
        "ApertureMode": "0x00000000",
        "BLCMode": "0x00000000",
        "DayNightColor": "0x00000000",
        "Day_nfLevel": 0,
        "Night_nfLevel": 0,
        "DncThr": 0,
        "ElecLevel": 0,
        "EsShutter": "0x00000000",
        "ExposureParam": {
            "LeastTime": "0x00000000",
            "Level": 0,
            "MostTime": "0x00000000"
        },
        "GainParam": {
            "AutoGain": 0,
            "Gain": 50
        },
        "IRCUTMode": 0,
        "InfraredSwap": 0,
        "IrcutSwap": 0,
        "PictureFlip": "0x00000000",
        "PictureMirror": "0x00000000",
        "RejectFlicker": "0x00000000",
        "WhiteBalance": "0x00000000"
    }
python
camera_param = config_data.get("Camera.Param", [])
if not camera_param:
    camera_param = [get_default_camera_param()]

camera_param[0]["PictureMirror"] = bool_to_hex(enable)

result = set_camera_param(
    device_token=args.device_token,
    uuid=args.uuid,
    app_key=args.app_key,
    app_secret=args.app_secret,
    move_card=args.move_card,
    config=camera_param
)

The same fallback pattern is used by the mirror, flip, combined-flip, and reset actions.

Technical Analysis

The Skill declares a narrow purpose: reading and modifying PictureFlip and PictureMirror. However, if the API returns no Camera.Param entry, the code does not stop. It creates a complete hard-coded camera configuration containing exposure, gain, infrared, day/night, ...[truncated 2017 chars]

Remediation
View remediation

Remediation Suggestions

  1. Fail closed when the current camera configuration is unavailable:

    python
    camera_param = config_data.get("Camera.Param")
    if not isinstance(camera_param, list) or not camera_param:
        raise RuntimeError(
            "Camera.Param is missing; refusing to overwrite camera defaults"
        )
    
  2. Use a documented partial-update operation containing only the requested field whenever the API supports it:

    python
    update = {"PictureMirror": bool_to_hex(enable)}
    
  3. If the API requires a complete object, retrieve the existing configuration, validate its schema, copy it without altering unrelated fields, and abort if any required value is missing.

  4. Remove get_default_camera_param() from mutation paths unless the user explicitly requests and confirms a complete camera reset.

  5. Validate that the selected channel and returned configuration correspond to the same target before writing.

  6. Add tests for empty arrays, missing keys, malformed objects, unsupported firmware responses, and multi-channel devices.

  7. Record which fields will be changed and require explicit confirmation before any operation capable of resetting unrelated settings.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/crypto.py:95
Finding

Unvalidated move-card value causes a deterministic signing failure

Content
View full analysis

Vulnerability Details

File Location: scripts/crypto.py, lines 95-102; scripts/image_flip.py, lines 370-371; SKILL.md, lines 113-120
Vulnerability Type: Unhandled division by zero caused by invalid configuration
Risk Level: Low

Vulnerable Code

python
encrypt_length = len(encrypt_byte)

for i in range(encrypt_length):
    if (i % move_card) > ((encrypt_length - i) % move_card):
        temp = encrypt_byte[i]
    else:
        temp = encrypt_byte[encrypt_length - (i + 1)]

The command-line argument accepts any integer without range validation:

python
parser.add_argument("--move-card", type=int, default=os.getenv("JF_MOVE_CARD", "2"),
                    help="移动卡标识")

The documentation provides an example that sets the value to zero:

bash
export JF_MOVE_CARD=0

Technical Analysis

change() uses move_card as the divisor of a modulo operation. A value of zero therefore raises ZeroDivisionError. Neither argument parsing nor generate_signature() verifies that the value is positive before it reaches this operation.

This is especially likely to occur because the documented environment setup explicitly assigns zero to JF_MOVE_CARD, despite the implementation defaulting to 2.

The action handlers only catch RuntimeError. ZeroDivisionError is not caught, so the process terminates with an unhandled exception rather than returning a controlled error.

Attack Path

  1. A user follows the documented setup and sets JF_MOVE_CARD=0, or an attacker modifies that environment variable.
  2. The user invokes any Skill action.
  3. get_headers() calls generate_signature().
  4. generate_signature() calls change() with move_card equal to zero.
  5. The modulo expression raises ZeroDivisionError.
  6. The Skill terminates before making the API request.

Impact Assessment

This issue causes denial of service for all query and configuration a ...[truncated 308 chars]

Remediation
View remediation

Remediation Suggestions

  1. Validate the value during argument processing and reject zero or negative values:

    python
    if args.move_card <= 0:
        parser.error("--move-card must be a positive integer")
    
  2. Add defense-in-depth validation inside the cryptographic helper:

    python
    def change(encrypt_str: str, move_card: int) -> list:
        if not isinstance(move_card, int) or move_card <= 0:
            raise ValueError("move_card must be a positive integer")
    
  3. Correct the SKILL.md example so it uses a server-supported positive value consistent with the implementation and JFTech protocol.

  4. Catch configuration and validation errors at the command boundary and return a concise error without a traceback.

  5. Add unit tests for zero, negative values, non-integer environment values, excessively large values, and the documented default.

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

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

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The request destination is derived from the JF_ENDPOINT environment variable and then used to construct the HTTPS URL for API calls. Because the script also sends authentication material in headers (uuid, appKey, signature, timestamp), an attacker who can influence the environment can redirect requests to an attacker-controlled host and exfiltrate signed API requests and device metadata; in this skill context, that makes the issue more dangerous because the script is explicitly designed to perform privileged device-management operations.

Content

Scanner excerpt · scripts/image_flip.py (reported line 62)May include surrounding context.

python
if channel is not None:
        body["Channel"] = str(channel)
    
    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 80, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The configuration-setting request uses a URL built from JF_ENDPOINT, which comes from an environment variable, and transmits signed control-plane requests plus device configuration payloads. If an attacker can set or alter that variable, they can cause the tool to send privileged management traffic to an attacker-controlled server, enabling credential/signature capture and possible misuse; this is especially sensitive here because this path performs state-changing device operations.

Content

Scanner excerpt · scripts/image_flip.py (reported line 88)May include surrounding context.

python
"Camera.Param": config
    }
    
    response = requests.post(url, headers=headers, json=body, timeout=30)
    result = response.json()
    
    if result.get("code") != 2000:

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared purpose is image flip control, but the skill behavior includes credential/signature generation and authentication mechanics while the documented evidence does not clearly show the actual flip-control logic. This description-behavior mismatch is dangerous because it can conceal privileged operations, mislead reviewers about what executes, and normalize handling of sensitive secrets under a benign-looking feature label.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

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

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding

The skill documentation describes use of environment variables containing secrets and outbound API calls, but it does not declare any tool scope such as permissions or allowed-tools. That creates an undeclared capability boundary: a host agent may allow the skill to access env secrets and the network without explicit review, increasing the chance of secret exposure or unintended external requests.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code file contains natural-language strings that force a specific language for documentation and operational messages. Under the policy, language-only behavior without user opt-in or justification is a natural-language policy violation.

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/image_flip.py (reported line 62)May include surrounding context.

python
if channel is not None:
        body["Channel"] = str(channel)
    
    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/image_flip.py (reported line 88)May include surrounding context.

python
if channel is not None:
        body["Channel"] = str(channel)
    
    response = requests.post(url, headers=headers, json=body, timeout=30)
    result = response.json()
    
    if result.get("code") != 2000:

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code file contains natural-language documentation entirely in Chinese, and the script also emits Chinese user-facing text later in execution. The policy for this audit flags language/locale constraints when a skill forces a specific language without offering user choice or clearly documenting a justified region-specific requirement.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The printed notice at runtime is fixed to Chinese, which can force a specific language experience on users of the skill. Under the stated policy criteria, locale-specific messaging should either be optional, configurable, or clearly justified as region-specific.

Content

No source excerpt is available for this finding.