Back to skill

Security audit

Daily News Portal (Prasowka)

Security checks for vulnerabilities and agentic risk

Overview

This skill is a mostly coherent Polish news-portal generator, but it needs Review because it renders untrusted news content into HTML unsafely and uses hard-coded write paths beyond the documented workspace.

Review before installing. The main issue is not credential theft or destructive behavior; it is that live news content can be written into an HTML page without escaping, so a malicious article or URL could run browser-side script when the generated portal is opened or clicked. Ask the publisher to escape all rendered fields, validate links to http/https, add rel="noopener noreferrer", clarify which generator is authoritative, and restrict writes to documented workspace paths.

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

Error
Location
scripts/generate_portal.py:275
Finding
Stored HTML and JavaScript Injection in the News Portal Generator<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate_portal.py:275-288` **Vulnerability Type**: Stored HTML injection, stored cross-site scripting, and unsafe URL injection **Risk Level**: High ### Vulnerable Code ```python for article in articles: title = article.get("title", "Bez tytułu") url = article.get("url", "#") source = article.get("source", "Unknown") summary = article.get("summary", "")[:200] if summary: summary += "..." html += f''' <article class="card"> <div class="card-title"><a href="{url}" target="_blank">{title}</a></div> <div class="card-meta">{source}</div> <div class="card-summary">{summary}</div> </article> ''' ``` ### Technical Analysis The generator places remotely supplied article fields directly into an HTML document without contextual output encoding: - `title`, `source`, and `summary` are inserted into HTML element bodies without HTML escaping. - `url` is inserted into an `href` attribute without attribute escaping or URL-scheme validation. - The content is obtained from external news services through `scripts/fetch_news.py`, making these values untrusted. - No Content Security Policy is added to the generated page to reduce the impact of injected scripts. - The external link uses `target="_blank"` without `rel="noopener noreferrer"`. An attacker-controlled title such as the following could introduce executable markup: ```html <img src=x onerror="alert(document.domain)"> ``` An attacker-controlled URL could use a dangerous scheme: ```text javascript:alert(document.domain) ``` The first payload may execute when the generated portal is opened. The second generally requires the user to click the malicious article link. ### Attack Path 1. An attacker submits a story or other content to one of the configured news sources, or compromises a source response. 2. The source returns an attacker-controlled title, description, source f ...[truncated 1249 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every value inserted into an HTML text context: ```python from html import escape safe_title = escape(str(article.get("title", "Bez tytułu")), quote=True) safe_source = escape(str(article.get("source", "Unknown")), quote=True) safe_summary = escape(str(article.get("summary", ""))[:200], quote=True) ``` 2. Validate article URLs with `urllib.parse.urlsplit` and allow only explicitly approved schemes: ```python from urllib.parse import urlsplit from html import escape def safe_external_url(value): value = str(value or "") parsed = urlsplit(value) if parsed.scheme not in {"https", "http"} or not parsed.netloc: return "#" return escape(value, quote=True) ``` 3. Prefer an auto-escaping template engine such as Jinja2 rather than constructing HTML through f-strings. 4. Add `rel="noopener noreferrer"` to links opened in new tabs: ```html <a href="..." target="_blank" rel="noopener noreferrer">...</a> ``` 5. Add a restrictive Content Security Policy appropriate for the generated portal. Avoid inline scripts where possible and move JavaScript into a separately trusted asset. 6. Add automated tests for hostile inputs, including: ```text <img src=x onerror=alert(1)> "><svg onload=alert(1)> javascript:alert(1) data:text/html,<script>alert(1)</script> ``` The tests should verify that markup is rendered as text and that dangerous URL schemes are replaced or rejected. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/prasowka_real.py:174
Finding
Stored HTML and JavaScript Injection in Real-News Article Cards<![CDATA[ ## Vulnerability Details **File Location**: `scripts/prasowka_real.py:174-196` **Vulnerability Type**: Stored HTML injection, stored cross-site scripting, and unsafe URL injection **Risk Level**: High ### Vulnerable Code ```python def generate_card(item): """Generuje kartę artykułu ze streszczeniem""" title = item.get("title", "Bez tytułu") url = item.get("url", "#") source = item.get("source", "Unknown") time = item.get("time", "") score = item.get("score", 0) comments = item.get("comments", 0) summary = item.get("_summary", "") score_html = f'<span class="score">▲ {score}</span>' if score else '' comments_html = f'<span class="comments">💬 {comments}</span>' if comments else '' return f''' <div class="card"> <div class="card-source">{source}</div> <h3 class="card-title"><a href="{url}" target="_blank">{title}</a></h3> <div class="card-summary">{summary}</div> <div class="card-meta"> <span>📅 {time}</span> {score_html} {comments_html} </div> </div> ''' ``` ### Technical Analysis The `generate_card` function interpolates multiple values derived from remote news APIs directly into HTML: - `title`, `source`, `summary`, and `time` are inserted into HTML text contexts without escaping. - `url` is inserted into an HTML attribute without escaping or scheme validation. - `score` and `comments` are also interpolated without explicit type enforcement at the rendering boundary. - `generate_summary` can copy a remotely supplied GitHub repository description into `_summary`, so the summary field can also contain attacker-controlled markup. - The generated document has no Content Security Policy limiting injected scripts. - Links opened using `target="_blank"` omit `rel="noopener noreferrer"`. Because the function processes real external content collected by `fetch_all_news`, a malicious title or description can break out of the intended markup ...[truncated 1454 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Normalize and escape all remotely derived values before rendering: ```python from html import escape title = escape(str(item.get("title", "Bez tytułu")), quote=True) source = escape(str(item.get("source", "Unknown")), quote=True) time = escape(str(item.get("time", "")), quote=True) summary = escape(str(item.get("_summary", "")), quote=True) ``` 2. Require `score` and `comments` to be numeric before interpolation: ```python score = item.get("score", 0) score = score if isinstance(score, (int, float)) else 0 comments = item.get("comments", 0) comments = comments if isinstance(comments, int) else 0 ``` 3. Validate URLs and permit only absolute `https` or, if operationally necessary, `http` URLs. Replace all other values with `#` or omit the link. 4. Use a template engine with automatic HTML escaping and treat pre-rendered HTML fragments as exceptional, reviewed values. 5. Add `rel="noopener noreferrer"` to every external link using `target="_blank"`. 6. Introduce a restrictive Content Security Policy and avoid inline JavaScript. This should be defense in depth rather than a substitute for correct output encoding. 7. Test the complete fetch-to-render pipeline with malicious titles, descriptions, source names, dates, and URLs to ensure the final output contains encoded text and no executable attacker-controlled markup. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (33)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Although most mismatch claims look inconsistent, one security-relevant part is valid: the skill writes output to a predetermined workspace path and updates state files without explicit disclosure of safeguards or confirmation. Hidden or under-documented filesystem side effects can surprise users and, in an automated setting, overwrite or persist data beyond what the user expected.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
Although most mismatch claims look inconsistent, one security-relevant part is valid: the skill writes output to a predetermined workspace path and updates state files without explicit disclosure of safeguards or confirmation. Hidden or under-documented filesystem side effects can surprise users and, in an automated setting, overwrite or persist data beyond what the user expected.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
Although most mismatch claims look inconsistent, one security-relevant part is valid: the skill writes output to a predetermined workspace path and updates state files without explicit disclosure of safeguards or confirmation. Hidden or under-documented filesystem side effects can surprise users and, in an automated setting, overwrite or persist data beyond what the user expected.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
Although most mismatch claims look inconsistent, one security-relevant part is valid: the skill writes output to a predetermined workspace path and updates state files without explicit disclosure of safeguards or confirmation. Hidden or under-documented filesystem side effects can surprise users and, in an automated setting, overwrite or persist data beyond what the user expected.

Hidden Instructions

High
Category
Prompt Injection
Content
<section class="section" id="ai-models">
    <div class="section-title">🤖 Nowe modele AI — [LICZBA] artykułów</div>
    <div class="articles-grid">
      <!-- ARTYKUŁY AI-MODELS -->
      <!-- format każdego artykułu: -->
      <!--
      <article class="article [featured jeśli top]">
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill requests powerful tools (`exec`, `web_search`, `web_fetch`, `write`) but does not declare a narrower tool scope or permissions policy. That increases risk because the workflow performs shell execution, network access, and file writes based on external content, leaving broad capability available without explicit constraint or user-facing guardrails.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill instructs saving an HTML file and updating `seen_urls.json` in the workspace without any warning, consent step, or mention of file modification scope. Silent writes are risky because they can overwrite user data, persist tracking state, or make unintended changes when the skill is run automatically or by a less experienced user.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The manifest sets `lang` to `pl`, which indicates the skill is fixed to Polish. Under the policy criteria, forcing a specific language without user opt-in or a documented justification can be a natural-language policy violation.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
This JavaScript file contains user-facing branding and messages in Polish, such as "PRASÓWKA" and later Polish UI text, without any indication that the skill is region-specific or that users can opt into another language. Under the policy, forcing a specific language without user choice or documented justification is a natural-language policy violation.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The install prompt uses fixed Polish product text such as "Install Prasówka" and "Add to home screen for quick access" in the context of an otherwise Polish-branded experience, with no user-selectable locale handling present in the file. This indicates a single enforced language/locale rather than an offered choice.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The script's comments and emitted log messages are written in Polish, indicating a fixed language choice without any user opt-in or documented locale constraint. This can violate language or locale policy when the skill is expected to be generally usable across users without forcing a specific language.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The file's natural-language instructions and embedded HTML require Polish output, including `lang="pl"`, a Polish title, and Polish summaries. This imposes a specific language/locale without offering the user any choice or documenting a justified regional-only constraint.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The instruction "szukaj po angielsku" mandates using English for Yandex News queries. This is a natural-language locale policy choice embedded in the file, and there is no indication that the user can opt into another language or that the English-only requirement is justified by a region-specific purpose.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This markdown file presents all headings, labels, and instructions in Polish, and there is no indication that the skill is region-specific or that users may opt into this locale. Under the natural-language policy rule, forcing a specific language without user choice is a policy concern.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This Python code fetches data from multiple external services including Hacker News, GitHub, Reddit, V2EX, WallStreetCN, and Yandex using urllib, which transmits system/user request metadata such as IP address and User-Agent to third parties. While network access is the core purpose of a news fetcher, the file contains no confirmation prompt, visible logging, or user-facing notice in comments/docstrings explaining that external requests will be made to several services.

External Transmission

Medium
Category
Data Exfiltration
Content
def fetch_github_trending(limit=10, keyword=None, deep=False):
    try:
        url = "https://api.github.com/search/repositories?q=created:>2026-01-01&sort=stars&order=desc&per_page=50"
        req = urllib.request.Request(url, headers={"User-Agent": "OpenClaw-NewsAggregator/1.0", "Accept": "application/vnd.github+json"})
        with urllib.request.urlopen(req, timeout=10) as r:
            data = json.loads(r.read())
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This code file contains natural-language strings and metadata that force a specific language/locale, including the Polish docstring, HTML lang="pl", and Polish UI/content text. The policy requires offering a language/locale choice or clearly justifying the restriction, neither of which is present here.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The manifest says the skill generates a daily news portal using fetch_news.py, web fetching, and an LLM for summaries. In this file, the page content is hard-coded sample articles across categories and then written directly into HTML, with no use of fetched input data, summaries, or integration with external news-processing outputs.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def fetch_from_source(source, limit):
    """Pobierz newsy ze źródła"""
    result = subprocess.run(
        ["python3", str(BASE_DIR / "scripts/fetch_news.py"),
         "--source", source,
         "--limit", str(limit)],
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The generated page hard-codes `lang="pl"`, which enforces a specific language/locale in the output. This file does not offer a user opt-in or selection mechanism, and the locale restriction is not explicitly justified as region-specific.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
for source, label, limit in sources:
        try:
            print(f"  🔍 {label}...", flush=True)
            result = subprocess.run(
                ["python3", str(SCRIPTS_DIR / "fetch_news.py"), "--source", source, "--limit", str(limit)],
                capture_output=True,
                text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
81% confidence
Finding
The stated purpose is to generate a daily news portal HTML file using news fetching and summaries, but this implementation delegates collection to a subprocess invocation of another script. Launching external processes is a broader execution capability than the manifest suggests and is not clearly justified from the skill description alone.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The document declares lang="pl" and immediately presents the page description in Polish, with the rest of the template continuing in Polish-only labels and navigation text. This enforces a specific language/locale for all users without any visible choice, which matches the policy's language/locale violation criterion.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
This CSS file includes prominent natural-language comments in Polish ("PRASÓWKA 2025/2026" and related wording) without any indication that the skill is region-specific or that users can choose the language. Under the policy criteria, forcing a specific language or locale without opt-in can be a natural-language policy violation even when it appears only in comments or labels.

Natural-Language Policy Violations

Low
Confidence
89% confidence
Finding
The file contains prominent Polish-language comments (e.g. "PRASÓWKA 2025/2026 — ULTRANOWOCZESNY UI") without any indication that the skill is region-specific or that language choice is optional. Under the policy for natural-language violations, hard-coding a specific language can be a locale-policy issue when no opt-in or justification is present.

Static analysis

No suspicious patterns detected.