Back to skill

Security audit

Vision Recognition Ocr

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a legitimate Baidu vision and OCR skill, but it can upload any readable user-specified local file to Baidu without image validation or clear consent controls.

Review before installing. Use only with images or documents you are comfortable sending to Baidu, prefer dedicated least-privilege Baidu credentials, and avoid passing untrusted or agent-suggested local paths until the skill adds file-type, path-scope, size, and confirmation safeguards.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (2)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/_baidu_image_classify.py:46
Finding
Arbitrary Readable Local Files Can Be Uploaded to Baidu APIs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/_baidu_image_classify.py:46-67` **Vulnerability Type**: Missing file-type, path-scope, and upload validation **Risk Level**: Medium ### Vulnerable Code ```python def read_image_base64(image_path: str) -> str: p = Path(image_path) if not p.exists() or not p.is_file(): raise FileNotFoundError(f"image_path not found: {image_path}") return base64.b64encode(p.read_bytes()).decode("utf-8") def build_image_payload(payload: Dict[str, Any]) -> Dict[str, Any]: data: Dict[str, Any] = {} image_base64 = payload.get("image_base64") image_path = payload.get("image_path") image_url = payload.get("url") if image_base64: if image_base64.startswith("data:") and "," in image_base64: image_base64 = image_base64.split(",", 1)[1] data["image"] = image_base64 elif image_path: data["image"] = read_image_base64(image_path) elif image_url: data["url"] = image_url else: raise ValueError("One of image_base64/image_path/url must be provided") return data ``` This shared function is used by every OCR and image-classification entry point. ### Technical Analysis The Skill declares that `image_path` identifies a local image, but the implementation only checks that the supplied path exists and is a regular file. It does not verify: - That the file contains a supported image format. - That its detected MIME type matches an allowed image type. - That the path is inside an approved workspace or upload directory. - That the path does not resolve through a symbolic link. - That the file is within a safe size limit. - That the user has explicitly approved transmitting the selected local file. `Path.read_bytes()` therefore reads any regular file accessible to the Skill process. Base64 encoding provides no confidentiality; it only serializes the file for submission. The resulting value is placed in the `image` request param ...[truncated 1813 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict local inputs to explicitly approved workspace or upload directories: - Resolve the path with `Path.resolve(strict=True)`. - Confirm that the resolved path is beneath an allowlisted root. - Reject paths outside that root. 2. Reject symbolic links and non-regular filesystem objects before reading them. 3. Validate actual file contents rather than trusting the extension: - Decode the file with a maintained image library. - Permit only required formats, such as JPEG, PNG, WebP, or BMP. - Reject malformed, unsupported, or decompression-bomb images. - Re-encode the decoded image before uploading to ensure unrelated appended data is not transmitted. 4. Enforce conservative compressed-byte and decoded-dimension limits before loading the complete file into memory. 5. Require explicit user confirmation that identifies the resolved local path and the third-party destination before uploading sensitive local content. 6. Document clearly that local image bytes are sent to Baidu and may be processed according to Baidu's data-handling policies. 7. Where possible, replace unrestricted path input with an opaque attachment identifier supplied by a trusted host application. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/_baidu_image_classify.py:31
Finding
OAuth Secrets and Access Tokens Are Included in Request URLs<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/_baidu_image_classify.py:31-42, 90-101` - `scripts/_baidu_ocr.py:30-41, 54-65` **Vulnerability Type**: Sensitive credentials in URL query strings **Risk Level**: Low ### Vulnerable Code From `scripts/_baidu_image_classify.py`: ```python def get_access_token(api_key: str, secret_key: str) -> str: params = { "grant_type": "client_credentials", "client_id": api_key, "client_secret": secret_key, } resp = requests.post(TOKEN_URL, params=params, timeout=20) resp.raise_for_status() data = resp.json() token = data.get("access_token") if not token: raise RuntimeError(f"Failed to get access_token: {data}") return token ``` ```python def call_image_classify(endpoint: str, data: Dict[str, Any]) -> Dict[str, Any]: api_url = f"{BASE_API_URL}/{endpoint}" headers = {"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"} bearer = pick_bearer_token() if bearer.startswith("bce-v3/"): headers["Authorization"] = f"Bearer {bearer}" resp = requests.post(api_url, headers=headers, data=data, timeout=30) else: api_key, secret_key = pick_oauth_credentials() token = get_access_token(api_key, secret_key) resp = requests.post(f"{api_url}?access_token={token}", headers=headers, data=data, timeout=30) ``` From `scripts/_baidu_ocr.py`: ```python def get_access_token(api_key: str, secret_key: str) -> str: params = { "grant_type": "client_credentials", "client_id": api_key, "client_secret": secret_key, } resp = requests.post(TOKEN_URL, params=params, timeout=20) resp.raise_for_status() data = resp.json() token = data.get("access_token") if not token: raise RuntimeError(f"Failed to get access_token: {json.dumps(data, ensure_ascii=False)}") return token ``` ```python def call_ocr(endpoint: str, data: Dict[str, An ...[truncated 2879 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use the HTTP `Authorization` header for access tokens wherever the Baidu endpoint supports it: ```python headers["Authorization"] = f"Bearer {token}" resp = requests.post(api_url, headers=headers, data=data, timeout=30) ``` 2. If the OAuth endpoint supports form-encoded client credentials, send them in the POST body rather than through `params`: ```python resp = requests.post(TOKEN_URL, data=params, timeout=20) ``` 3. If Baidu mandates query-string credentials for a particular API: - Disable verbose HTTP client logging in production. - Configure proxies, observability platforms, and error trackers to redact `client_secret` and `access_token`. - Never log prepared request URLs without sanitization. - Ensure errors exposed to users do not include credential-bearing URLs. 4. Use dedicated, least-privilege credentials for this Skill rather than general-purpose Baidu account credentials. 5. Rotate the client secret and revoke potentially exposed tokens after remediation. 6. Apply short token lifetimes, quota restrictions, billing alerts, and API-level scope restrictions where supported. 7. Consolidate authentication into one shared, reviewed implementation so OCR and classification clients cannot diverge in credential-handling behavior. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • 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
Findings (22)

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

