Back to skill

Security audit

HN Podcast Archive

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it says, but its downloader can fetch arbitrary feed-controlled URLs without enough safeguards.

Install only if you will use trusted RSS feeds or add allowlisting and download safeguards first. Before scheduling it, restrict feed and enclosure URLs to expected HTTPS domains, block local and private-network destinations, add timeouts and size limits, and pin reviewed dependency versions.

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/hn_podcast_archive.py:81
Finding

Unrestricted URL Retrieval Enables SSRF and Local File Copying

Content
View full analysis

Vulnerability Details

File Location: scripts/hn_podcast_archive.py:81-133
Vulnerability Type: Server-Side Request Forgery (SSRF), unsafe URL scheme handling, and unbounded resource retrieval
Risk Level: High

Vulnerable Code

python
def parse_feed(feed_url: str) -> list[Episode]:
    parsed = feedparser.parse(feed_url, agent=DEFAULT_USER_AGENT)
    episodes: list[Episode] = []
    for entry in parsed.entries:
        enclosures = getattr(entry, "enclosures", []) or []
        audio_url = ""
        audio_type = ""
        for enc in enclosures:
            href = enc.get("href") or ""
            enc_type = enc.get("type") or ""
            if href:
                audio_url = href
                audio_type = enc_type
                break
        if not audio_url:
            links = getattr(entry, "links", []) or []
            for link in links:
                href = link.get("href") or ""
                rel = link.get("rel") or ""
                link_type = link.get("type") or ""
                if href and (rel == "enclosure" or str(link_type).startswith("audio/")):
                    audio_url = href
                    audio_type = link_type
                    break
        if not audio_url:
            continue
        title = getattr(entry, "title", "Untitled episode")
        guid = getattr(entry, "id", "") or getattr(entry, "guid", "") or audio_url
        published = getattr(entry, "published", "") or getattr(entry, "updated", "") or ""
        link = getattr(entry, "link", "") or audio_url
        slug = slugify(title)
        episodes.append(Episode(title=title, guid=guid, published=published, link=link, audio_url=audio_url, audio_type=audio_type, slug=slug))
    return episodes
python
def download_file(url: str, dest: Path, dry_run: bool) -> None:
    if dry_run:
        return
    req = urllib.request.Request(url, headers={"User-Agent": DEFAULT_USER_AGENT})
    with urllib.request.urlopen(req) as resp, dest.
...[truncated 2754 chars]
Remediation
View remediation

Remediation Suggestions

  1. Permit only explicitly required URL schemes, preferably HTTPS. Reject file:, ftp:, data:, and all other unsupported schemes.
  2. Reject URLs containing embedded credentials or malformed host components.
  3. Resolve destination hostnames before connecting and reject every resolved address that is loopback, private, link-local, multicast, reserved, unspecified, or otherwise non-public.
  4. Explicitly block known metadata destinations, including link-local cloud metadata addresses.
  5. Disable redirects or validate the scheme, hostname, port, and resolved addresses after every redirect.
  6. Apply explicit connection and read timeouts.
  7. Enforce a configurable maximum download size by checking Content-Length where available and counting bytes while streaming.
  8. Download into a securely created temporary file with restrictive permissions, validate the result, and atomically rename it only after successful completion.
  9. Remove partial files after failed or oversized downloads unless deliberate retention is required and safely quarantined.
  10. Consider an administrator-managed allowlist of trusted feed and media domains for recurring automated deployments.
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (3)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/hn_podcast_archive.py (reported line 149)May include surrounding context.

python
"--output_dir",
        str(transcripts_dir),
    ]
    subprocess.run(cmd, check=True)
    if not expected.exists():
        raise FileNotFoundError(f"Whisper did not produce expected transcript: {expected}")
    return expected

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified as feedparser>=6.0.0, which allows any future version to be installed and makes builds non-reproducible. This can unintentionally introduce vulnerable or breaking releases into the environment, especially since the package has a history of security advisories.

Content

Scanner excerpt · scripts/requirements.txt (reported line 1)May include surrounding context.

text
feedparser>=6.0.0

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
88% confidence
Finding

The manifest references feedparser without pinning to a specific vetted version, while the package has multiple known advisories. Because the resolved version is unknown, the deployment may install an affected release or a future release with unresolved issues, preventing auditors from verifying exposure.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.