Back to skill

Security audit

Instagram Publisher

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its Instagram publishing and messaging purpose, but it can take account-impacting actions and fetch arbitrary remote carousel URLs from the user's machine before uploading them.

Review before installing. Use this only in a workspace where Instagram account credentials and media files are intended for MyBrandMetrics, confirm every post and DM before execution, and avoid passing untrusted remote URLs as carousel items until URL validation and size limits are added.

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

Error
Location
scripts/publish_instagram.py:81
Finding

Arbitrary URL Fetch Enables SSRF and Unintended Data Upload

Content
View full analysis

Vulnerability Details

File Location: scripts/publish_instagram.py, lines 81–92, 98–100, and 247–249
Vulnerability Type: Server-Side Request Forgery (SSRF), unrestricted download, and unintended data transfer
Risk Level: High

Vulnerable Code

python
def _download_to_tempfile(self, url: str) -> Path:
    response = requests.get(url, stream=True, timeout=300)
    response.raise_for_status()
    suffix = Path(url.split("?")[0]).suffix or ".bin"
    fd, temp_path = tempfile.mkstemp(prefix="instagram_publish_", suffix=suffix)
    os.close(fd)
    temp_file = Path(temp_path)
    with temp_file.open("wb") as handle:
        for chunk in response.iter_content(chunk_size=8192):
            handle.write(chunk)
    return temp_file
python
if url:
    temp_path = self._download_to_tempfile(url)
    path = str(temp_path)
python
if item.startswith(("http://", "https://")):
    child_id = publisher.upload_media_item(
        media_type,
        url=item,
        thumb_offset=args.thumb_offset,
        wait=args.wait,
    )

Technical Analysis

The carousel workflow accepts an arbitrary HTTP or HTTPS URL and downloads its response from the machine running the Skill. It does not validate the resolved destination or reject loopback, private, link-local, reserved, multicast, or cloud metadata addresses.

The requests library follows redirects by default. Consequently, validating only the original string prefix does not prevent a public URL from redirecting to an internal destination. DNS rebinding and IPv6 address representations are also not addressed.

After downloading the response, upload_media_item() opens the temporary file and submits its contents to the declared MyBrandMetrics publishing endpoint. An attacker-controlled URL can therefore cause data obtained from a resource accessible to the Skill host to be transferred to an external service.

The download loop also has no maximum byte count. Although a network timeout i ...[truncated 1992 chars]

Remediation
View remediation

Remediation Suggestions

  1. Permit remote media downloads only over HTTPS unless HTTP support is explicitly required.
  2. Parse URLs with a standard URL parser and reject embedded credentials, malformed hosts, unsupported ports, and ambiguous address representations.
  3. Resolve the hostname before connecting and reject every resolved IPv4 and IPv6 address in loopback, private, link-local, multicast, reserved, unspecified, and other non-public ranges.
  4. Explicitly block cloud metadata destinations, including link-local metadata addresses and provider-specific metadata hostnames.
  5. Disable automatic redirects or validate the destination of every redirect using the same URL and resolved-address policy.
  6. Account for DNS rebinding by ensuring the validated address is the address used for the connection, or route downloads through an isolated proxy with strict egress controls.
  7. Enforce a conservative response-size limit using both Content-Length when present and a running byte counter while streaming. Abort and delete the temporary file once the limit is exceeded.
  8. Permit only expected image and video MIME types. Verify file signatures rather than relying on URL extensions or Content-Type alone.
  9. Apply explicit connection and read timeouts and limit the number of redirects.
  10. Run the downloader in a network-restricted environment that cannot access internal networks or metadata services.
  11. Consider avoiding local URL retrieval entirely: pass validated public media URLs to the publishing service where supported.
  12. Require explicit user confirmation when retrieving media from a previously unapproved domain.
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 (12)

Tainted flow: 'api_key' from os.environ.get (line 41, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/receive_messages.py (reported line 66)May include surrounding context.

python
def receive_messages(api_key: str, connection_id: str, limit: int) -> dict:
    response = requests.get(
        f"{BASE_URL}/messages",
        headers={"X-API_KEY": api_key},
        params={"connection_id": connection_id, "limit": limit},

Tainted flow: 'api_key' from os.environ.get (line 41, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/send_message.py (reported line 66)May include surrounding context.

python
def send_message(api_key: str, connection_id: str, conversation_id: str, message: str) -> dict:
    response = requests.post(
        f"{BASE_URL}/messages/send",
        headers={"X-API_KEY": api_key, "Content-Type": "application/json"},
        json={

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The code interacts exclusively with the Instagram publishing base URL (https://api.mybrandmetrics.com/instagram/publishing) and implements media upload/publish and status-check functionality. There is no logic for direct-message retrieval or sending, no messaging endpoints, and no message-related arguments or workflows. The publishing-related portions of the description are accurate, but the declared messaging capabilities are not represented in this code chunk, so the description overstates the skill's behavior.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill declares bash/python dependencies and clearly relies on environment variables, local file paths, workspace config files, and outbound API/network access, but it does not define an explicit tool scope such as permissions or allowed-tools. That ambiguity can cause an agent runtime to grant broader capabilities than users expect, increasing the risk of unintended secret access, file reads, or network actions when handling Instagram publishing and messaging workflows.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The manifest description says the skill can fetch recent Instagram direct messages and send Instagram direct messages, but the code in this file only targets the publishing API base path and implements media upload, publish-once, and publish status retrieval. No messaging endpoints, message retrieval logic, or DM send operations appear anywhere in the script.

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

python
url = f"{BASE_URL}/{endpoint}"
        headers = self.headers.copy()
        if is_json:
            return requests.post(url, headers=headers, json=data, timeout=300)
        data_str = {k: str(v) for k, v in data.items() if v is not None}
        return requests.post(url, headers=headers, data=data_str, files=files, timeout=300)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This script can publish content to an external Instagram-connected service and upload local files or remote-fetched media without any explicit confirmation, dry-run mode, or prominent warning about external transmission and account-impacting actions. In an agent-skill context, that increases the risk of unintended posting, disclosure of local media, or surprise use of connected social accounts when driven by ambiguous or malicious prompts.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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/publish_instagram.py (reported line 14)May include surrounding context.

python
import requests


BASE_URL = "https://api.mybrandmetrics.com/instagram/messaging"


def resolve_config_path(explicit_path: str | None) -> Path:

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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/receive_messages.py (reported line 12)May include surrounding context.

python
import requests


BASE_URL = "https://api.mybrandmetrics.com/instagram/messaging"


def resolve_config_path(explicit_path: str | None) -> Path:

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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/send_message.py (reported line 12)May include surrounding context.

python
import requests


BASE_URL = "https://api.mybrandmetrics.com/instagram/messaging"


def resolve_config_path(explicit_path: str | None) -> Path:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This code performs a network POST that transmits the conversation identifier and message text to an external service, which can affect user data and privacy. Aside from the CLI argument names, there is no confirmation prompt, visible disclosure, or explanatory comment/docstring warning that message content will be sent to the MyBrandMetrics API.

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

python
def send_message(api_key: str, connection_id: str, conversation_id: str, message: str) -> dict:
    response = requests.post(
        f"{BASE_URL}/messages/send",
        headers={"X-API_KEY": api_key, "Content-Type": "application/json"},
        json={

Static analysis

No suspicious patterns detected.