Back to skill

Security audit

Yt Assemblyai Monitor

Security checks for vulnerabilities and agentic risk

Overview

The skill largely matches its YouTube transcription purpose, but it can store and later fetch arbitrary channel URLs, which may let it contact non-YouTube or internal network addresses.

Install only if you are comfortable with YouTube and AssemblyAI cloud processing. Use environment variables for the AssemblyAI key where possible, keep any config file private, and only add real YouTube HTTPS channel URLs until the skill validates channel destinations.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/monitor.py:130
Finding
Unrestricted Channel URL Fetch Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/monitor.py:130-131`, `scripts/monitor.py:233-245`, and `scripts/monitor.py:281-282` **Vulnerability Type**: Server-Side Request Forgery (SSRF) caused by missing URL validation **Risk Level**: Medium ### Vulnerable Code ```python def get_channel_videos(channel_url, limit=5): """Get recent video IDs from a YouTube channel page.""" resp = requests.get(channel_url, headers={"User-Agent": UA}, timeout=20) if resp.status_code != 200: print(f" ERROR: Channel HTTP {resp.status_code}") return [] ``` ```python def add_channel(url, alias=None): channels = load_json(CHANNELS_FILE, []) for ch in channels: if ch.get("url") == url: print(f"Already exists: {ch.get('alias', 'unnamed')}") return if not alias: alias = get_channel_info(url) channels.append({"url": url, "alias": alias or url, "added": time.strftime("%Y-%m-%d")}) save_json(CHANNELS_FILE, channels) print(f"Added: {alias} ({url})") ``` ```python for ch in channels: alias = ch.get("alias", "?") url = ch.get("url") print(f"\n📺 {alias}") videos = get_channel_videos(url, limit) ``` ### Technical Analysis The `add_channel()` function accepts and persists an arbitrary URL without validating its scheme, hostname, port, resolved address, or intended destination. During a subsequent `check` operation, `check_channels()` passes that stored URL to `get_channel_videos()`, which performs a server-side `requests.get()` call. The Skill's declared functionality only requires access to YouTube channel pages. Allowing requests to arbitrary destinations therefore exceeds the minimum network privileges necessary for its intended operation. An attacker who can provide command-line arguments or modify `data/channels.json` can direct the process toward loopback addresses, private network services, link-local endpoints, or cloud instance metadata services. Redirect ...[truncated 1971 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Restrict network access to the exact destinations required by the Skill: 1. Parse every channel URL with `urllib.parse.urlsplit()`. 2. Require the `https` scheme. 3. Permit only exact approved YouTube hostnames, such as `www.youtube.com` and `youtube.com`; do not use suffix checks that could accept domains such as `youtube.com.attacker.example`. 4. Reject URLs containing embedded credentials, fragments, or nonstandard ports. 5. Resolve the hostname and reject loopback, private, link-local, reserved, multicast, and unspecified IP addresses for both IPv4 and IPv6. 6. Disable automatic redirects or validate every redirect destination against the same allowlist. 7. Validate URLs both when they are added and immediately before each request because `data/channels.json` can be modified independently. 8. Consider constructing canonical YouTube URLs from validated channel identifiers rather than storing arbitrary URLs. 9. Apply equivalent validation to any future feature that accepts remote media URLs. Example defensive direction: ```python from urllib.parse import urlsplit ALLOWED_YOUTUBE_HOSTS = {"youtube.com", "www.youtube.com"} def validate_channel_url(value): parsed = urlsplit(value) if parsed.scheme != "https": raise ValueError("Only HTTPS YouTube URLs are allowed") if parsed.hostname not in ALLOWED_YOUTUBE_HOSTS: raise ValueError("Only approved YouTube hosts are allowed") if parsed.username or parsed.password or parsed.port not in (None, 443): raise ValueError("Credentials and nonstandard ports are not allowed") return value ``` The implementation should additionally validate DNS resolution and redirect targets to address DNS rebinding and redirect-based bypasses. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
SKILL.md:27
Finding
AssemblyAI API Key May Be Stored in an Unprotected Plaintext Configuration File<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:27-36` and `scripts/monitor.py:31-41` **Vulnerability Type**: Plaintext sensitive credential storage **Risk Level**: Low ### Vulnerable Code The documentation instructs users to write the credential directly to a project file: ```bash # Option A: environment variable export ASSEMBLYAI_API_KEY="your-key" # Option B: config file echo '{"api_key": "your-key"}' > data/config.json ``` The application then reads the plaintext credential without checking file ownership or permissions: ```python def get_api_key(): key = os.environ.get("ASSEMBLYAI_API_KEY") if key: return key if CONFIG_FILE.exists(): return json.loads(CONFIG_FILE.read_text()).get("api_key") print("ERROR: No API key. Set ASSEMBLYAI_API_KEY or create data/config.json") sys.exit(1) ``` ### Technical Analysis The optional configuration workflow stores a long-lived AssemblyAI API key in plaintext under `data/config.json`. The documented shell redirection relies on ambient directory permissions and the user's `umask`; it does not explicitly create the file with owner-only permissions. The application likewise does not verify that the file is owned by the expected user or inaccessible to group and other accounts. The credential may consequently be exposed to other local users, backup systems, unrelated processes with project access, or source-control commits. The static pre-scan's sensitive network behavior is otherwise consistent with the declared cloud transcription feature: the key is placed in the `authorization` header and sent to the fixed HTTPS AssemblyAI API. The issue is the local storage option rather than evidence of covert credential exfiltration. ### Attack Path 1. A user follows the documented configuration-file setup: ```bash echo '{"api_key": "secret-key"}' > data/config.json ``` 2. The resulting permissions are determined by the environment's `umask` and directory access cont ...[truncated 955 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer environment variables, an operating-system credential manager, or a dedicated secret-management service. 2. If file-based storage remains supported, document creation with owner-only permissions: ```bash install -m 600 /dev/null data/config.json printf '%s\n' '{"api_key":"your-key"}' > data/config.json chmod 600 data/config.json ``` 3. Before reading the file, verify that it is a regular file, owned by the expected user, and has no group or other permission bits. 4. Add `data/config.json` to `.gitignore` and provide a non-sensitive `config.example.json` instead. 5. Avoid printing the API key or including it in exception messages and logs. 6. Document credential rotation procedures for suspected exposure. 7. Use a narrowly scoped API credential where the service supports scope restrictions, usage limits, or separate project keys. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises capabilities that include environment access, file read/write, and network use, but it does not declare any explicit tool scope or permission boundaries. This increases the chance of over-broad execution in agent environments, making unintended data access, filesystem modification, or outbound requests harder to audit and restrict.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The activation guidance is broad enough to match common requests like summarization or monitoring, which can cause the skill to be invoked in situations beyond the user's likely intent. In an agentic system, over-broad routing can expose users to unnecessary network actions, third-party data transfer, or file operations when a simpler local skill would have sufficed.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill sends video-derived audio/transcription content to AssemblyAI, a third-party cloud service, but the markdown lacks a clear upfront warning to users about that transfer. This creates a privacy and consent risk because users may assume processing is local or limited to YouTube fetching, especially given the emphasis on 'pure Python' and 'zero local dependencies.'

