Back to skill

Security audit

Podcast Intel

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims, but it sends podcast audio and transcripts to AI services and stores untrusted, model-derived podcast content in persistent OpenClaw memory with weak containment.

Review this skill before installing. Use it only with podcast feeds and episode URLs you trust, prefer dry_run until you understand the diary behavior, avoid setting OPENAI_BASE_URL unless you control and trust that endpoint, and assume full audio, transcript excerpts, and derived summaries may be sent to external AI providers and stored locally.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (4)

T02 · Agent Memory Poisoning

Error
Location
scripts/segment.py:148
Finding
Untrusted Podcast Content Can Poison Persistent Agent Memory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/segment.py:148-186`, `scripts/diary.py:94-115`, `scripts/utils/config.py:174-187` **Vulnerability Type**: Prompt injection leading to persistent memory poisoning **Risk Level**: High ### Vulnerable Code ```python # scripts/segment.py:148-186 max_words = 10000 text_preview = " ".join(transcript_text.split()[:max_words]) prompt = f"""Analyze this podcast transcript and identify distinct topical segments. For each major topic discussed, provide: 1. label (short topic title) 2. key_entities (array of strings) 3. summary (1-2 sentences) 4. information_density (number between 0 and 1) Return strictly a JSON array and nothing else. Transcript: {text_preview} """ model = config.get("model", "gpt-4o-mini") response_text = "" # Preferred OpenAI Python API. try: response = client.responses.create( model=model, input=[{"role": "user", "content": prompt}], temperature=0.2, max_output_tokens=2000, ) response_text = _extract_response_text(response) except Exception: # Backward-compatible fallback for older client surfaces. try: response = client.chat.completions.create( model=model, messages=[{"role": "user", "content": prompt}], temperature=0.2, max_tokens=2000, ) response_text = _extract_response_text(response) ``` ```python # scripts/diary.py:94-115 markdown = f""" ### {entry['show']}: {entry['title']} (WYT: {wyt_percent}%) - **Worth listening:** {novel_text} - **Key topics:** {', '.join(entry['topics_exposed'][:3])} - **Recommended segments:** {len(entry['segments_recommended'])} / {len(entry['segments_recommended']) + len(entry['segments_skipped'])} """ if entry['overlap_flags']: markdown += f"- **Note:** Overlaps with {', '.join(entry['overlap_flags'][:2])}\n" markdown += "\n" save_markdown_note(markdown) ``` ```python # scripts/utils/config.py:174-187 def save_markdown ...[truncated 2801 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat transcripts and all model output as untrusted data. 2. Use the provider's structured-output or JSON-schema functionality rather than extracting arbitrary JSON with a regular expression. 3. Impose strict schema constraints on every returned field: - Maximum label and summary lengths. - Allowed character sets. - No line breaks, Markdown, XML, URLs, or instruction-like phrases in labels. - Bounded array sizes and entity lengths. 4. Clearly delimit transcript data and state that content inside the delimiter is data, not instructions. This reduces but does not eliminate prompt-injection risk. 5. Add a deterministic sanitizer before model output reaches diary or memory storage. 6. Store factual diary records in a non-agent-memory data directory unless persistence in agent memory is explicitly required. 7. If Markdown memory is required, encode untrusted values and mark them as quoted external content. 8. Require user confirmation before persisting content derived from an untrusted, manually supplied episode. 9. Record provenance so future consumers can distinguish remote podcast content from trusted user-authored memory. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/transcribe.py:42
Finding
Audio URL Validation Can Be Bypassed Through DNS Resolution and Redirects<![CDATA[ ## Vulnerability Details **File Location**: `scripts/transcribe.py:42-106` **Vulnerability Type**: Server-side request forgery through incomplete URL validation **Risk Level**: High ### Vulnerable Code ```python def validate_audio_url(url: str) -> Tuple[bool, str]: """Validate that URL is safe for ffmpeg fetches.""" parsed = urlparse(url) if parsed.scheme not in {"http", "https"}: return False, "Only http/https audio URLs are allowed" host = parsed.hostname if not host: return False, "Audio URL missing hostname" lowered = host.lower() if lowered in {"localhost", "localhost.localdomain"}: return False, "Localhost audio URLs are blocked" try: addr = ipaddress.ip_address(host) if ( addr.is_private or addr.is_loopback or addr.is_link_local or addr.is_multicast or addr.is_reserved or addr.is_unspecified ): return False, "Private/local IP audio URLs are blocked" except ValueError: # Host is a domain name; we allow it. pass return True, "" def download_audio(url: str, temp_dir: Path) -> Optional[Path]: """Download audio from URL to temp WAV file.""" is_valid, reason = validate_audio_url(url) if not is_valid: print(f"Unsafe audio URL rejected: {reason}", file=sys.stderr) return None audio_file = temp_dir / "audio.wav" cmd = [ "ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-i", url, "-vn", "-ac", "1", "-ar", "16000", "-acodec", "pcm_s16le", "-f", "wav", str(audio_file), "-y", ] try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=900) ``` ### Technical Analysis The validation blocks dangerous addresses only when the hostname itself is a ...[truncated 2059 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve every hostname before initiating a download and validate every returned IPv4 and IPv6 address. 2. Reject addresses that are not globally routable, including loopback, private, link-local, multicast, reserved, unspecified, and carrier-grade NAT ranges. 3. Protect against DNS rebinding by connecting to a validated, pinned address while preserving the expected TLS hostname. 4. Disable redirects where possible. Otherwise, validate the scheme, hostname, port, and resolved addresses at every redirect hop. 5. Prefer HTTPS and reject non-default or unnecessary destination ports. 6. Consider an allowlist of podcast hosting domains or require explicit user approval for new hosts. 7. Enforce response-size, duration, transfer-rate, and media-duration limits. 8. Run `ffmpeg` inside a sandbox with restricted network access and resource limits. 9. Validate the media content type and format before full processing. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/utils/config.py:95
Finding
Unvalidated OpenAI Base URL Can Expose API Credentials and Podcast Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/utils/config.py:95-104`, `scripts/transcribe.py:124-138`, `scripts/segment.py:138-175` **Vulnerability Type**: Sensitive information disclosure through an untrusted API endpoint **Risk Level**: High ### Vulnerable Code ```python # scripts/utils/config.py:95-104 def get_openai_config() -> Dict[str, str]: """Get OpenAI API configuration from environment.""" config = { 'api_key': os.getenv('OPENAI_API_KEY', ''), 'base_url': os.getenv('OPENAI_BASE_URL', 'https://api.openai.com/v1'), 'model': os.getenv('OPENAI_MODEL', 'gpt-4o-mini'), } if not config['api_key']: raise ValueError("OPENAI_API_KEY environment variable not set") return config ``` ```python # scripts/transcribe.py:124-138 def transcribe_with_openai(audio_file: Path, model: str = "whisper-1") -> Optional[Dict[str, Any]]: """Transcribe using OpenAI Whisper API.""" try: from openai import OpenAI from utils import get_openai_config config = get_openai_config() client = OpenAI(api_key=config["api_key"], base_url=config["base_url"]) with open(audio_file, "rb") as handle: transcript = client.audio.transcriptions.create( model=model, file=handle, response_format="verbose_json", ) ``` ```python # scripts/segment.py:138-175 try: from openai import OpenAI except Exception as exc: print(f"Error loading OpenAI client: {exc}", file=sys.stderr) return None try: config = get_openai_config() client = OpenAI(api_key=config["api_key"], base_url=config["base_url"]) except Exception as exc: print(f"Error creating OpenAI client: {exc}", file=sys.stderr) return None max_words = 10000 text_preview = " ".join(transcript_text.split()[:max_words]) prompt = f"""Analyze this podcast transcript and identify distinct topical segments. For each major topic discussed, ...[truncated 2407 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Default to the official HTTPS API endpoint and reject arbitrary overrides unless custom providers are an explicit supported feature. 2. Validate the URL before client creation: - Require HTTPS. - Reject embedded credentials. - Reject fragments and unexpected ports. - Require an allowlisted hostname. - Reject private and non-global resolved addresses. 3. Do not reuse an OpenAI credential with arbitrary OpenAI-compatible providers. 4. Introduce separate provider-specific endpoint and credential settings. 5. Display the selected provider host and require explicit user confirmation before transmitting audio or transcripts to a non-default provider. 6. Prevent redirects to untrusted hosts and validate every redirect destination. 7. Use narrowly scoped keys, spending limits, rotation procedures, and secret-manager injection. 8. Update the privacy documentation to identify precisely which data is transmitted and to which configured provider. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding
Installer Resolves and Executes Unpinned Third-Party Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `install.sh:47-50`, `requirements.txt:1-6` **Vulnerability Type**: Uncontrolled dependency resolution and supply-chain exposure **Risk Level**: Medium ### Vulnerable Code ```bash # install.sh:47-50 # Install Python dependencies echo "" echo "📦 Installing Python dependencies..." pip install -r "$SKILL_ROOT/requirements.txt" --quiet ``` ```text # requirements.txt:1-6 feedparser>=6.0 openai>=1.0 pydantic>=2.0 pyyaml>=6.0 numpy>=1.24 httpx>=0.27 ``` ### Technical Analysis All direct dependencies use open-ended minimum-version constraints. There is no upper bound, exact lock file, hash verification, or transitive dependency inventory. Each installation may therefore resolve a different set of packages than the set considered during this audit. Python package installation can execute package build backends and installation-related code. If a future release or transitive dependency is compromised, the installer can execute unaudited code with the privileges of the user running `install.sh`. The use of standard package names and the default package index reduces dependency-confusion risk, but it does not mitigate compromised releases, future malicious versions, or unexpected compatibility changes. ### Attack Path 1. A direct or transitive dependency account, release process, or distribution channel is compromised. 2. A newer package version is published that still satisfies the `>=` constraint. 3. A user runs `install.sh`. 4. `pip` resolves the newer, unaudited package or transitive dependency. 5. Package build or runtime code executes on the user's system. 6. Malicious code gains the permissions available to the installing user. ### Impact Assessment The immediate execution scope is the account running `pip`. A compromised dependency may read or alter files accessible to that account, access environment variables such as `OPENAI_API_KEY`, modify the Python environment, or establish additional network co ...[truncated 314 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every reviewed direct dependency to an exact version. 2. Generate a lock file that includes all transitive dependencies. 3. Require package hashes, for example through a hash-locked requirements file and `pip install --require-hashes`. 4. Install dependencies in a dedicated virtual environment instead of the user's global Python environment. 5. Use a controlled package index or internal mirror with provenance and malware scanning. 6. Add automated dependency vulnerability and integrity scanning to release workflows. 7. Review and deliberately update the lock file rather than allowing versions to change at installation time. 8. Document that the installer must not be executed as root. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (34)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
To clear cache:
```bash
rm -rf ~/.openclaw/cache/podcast-intel/
```

## Cost Estimates
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
To clear cache:
```bash
rm -rf ~/.openclaw/cache/podcast-intel/
```

## Cost Estimates
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
sudo apt-get install ffmpeg

# Fedora
sudo dnf install ffmpeg
```

### 4. Set up environment
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The README states that only RSS feeds, Whisper API, and an LLM API are contacted, but it does not clearly and explicitly warn users that full podcast audio, transcripts, and derived analyses may be transmitted to third-party AI providers for processing. This can cause users to underestimate privacy, confidentiality, and data-handling implications, especially if feeds contain copyrighted, sensitive, or private material.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The activation text includes a broad catch-all phrase covering essentially any request involving podcast content analysis, which can cause the skill to trigger outside narrowly intended scenarios. Overbroad auto-activation increases the chance of inappropriate tool use, unnecessary external data processing, and unintended access to configured feeds, transcripts, or the local consumption diary when a user request only loosely relates to podcasts.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
fi
    elif command -v apt-get &> /dev/null; then
        # Ubuntu/Debian
        sudo apt-get update
        sudo apt-get install -y ffmpeg
    elif command -v dnf &> /dev/null; then
        # Fedora
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
fi
    elif command -v apt-get &> /dev/null; then
        # Ubuntu/Debian
        sudo apt-get update
        sudo apt-get install -y ffmpeg
    elif command -v dnf &> /dev/null; then
        # Fedora
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
fi
    elif command -v apt-get &> /dev/null; then
        # Ubuntu/Debian
        sudo apt-get update
        sudo apt-get install -y ffmpeg
    elif command -v dnf &> /dev/null; then
        # Fedora
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
fi
    elif command -v apt-get &> /dev/null; then
        # Ubuntu/Debian
        sudo apt-get update
        sudo apt-get install -y ffmpeg
    elif command -v dnf &> /dev/null; then
        # Fedora
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

External Transmission

Medium
Category
Data Exfiltration
Content
# OpenAI Base URL
if [ -z "$OPENAI_BASE_URL" ]; then
    echo "→ OPENAI_BASE_URL: NOT SET (using default: https://api.openai.com/v1)"
else
    echo "✓ OPENAI_BASE_URL: $OPENAI_BASE_URL"
fi
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
# OpenAI Base URL
if [ -z "$OPENAI_BASE_URL" ]; then
    echo "→ OPENAI_BASE_URL: NOT SET (using default: https://api.openai.com/v1)"
else
    echo "✓ OPENAI_BASE_URL: $OPENAI_BASE_URL"
fi
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = ["python3", str(script_path)] + args

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
    except subprocess.TimeoutExpired:
        print(f"Error running {script_name}: timeout after {timeout}s", file=sys.stderr)
        return None
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code appends analysis results to a diary, which is a file/data write operation with persistent effects. While a dry-run option exists and stderr logging announces the update when it happens, there is no upfront warning in the file's documented behavior that running the skill will modify diary data by default.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The code sends transcript content to an external OpenAI-compatible service in LLM mode by embedding transcript text directly into the prompt, but it provides no explicit consent, warning, or data-classification check at the transmission point. Because transcripts may contain sensitive or proprietary content, this creates a real confidentiality and privacy risk, especially when the default method is 'llm'.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The script caches transcript-derived summaries and entities to disk automatically, which can persist sensitive information beyond the immediate run and expose it to other local users, backups, or later compromise. This is a genuine data-handling weakness because the cache write occurs silently and is not clearly disclosed to the user at the write point.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
]

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=900)
    except Exception as exc:
        print(f"Error downloading audio: {exc}", file=sys.stderr)
        return None
Confidence
74% confidence
Finding
The code invokes ffmpeg on a user-supplied remote URL, which can trigger server-side requests and media parsing of attacker-controlled content. Although shell injection is mitigated by passing an argument list and there is some URL validation, the validation only checks the literal hostname/IP and does not prevent DNS rebinding/resolution to internal addresses or risks inherent in ffmpeg processing untrusted media.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This code uploads the audio file to a remote API via `client.audio.transcriptions.create`, which transmits user-provided audio content off-system. While the module docstring mentions use of the Whisper API, there is no explicit user-facing warning at the point of operation about sending audio data to an external service.

Unpinned Dependencies

Low
Category
Supply Chain
Content
feedparser>=6.0
openai>=1.0
pydantic>=2.0
pyyaml>=6.0
Confidence
96% confidence
Finding
The dependency is specified with a lower-bound constraint only, which allows future installs to resolve to different versions over time. This weakens build reproducibility and makes it harder to verify whether a deployed version includes known security fixes or newly introduced vulnerable releases.

Unverifiable Dependency: feedparser has 10 known advisory(ies) (CVE-2011-1157 (feedparser Cross-site Scripting vulnerability); CVE-2009-5065 (feedparser Cross-site Scripting vulnerability); CVE-2011-1158 (feedparser Cross-site Scripting vulnerability) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
93% confidence
Finding
feedparser has known advisories, and because the manifest does not pin a version, it is impossible to determine from this file whether a safe or affected release will be installed. This creates uncertainty in security review and allows vulnerable versions to be selected depending on resolution time and environment.

Unpinned Dependencies

Low
Category
Supply Chain
Content
feedparser>=6.0
openai>=1.0
pydantic>=2.0
pyyaml>=6.0
numpy>=1.24
Confidence
96% confidence
Finding
The openai package is unpinned and may resolve to different versions in different environments or at different times. That can introduce supply-chain risk, unexpected breaking changes, or accidental installation of a version with a security issue that has not been reviewed.

Unpinned Dependencies

Low
Category
Supply Chain
Content
feedparser>=6.0
openai>=1.0
pydantic>=2.0
pyyaml>=6.0
numpy>=1.24
httpx>=0.27
Confidence
96% confidence
Finding
Using pydantic>=2.0 permits any newer major or minor release, reducing reproducibility and making security posture difficult to assess. If a vulnerable or incompatible release is pulled in, downstream validation logic or service reliability could be affected.

Unverifiable Dependency: pydantic has 4 known advisory(ies) (CVE-2021-29510 (Use of "infinity" as an input to datetime and date fields causes infinite loop i); CVE-2024-3772 (Pydantic regular expression denial of service); CVE-2021-29510 (Pydantic is a data validation and settings management using Python type hinting.) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
94% confidence
Finding
pydantic has known advisories, including denial-of-service related issues, and the unpinned constraint prevents verification that an installed version is unaffected. If the application validates untrusted input, a vulnerable release could amplify risk through parser or regex-related abuse.

Unpinned Dependencies

Low
Category
Supply Chain
Content
feedparser>=6.0
openai>=1.0
pydantic>=2.0
pyyaml>=6.0
numpy>=1.24
httpx>=0.27
Confidence
97% confidence
Finding
PyYAML has a history of unsafe deserialization issues, and leaving it unpinned makes it unclear which release will be installed. In contexts where YAML may be parsed from untrusted sources, version drift can materially affect exposure to known parser and deserialization flaws.

Unverifiable Dependency: pyyaml has 8 known advisory(ies) (CVE-2019-20477 (Deserialization of Untrusted Data in PyYAML); CVE-2020-1747 (Improper Input Validation in PyYAML); CVE-2020-14343 (Improper Input Validation in PyYAML) +5 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
97% confidence
Finding
PyYAML has multiple historical security advisories, and without version pinning the deployment may resolve to an affected release. Given PyYAML's relevance to deserialization and parsing, the uncertainty is more dangerous than for purely local utility packages if untrusted YAML is ever processed.

Unpinned Dependencies

Low
Category
Supply Chain
Content
openai>=1.0
pydantic>=2.0
pyyaml>=6.0
numpy>=1.24
httpx>=0.27
Confidence
95% confidence
Finding
An unpinned numpy dependency allows unreviewed releases to be installed, undermining reproducibility and making vulnerability status uncertain. While often lower risk than network-facing libraries, it still creates avoidable supply-chain and maintenance risk.

Static analysis

Detected: suspicious.destructive_delete_command

Documentation contains a destructive delete command without an explicit confirmation gate.

Warn
Code
suspicious.destructive_delete_command
Location
README.md:204