T09 · Insecure Skill Coding Practices
- 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: HighVulnerable 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 episodespython 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
- Permit only explicitly required URL schemes, preferably HTTPS. Reject
file:,ftp:,data:, and all other unsupported schemes. - Reject URLs containing embedded credentials or malformed host components.
- Resolve destination hostnames before connecting and reject every resolved address that is loopback, private, link-local, multicast, reserved, unspecified, or otherwise non-public.
- Explicitly block known metadata destinations, including link-local cloud metadata addresses.
- Disable redirects or validate the scheme, hostname, port, and resolved addresses after every redirect.
- Apply explicit connection and read timeouts.
- Enforce a configurable maximum download size by checking
Content-Lengthwhere available and counting bytes while streaming. - Download into a securely created temporary file with restrictive permissions, validate the result, and atomically rename it only after successful completion.
- Remove partial files after failed or oversized downloads unless deliberate retention is required and safely quarantined.
- Consider an administrator-managed allowlist of trusted feed and media domains for recurring automated deployments.
- Permit only explicitly required URL schemes, preferably HTTPS. Reject