Critical
Category
Data Flow
Content
"client_id": api_key,
        "client_secret": secret_key,
    }
    resp = requests.post(TOKEN_URL, params=params, timeout=20)
    resp.raise_for_status()
    data = resp.json()
    token = data.get("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: 'token' from os.getenv (line 100, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
else:
        api_key, secret_key = pick_oauth_credentials()
        token = get_access_token(api_key, secret_key)
        resp = requests.post(f"{api_url}?access_token={token}", headers=headers, data=data, timeout=30)

    resp.raise_for_status()
    result = resp.json()
Confidence
90% confidence
Finding
The access token is embedded in the request URL query string, which can expose it through logs, monitoring systems, proxies, browser/debug tooling, or error messages even when TLS is used. Tokens in URLs are generally more leak-prone than tokens in headers and can enable unauthorized API use if captured.

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

Critical
Category
Data Flow
Content
"client_id": api_key,
        "client_secret": secret_key,
    }
    resp = requests.post(TOKEN_URL, params=params, timeout=20)
    resp.raise_for_status()
    data = resp.json()
    token = data.get("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: 'token' from os.getenv (line 64, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
else:
        api_key, secret_key = pick_oauth_credentials()
        token = get_access_token(api_key, secret_key)
        resp = requests.post(f"{api_url}?access_token={token}", headers=headers, data=data, timeout=30)

    resp.raise_for_status()
    result = resp.json()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code clearly supports image input via local path, URL, and base64, and it prepares/authenticates requests to Baidu image-classify APIs, which aligns with the image recognition portion of the description. However, the declared purpose prominently includes OCR/text extraction, including invoices and tables, while the supplied code only targets the image-classify base API and contains no OCR endpoints, text-recognition request building, or OCR-specific processing. This is a material description-versus-behavior mismatch for the provided chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The code chunk only supports calling Baidu OCR endpoints and managing authentication tokens. There is no evidence of vehicle, animal, or plant recognition logic, nor any parsing/loading of image inputs from local paths, URLs, or base64. While OCR is consistent with part of the description, the declared description materially overstates the implemented capabilities in this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The supplied code chunk has a much narrower purpose than the declared description. It builds an image payload and calls an image classification function specifically with the category 'animal'. There is no OCR logic, no vehicle or plant recognition, and no invoice/table/text extraction behavior shown here. While the input handling appears consistent with image input support, the primary purpose of this code chunk is limited to animal recognition, making the broad declared description materially inaccurate for this specific code.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The code chunk is narrowly focused on car recognition: it builds an image payload and calls call_image_classify("car", data). While vehicle recognition is part of the declared description, the description claims a much broader multifunction skill including OCR and recognition of animals/plants/invoices/tables. Those capabilities are not present in this code chunk. Input support for image payloads may be inherited from build_image_payload, but the primary implemented behavior here is only car classification, making the declared description materially broader than the actual behavior shown.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description presents a combined image-recognition plus OCR skill covering object/category recognition (vehicle/animal/plant) and multiple OCR use cases. The supplied code chunk, however, is narrowly focused on generic OCR via Baidu's accurate_basic API. While OCR is consistent with part of the declared purpose, the broader recognition claims are not represented in this code. This is a material description/behavior mismatch because the actual primary behavior here is only OCR, not the full multimodal recognition capability described.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The description presents the skill as a broader multimodal recognition tool covering both image classification (e.g., vehicle/animal/plant recognition, 看图识别) and OCR extraction from multiple document types. However, the supplied code only performs a generic OCR operation via call_ocr("general_basic", data). It accepts an image payload and optional OCR flags, but contains no logic for image classification, no vehicle/animal/plant recognition, and no specialized invoice/table processing in this chunk. This is a description-to-behavior mismatch because the declared primary scope is materially broader than what the code actually does.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill documentation advertises capabilities that require network access, environment-variable credential use, and local file reading, but it does not declare any tool scope or permissions boundary. That increases the chance the agent invokes broader capabilities than users expect, weakening least-privilege and informed-consent controls around sensitive image and OCR data.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill states it uses Baidu vision APIs but does not clearly warn users that provided images and the text extracted from them may be transmitted to an external cloud provider. In an OCR skill, this matters because screenshots, invoices, tables, and photos often contain highly sensitive personal or business data, so users may unknowingly expose confidential material.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The helper sends credentials and potentially sensitive image content to external Baidu endpoints, but there is no visible user-consent, warning, or disclosure mechanism in the implementation. In an OCR/vision skill, inputs may include invoices, screenshots, or other personal/business data, so undisclosed third-party transfer creates privacy and compliance risk.

Tainted flow: 'data' from requests.post (line 39, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
bearer = pick_bearer_token()
    if bearer.startswith("bce-v3/"):
        headers["Authorization"] = f"Bearer {bearer}"
        resp = requests.post(api_url, headers=headers, data=data, timeout=30)
    else:
        api_key, secret_key = pick_oauth_credentials()
        token = get_access_token(api_key, secret_key)
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: 'data' from requests.post (line 39, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
else:
        api_key, secret_key = pick_oauth_credentials()
        token = get_access_token(api_key, secret_key)
        resp = requests.post(f"{api_url}?access_token={token}", headers=headers, data=data, timeout=30)

    resp.raise_for_status()
    result = resp.json()
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.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This helper transmits images or extracted document data to Baidu's remote OCR service without any in-code indication of user consent, privacy notice, or sensitivity checks. In the context of OCR for screenshots, invoices, and tables, this can expose personal, financial, or confidential business data to a third party unexpectedly.

Tainted flow: 'data' from requests.post (line 38, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
bearer = pick_bearer_token()
    if bearer.startswith("bce-v3/"):
        headers["Authorization"] = f"Bearer {bearer}"
        resp = requests.post(api_url, headers=headers, data=data, timeout=30)
    else:
        api_key, secret_key = pick_oauth_credentials()
        token = get_access_token(api_key, secret_key)
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: 'data' from requests.post (line 38, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
else:
        api_key, secret_key = pick_oauth_credentials()
        token = get_access_token(api_key, secret_key)
        resp = requests.post(f"{api_url}?access_token={token}", headers=headers, data=data, timeout=30)

    resp.raise_for_status()
    result = resp.json()
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.

Missing User Warnings

Low
Confidence
89% confidence
Finding
The credential setup section explains how to supply API keys and bearer tokens, but it omits a plain-language warning that the skill performs external network calls using those credentials to process user-supplied data. This creates an informed-consent and data-governance risk, especially in environments where users may assume local-only OCR or may not realize secrets and document contents are involved in third-party processing.

Context-Inappropriate Capability

Low
Confidence
77% confidence
Finding
The manifest describes a recognition/OCR skill for processing images from path, URL, or base64, but does not mention credential discovery from environment variables. While network access to Baidu is expected for this skill, reading multiple environment variables for API and bearer tokens introduces a separate capability beyond the stated user-facing function.

Context-Inappropriate Capability

Low
Confidence
77% confidence
Finding
The skill is described as vehicle/animal/plant recognition and OCR on images, but this helper pulls credentials from broad, generic environment variables such as BAIDU_API_KEY and BAIDU_SECRET_KEY in addition to vision-specific names. Reading environment variables for credentials is not described in the manifest and is a capability that is not clearly justified by the narrow end-user purpose alone.

Description-Behavior Mismatch

Low
Confidence
92% confidence
Finding
The manifest presents the skill as a general image-recognition and OCR capability across multiple domains, but this script is narrowly scoped to calling image classification with the fixed category "car". That is a semantic mismatch between the broad claimed behavior and this file's actual behavior, even though the broader skill may be implemented elsewhere.

Static analysis

No suspicious patterns detected.