External Transmission

Medium
Category
Data Exfiltration
Content
SUMMARIES_DIR = DATA_DIR / "summaries"
CONFIG_FILE = DATA_DIR / "config.json"

API_BASE = "https://api.assemblyai.com/v2"
POLL_INTERVAL = 15
MAX_WAIT = 600
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
return None

    body = {"videoId": video_id, "context": ctx}
    resp = requests.post(
        f"https://www.youtube.com/youtubei/v1/player?key={key}",
        json=body,
        headers={
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Tainted flow: 'key' from requests.get (line 74, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
return None

    body = {"videoId": video_id, "context": ctx}
    resp = requests.post(
        f"https://www.youtube.com/youtubei/v1/player?key={key}",
        json=body,
        headers={
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
96% confidence
Finding
The skill sends YouTube-derived audio URLs and resulting transcript processing requests to AssemblyAI, a third-party cloud service, without any explicit consent, disclosure, or policy checks. In a monitoring/transcription skill this is core functionality, but it still creates a real privacy and data-governance risk because potentially sensitive media content is transmitted off-platform.

External Transmission

Medium
Category
Data Exfiltration
Content
"summary_type": "paragraph",
        "summary_model": "conversational",
    }
    resp = requests.post(f"{API_BASE}/transcript", json=payload, headers=headers, timeout=30)
    if resp.status_code != 200:
        print(f"  ERROR submit: {resp.status_code} {resp.text[:200]}")
        return None
Confidence
97% confidence
Finding
This request transmits audio source information to AssemblyAI for cloud transcription and summarization. Because the tool processes potentially sensitive content and sends it to an external processor, the risk is unauthorized disclosure, compliance issues, or user surprise if this transfer is not clearly disclosed and controlled.

Missing User Warnings

Low
Confidence
77% confidence
Finding
The function loads `ASSEMBLYAI_API_KEY` from an environment variable or `data/config.json`, which is sensitive credential material. There is no comment or warning advising users that a secret is being read from local config or environment, nor any note about protecting the config file.

Static analysis

No suspicious patterns detected.