Back to skill

Security audit

Danish News Aggregator

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real Danish RSS aggregator, but it has serious under-disclosed network-safety issues and overstates what the shipped code actually does.

Install only if you are comfortable reviewing or fixing the scripts first. Prefer running in a restricted environment, pin the Python dependencies, remove the TLS-bypass code, avoid HTTP feeds, restrict feeds.json to trusted HTTPS domains, and do not add the cron job until the behavior and output claims match what you expect.

Vulnerability Patterns
  • 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
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
aggregate_feeds.py:58
Finding
TLS Certificate and Hostname Verification Disabled<![CDATA[ ## Vulnerability Details **File Location**: `aggregate_feeds.py:58-68` **Vulnerability Type**: Improper certificate validation (CWE-295) **Risk Level**: High ### Complete Code Snippet ```python try: # Create SSL context that doesn't verify certificates (for testing) ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE req = urllib.request.Request( url, headers={"User-Agent": USER_AGENT} ) with urllib.request.urlopen(req, timeout=30, context=ctx) as response: content = response.read().decode("utf-8") ``` ### Technical Analysis The feed-fetching implementation explicitly disables both TLS certificate verification and hostname checking. Consequently, the client does not verify that an HTTPS response was produced by the legitimate server identified by the configured URL. Disabling these protections is not necessary for the declared RSS aggregation functionality. The default Python TLS context already validates trusted certificate chains and hostnames. The comment indicating that this behavior is “for testing” does not reduce the risk because the insecure context is used unconditionally in the production execution path. An attacker capable of intercepting or modifying network traffic can impersonate any configured HTTPS feed source. The attacker can then supply a forged RSS or Atom document containing fabricated headlines, attacker-controlled links, or content designed to exploit weaknesses in feed generation and downstream RSS clients. ### Attack Path 1. A user runs `aggregate_feeds.py` on an untrusted or compromised network. 2. The script initiates an HTTPS request to a configured feed. 3. A network-positioned attacker intercepts the connection and presents an arbitrary certificate. 4. The script accepts the certificate because certificate and hostname verification are disabled. 5. The attacker returns a forged RSS or Atom response. 6. The forged ...[truncated 607 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the custom TLS settings that disable validation. - Use the default verified TLS behavior: ```python req = urllib.request.Request( url, headers={"User-Agent": USER_AGENT}, ) with urllib.request.urlopen(req, timeout=30) as response: content = response.read().decode("utf-8") ``` - If a private certificate authority is legitimately required, load only that specific trusted CA rather than disabling all verification. - Fail closed on certificate or hostname validation errors. - Add automated tests confirming that expired, self-signed, and hostname-mismatched certificates are rejected. - Log validation failures without retrying through an insecure fallback. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
aggregate_feeds.py:47
Finding
Unrestricted Configurable URL Fetching Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `aggregate_feeds.py:47-68` **Vulnerability Type**: Server-Side Request Forgery (CWE-918) and cleartext transport **Risk Level**: High ### Complete Code Snippet ```python def fetch_feed(feed_info): """Fetch and parse a single RSS feed.""" name = feed_info.get("name", "unknown") url = feed_info.get("url", "") if not url: print(f"No URL for feed: {name}", file=sys.stderr) return [] print(f"Fetching: {name}...") try: # Create SSL context that doesn't verify certificates (for testing) ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE req = urllib.request.Request( url, headers={"User-Agent": USER_AGENT} ) with urllib.request.urlopen(req, timeout=30, context=ctx) as response: content = response.read().decode("utf-8") ``` The bundled configuration also contains a cleartext HTTP endpoint: ```json {"name": "Bold.dk", "url": "http://www.bold.dk/feed/rss_by_tag/246752", "category": "sports"} ``` ### Technical Analysis Feed URLs are loaded from `feeds.json` and passed directly to `urllib.request.Request` and `urlopen`. The implementation does not validate: - The URL scheme. - The destination hostname or port. - Whether DNS resolves to loopback, private, link-local, or other restricted addresses. - The destination reached after an HTTP redirect. - Whether the destination belongs to an approved feed-source domain. A user or process capable of modifying `feeds.json` can therefore cause the Skill to make requests to unintended local or internal services. A malicious external feed may also redirect the request to a restricted destination unless redirects and post-redirect destinations are validated. The bundled `http://` source creates an additional integrity risk because its traffic is not encrypted or authentic ...[truncated 1678 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Permit only `https` URLs. - Maintain an explicit allowlist of expected feed hostnames. - Reject URLs containing embedded credentials or unexpected ports. - Resolve destination hostnames and reject loopback, private, link-local, multicast, reserved, and unspecified IP ranges. - Validate every redirect target using the same policy, or disable automatic redirects and process approved redirects explicitly. - Protect against DNS rebinding by connecting only to the validated resolved address while preserving correct TLS hostname verification. - Apply conservative response-size limits and content-type checks. - Replace the bundled `http://www.bold.dk/...` endpoint with a verified HTTPS endpoint or remove it if HTTPS is unavailable. - Restrict write access to `feeds.json` and document that it is security-sensitive configuration. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
aggregator.py:125
Finding
Untrusted Remote Feed Fields Are Directly Interpolated into Generated XML<![CDATA[ ## Vulnerability Details **File Location**: `aggregator.py:125-151` **Vulnerability Type**: Improper neutralization of data in XML output (CWE-91) **Risk Level**: Medium ### Complete Code Snippet ```python def generate_rss(entries, feed_title, feed_description, feed_url): """Generate RSS 2.0 XML from entries.""" items = "" for entry in entries[:CONFIG["max_items_per_feed"]]: items += f"""<item> <title><![CDATA[{entry['title']}]]></title> <link>{entry['link']}</link> <description><![CDATA[{entry['summary']}]]></description> <pubDate>{entry['published']}</pubDate> <source>{entry['source']}</source> <category>{feed_title}</category> </item> """ rss = f"""<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"> <channel> <title>{feed_title}</title> <description>{feed_description}</description> <link>{feed_url}</link> <language>da</language> <lastBuildDate>{datetime.now().strftime('%a, %d %b %Y %H:%M:%S +0000')}</lastBuildDate> <atom:link href="{feed_url}" rel="self" type="application/rss+xml"/> <generator>Danish News Aggregator v1.0</generator> {items}</channel> </rss>""" ``` ### Technical Analysis Article titles, links, summaries, publication dates, and source names originate from remote feeds and are inserted into an XML document through string interpolation. The `link`, `pubDate`, and `source` fields are not XML-escaped. XML metacharacters such as `<`, `>`, and `&` can therefore terminate or introduce elements. Although titles and descriptions are wrapped in CDATA sections, an attacker-controlled value containing the CDATA terminator `]]>` can close the section and inject additional XML markup. This can produce malformed output or alter the structure and semantics of the generated RSS document. The risk is amplified because the project retrieves remote content and includes at least ...[truncated 1430 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not construct XML through string interpolation. - Use `xml.etree.ElementTree`, `lxml`, or another established XML serializer and assign all remote values as text nodes. - If CDATA is required, use a library that safely handles or splits embedded `]]>` sequences. - Validate links and permit only expected `https` URLs before including them. - Normalize and validate publication dates before serialization. - Treat descriptions as untrusted content. If they are later rendered as HTML, sanitize them with a maintained allowlist-based HTML sanitizer at the rendering boundary. - Add regression tests containing `&`, `<`, `>`, quotes, and `]]>` in every remotely sourced field. - Parse the generated document in tests to confirm it remains well-formed. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:25
Finding
Third-Party Python Dependencies Are Installed Without Version or Integrity Pinning<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:25-28` **Vulnerability Type**: Unpinned third-party dependency installation **Risk Level**: Medium ### Complete Code Snippet ```bash # Install dependencies pip install feedparser python-dateutil ``` ### Technical Analysis The installation instructions retrieve mutable latest versions of `feedparser` and `python-dateutil` without exact version constraints, package hashes, or a committed lockfile. This makes installations non-reproducible and prevents users from verifying that the installed artifacts match versions reviewed during the Skill audit. The package names shown are established packages rather than apparent typosquatting attempts, and the instructions use the default Python package index rather than an explicitly untrusted source. The confirmed weakness is therefore insufficient supply-chain control, not evidence that the current dependencies are malicious. Package installation can execute package build or installation logic with the privileges of the user running `pip`. If a future resolved release or its distribution channel is compromised, following these instructions could execute attacker-controlled installation code. ### Attack Path 1. A user follows the documented `pip install` command at a later time. 2. `pip` resolves whichever package versions are current and compatible at that time. 3. A newly released, compromised, or maliciously replaced dependency artifact is selected. 4. Installation or build logic executes with the invoking user’s privileges. 5. Malicious package code can then run during installation or when the aggregator imports the dependency. This is a conditional supply-chain attack path; the audit found no evidence that the currently named packages or available releases are malicious. ### Impact Assessment If the dependency supply chain is compromised, malicious package code would execute with the privileges of the account running `pip` or the aggregator. This ...[truncated 254 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Provide a reviewed `requirements.txt` or lockfile with exact versions. - Pin artifact hashes, for example by using `pip install --require-hashes -r requirements.txt`. - Generate lock data from a controlled environment and review dependency updates before merging them. - Install dependencies in a dedicated virtual environment rather than the system Python environment. - Prefer prebuilt, verified wheels where appropriate and avoid untrusted package indexes. - Run dependency vulnerability and provenance checks in continuous integration. - Document a controlled update process so security fixes can be adopted without reverting to unconstrained versions. ]]>
Vulnerability Patterns
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • 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
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The code does implement a Danish news RSS aggregation function, so the broad theme matches. However, the declared description materially overstates the implementation. The script creates a single merged RSS feed, not six category-based feeds. It does not contain logic for categorization, deduplication, authority scoring, or scheduled refresh. It loads feeds from a JSON file or a default list of five sources, which is inconsistent with the claim of combining 100+ feeds. These are core product-behavior discrepancies rather than minor implementation details, so this should be flagged as a mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The code substantially aligns with the general purpose of aggregating Danish RSS feeds into category-based RSS outputs, including deduplication and authority-based sorting. However, several material claims in the description are not implemented as stated. Most notably, there is no automatic 15-minute refresh mechanism, the feed count is far below the claimed 100+, and the output count is 7 feeds rather than 6 because the code generates an 'all' feed in addition to six category feeds. The specific claim that danish-all.xml combines the top 30 sources is also not implemented. These are significant enough to count as a description/behavior mismatch, even though the overall theme and primary function are related.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
- ✅ Source authority ranking (DR > major newspapers > regional)
- ✅ Time filtering (last 24h by default)
- ✅ RSS 2.0 compliant
- ✅ UTF-8 encoding
- ✅ Auto-refresh (15 min interval)
- ✅ Media RSS extensions for images

## Hosting

### Self-Host (Docker)
```bash
docker build -t danish-news-aggregator .
docker run -d -p 8080:8080 danish-news-aggregator
```

### Cron Job
```bash
# Add to crontab
*/15 * * * * cd /path/to/aggregator && python3 aggregator.py
```

## Subscribe

Add these URLs to your RSS reader:

- https://your-domain.com/danish-all.xml
- https://your-domain.com/danish-national.xml
- https://your-domain.com/danish-regional.xml
- https://your-domain.com/danish-sports.xml
- https://your-domain.com/danish-business.xml
- https://your-domain.com/danish-tech.xml
- https://your-domain.com/danish-english.xml

## Credits

Aggregates from: DR, Berlingske, Politiken, Information, Nordjyske, Fyens, JydskeVestkysten, Bold.dk, Tipsbladet, TV2 Sport, Finans, Nationalbanken, Vers
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The manifest describes a comprehensive aggregator that outputs six distinct feeds (all, national, regional, sports, business, tech, and English), deduplicates items, and ranks them by source authority. The implementation defines a single output file at L020 and in main/generate_rss only aggregates all fetched items into one RSS file sorted by parsed publication date, with no category partitioning, deduplication, or authority-ranking logic.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The code disables TLS certificate validation and hostname checking for every feed fetch, which allows a man-in-the-middle attacker to intercept HTTPS connections and supply attacker-controlled RSS content. In this skill context, that can poison the aggregated output with falsified links, misleading news items, or malicious payloads embedded in feed fields while making the traffic appear trusted.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding
Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The file’s natural-language strings and RSS metadata explicitly force a Danish locale, including the channel title/description and the RSS language tag set to "da". The policy requires either user opt-in for language constraints or clear documentation that the skill is intentionally region-specific; this file does neither within its user-facing behavior.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The generated RSS always sets `<language>da</language>`, which imposes a specific locale in output content. The file also aggregates an explicit English category, so forcing Danish for all generated feeds is a natural-language locale policy issue rather than a purely technical default.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The manifest says the skill produces 6 curated RSS feeds, but the implementation builds seven files: danish-all.xml plus six category feeds. This is a direct mismatch between the stated outputs and the actual behavior of the skill.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The manifest promises an auto-refreshing Danish news aggregator, which implies current feed output. This file contains much older articles from 2022 and 2020 alongside 2026 items, indicating the output is not purely current news as described.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The manifest describes a Danish news RSS aggregator producing curated news feeds, which implies timely news content. This file includes evergreen feature content such as advice on making a good impression at a Danish home, which is not news and falls outside the described aggregation purpose.

Intent-Code Divergence

Low
Confidence
96% confidence
Finding
The function docstring says duplicates are removed based on title similarity, and an inline comment says to use link as key. The actual implementation only keeps entries with unique non-empty links, which contradicts the documented deduplication behavior and materially differs from the manifest's broader 'deduplicates' claim.

Natural-Language Policy Violations

Low
Confidence
79% confidence
Finding
The RSS metadata explicitly sets the channel language to Danish via `<language>da</language>`. Under the stated policy, forcing a specific language without user opt-in or documented justification can be a locale-policy issue, and this file does not provide any such opt-in or explanation.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
The title and description present the resource as exclusively Danish, with no indication that users can choose another language or locale. Under the stated policy, forcing a specific language or locale without user opt-in can be a natural-language policy violation unless the constraint is explicitly justified.

Description-Behavior Mismatch

Low
Confidence
87% confidence
Finding
The manifest says the skill produces 6 curated RSS feeds limited to all, national, regional, sports, business, tech, and English-language Denmark. This file documents additional source domains and content types including politics, universities, international coverage, podcasts, radio, and weather, which go beyond the stated category-based feed scope.

Intent-Code Divergence

Low
Confidence
95% confidence
Finding
The file states 'Total Feeds: 80+' while the manifest description claims the aggregator 'combines 100+ Danish RSS feeds.' These two intent statements are directly inconsistent about the skill’s advertised coverage.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
The file identifies the skill as a "Danish News Aggregator" with a description focused on Danish RSS feeds, which imposes a Denmark-specific language/locale scope in the skill metadata. Because there is no accompanying opt-in, alternative locale handling, or documented justification in this file, this can be read as a locale policy constraint.

Description-Behavior Mismatch

Low
Confidence
91% confidence
Finding
The manifest says the skill produces 6 curated feeds covering national, regional, sports, business, tech, English-language, plus an all-news feed, but this configuration includes additional categories such as domestic, foreign, politics, and weather. That indicates the configured source scope is broader than the described output taxonomy, creating a semantic mismatch between the advertised aggregation scope and the actual configured inputs.

Static analysis

No suspicious patterns detected.