Back to skill

Security audit

Feeds Digest

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a legitimate RSS digest tool, but its documentation includes over-privileged cron setup and weakly disclosed external LLM and credential-handling flows that warrant review before installation.

Install only if you are comfortable with a networked RSS tool that writes cache/history locally and may send digest content to an LLM when --llm is used. Prefer user-level cron only, avoid the documented root cron example, keep API keys out of inline cron commands, use trusted feed URLs, and consider pinning dependencies or installing in a dedicated virtual environment.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
README.md:90
Finding

Root Cron Job Executes Code from a User-Controlled Directory

Content
View full analysis
> /var/log/feeds-digest.log 2>&1 EOF ``` ``` ### Technical Analysis The documentation recommends a system-wide cron entry that runs the Skill as `root`, even though generating a feed digest does not require administrative privileges. The executable is located under `/home/HolBot`, a location that may be writable by the unprivileged `HolBot` account. The Python entry point also imports modules from its adjacent `scripts/lib` directory. Consequently, write access to either `feeds-digest.py` or an imported Python module can become a path to scheduled root-level code execution. The installer does not automatically create this cron job, so exploitation requires an administrator to follow the documented system-wide setup. Nevertheless, the recommended configuration violates least privilege and creates an avoidable privilege boundary. ### Attack Path 1. An administrator installs the documented `/etc/cron.d/feeds-digest` entry. 2. The project directory or one of its Python files remains writable by `HolBot` or another compromised unprivileged account. 3. The attacker modifies `scripts/feeds-digest.py` or a module under `scripts/lib/` to run an arbitrary command. 4. Cron invokes the modified script according to the schedule. 5. The injected code executes with root privileges. ### Impact Assessment Successful exploitation provides arbitrary root-level code execution ...[truncated 451 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/lib/sources/base.py:30
Finding

Configurable Feed URLs Permit Server-Side Request Forgery and Unbounded Downloads

Content
View full analysis
SourceResult: if not self.url: return SourceResult( name=self.name, source_type=self.source_type, error="url fehlt", ) try: content = self._http_get(self.url) ``` ```python # scripts/lib/sources/ms_techcommunity.py:13-21 def __init__(self, name: str, config: dict): super().__init__(name, config) self.url = config.get("url", "") def fetch(self) -> SourceResult: if not self.url: return SourceResult( name=self.name, source_type=self.source_type, error="url fehlt", ) try: content = self._http_get(self.url) ``` ```python # scripts/lib/sources/base.py:30-38 def _http_get(self, url: str) -> bytes | None: """HTTP GET with timeout + UA. Returns None on error.""" try: r = requests.get( url, headers={"User-Agent": USER_AGENT}, timeout=TIMEOUT, ) r.raise_for_status() return r.content ``` ### Technical Analysis The generic RSS and Microsoft Tech Community source implementations pass a configuration-controlled URL directly to `requests.get`. The HTTP layer does not enforce: - An `https` scheme. - An approved hostname list. - Rejection of loopback, private, link-local, multicast, or reserved addresses. - Blocking of cloud metadata endpoints. - DNS rebinding protections. - Validation of every redirect destination. - A maximum response size. The `reques ...[truncated 1893 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Warning
Location
scripts/lib/llm.py:52
Finding

Untrusted Feed Content Can Inject Instructions into LLM Summarization

Content
View full analysis
list[FeedItem]: """Parse feed content into FeedItems.""" parsed = feedparser.parse(content) items: list[FeedItem] = [] for entry in parsed.entries: published = _parse_date(entry) description = _extract_description(entry) link = entry.get("link", "") items.append( FeedItem( source=self.name, source_type=self.source_type, title=entry.get("title", "").strip(), link=link, published=published, description=description, topics=list(self.topics), guid=entry.get("id", "") or entry.get("guid", "") or link, ) ) ``` ```python # scripts/lib/formatter.py:69-78 for item in source_items: date_str = item.published.strftime("%Y-%m-%d") if item.published else "?" title = _escape_md(item.title) link = item.link lines.append(f"- **{date_str}** - [{title}]({link})") if item.description: lines.append(f" *{item.description}*") lines.append("") ``` ```python # scripts/feeds-digest.py:198-211 if llm_flag: if not quiet: click.echo("🤖 LLM-Summary wird generiert...", err=True) try: llm_cfg = config_data["llm"] prompt_template = llm_lib.load_prompt_template(llm_cfg["prompt_file"]) summary = llm_lib.summarize( digest_markdown=output_text, provider=llm_cfg["provider"], model=llm_cfg["model"], prompt_template=prompt_template, max_tokens=llm_cfg["max_tokens"], ...[truncated 2499 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Installer Executes Open-Ended Third-Party Dependency Versions

Content
View full analysis
=6.0.10 requests>=2.31.0 click>=8.1.7 pyyaml>=6.0.1 python-dateutil>=2.8.2 ``` ```bash # scripts/install.sh:25-26 echo "📦 Installiere Python-Dependencies..." pip3 install --quiet --user -r "$SKILL_ROOT/requirements.txt" 2>&1 | tail -5 ``` ### Technical Analysis All dependencies use minimum-version constraints without upper bounds or hashes. Each installation can therefore resolve to a different future package version that was not reviewed with this project. Python package installation can execute package build logic, and installed package code runs when imported by the Skill. If a dependency publisher account, release process, or distribution artifact is compromised, running the installer may execute malicious code with the installing user's privileges. The use of `pip3 install --user` also modifies the user's shared Python environment rather than creating an isolated environment. This can cause dependency conflicts and may affect other Python applications used by the same account. No evidence was found that the listed package names are currently typosquatted or intentionally malicious. The finding concerns unsafe dependency resolution and installation practices rather than a confirmed compromised package. ### Attack Path 1. A listed dependency publishes a future version after the Skill was audited. 2. The future release is compromised, malicious, or otherwise unsafe. 3. A user runs `scripts/install.sh`. 4. Pip resolves the unrestricted new version because it satisfies the `>=` constraint. 5. Package build or installation code executes, or malicious code executes when the Skill imports the package. 6. The package gains the privileges and accessible data of the installing user ...[truncated 535 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (50)

Tainted flow: 'url' from os.environ.get (line 109, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/lib/llm.py (reported line 62)May include surrounding context.

python
}

    try:
        r = requests.post(url, json=payload, headers=headers, timeout=60)
        r.raise_for_status()
        data = r.json()
        return data["choices"][0]["message"]["content"]

Tainted flow: 'url' from os.environ.get (line 109, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/lib/llm.py (reported line 95)May include surrounding context.

python
}

    try:
        r = requests.post(url, json=payload, headers=headers, timeout=60)
        r.raise_for_status()
        data = r.json()
        return data["choices"][0]["message"]["content"]

Tainted flow: 'url' from os.environ.get (line 109, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

The Ollama request target is built from OLLAMA_HOST, an environment variable, and then used directly in requests.post without validation. If an attacker can influence the environment, they can redirect digest contents to an arbitrary host, enabling SSRF-like behavior and unintended data exfiltration of the summarized feed content.

Content

Scanner excerpt · scripts/lib/llm.py (reported line 118)May include surrounding context.

python
}

    try:
        r = requests.post(url, json=payload, timeout=120)
        r.raise_for_status()
        data = r.json()
        return data.get("response", "")

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% 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).

Content

Scanner excerpt · README.md (reported line 197)May include surrounding context.

md
- `feeds-digest --llm` zeigt Fehler-Details in stderr

### Doppelte Items
Cache prüfen: `feeds-digest --history`. Falls veraltet: `rm ~/.cache/feeds-digest/history/*.jsonl`

## LLM-Kosten

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · README.md (reported line 15)May include surrounding context.

Oder manuell:

bash
mkdir -p ~/.config/feeds-digest
cp config/config.example.yaml ~/.config/feeds-digest/config.yaml
pip3 install --user -r requirements.txt

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The README encourages use of an external LLM API key for summaries but does not clearly warn that feed content may be transmitted to a third-party provider. In an automation context, users may assume processing remains local and unintentionally disclose internal or sensitive feed contents to an external service.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
85% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · README.md (reported line 86)May include surrounding context.

Installation via User-Crontab:

bash
crontab -e
# Obige Zeile einfügen, speichern, fertig.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
75% confidence
Finding

The README includes a root-level cron installation example under /etc/cron.d/, which encourages privileged execution of a network-fetching script. If the script, dependencies, or writable paths are compromised, running as root increases the blast radius to system-wide files and persistence.

Content

Scanner excerpt · README.md (reported line 93)May include surrounding context.

Oder system-weit (/etc/cron.d/):

bash
sudo tee /etc/cron.d/feeds-digest <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 21 * * 0,3,5 root cd /home/HolBot && TZ=CET /home/HolBot/skills/feeds-digest/scripts/feeds-digest.py --since 7d --topics bc,fabric,openclaw --output /home/HolBot/reports/digest-$(date +\%Y-\%m-\%d).md --quiet >> /var/log/feeds-digest.log 2>&1

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The README instructs users to decrypt a GPG-protected secrets file inline inside a cron entry and export the resulting API key into the process environment. That expands the skill’s scope from feed aggregation into secret-handling guidance, and increases exposure risk through shell history, process/environment inspection, logging mistakes, and copy-paste reuse in broader automation.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · README.md (reported line 185)May include surrounding context.

"Config nicht gefunden"

bash
mkdir -p ~/.config/feeds-digest
cp config/config.example.yaml ~/.config/feeds-digest/config.yaml

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill advertises behaviors that imply network access, environment access, and writing output files, but it does not declare any explicit tool scope or permissions. This weakens user awareness and policy enforcement, increasing the chance that an agent executes the skill with broader capabilities than intended.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The --llm option indicates feed content may be sent to an external model service, but the skill description does not warn users about this data transfer. That omission can lead to unintentional disclosure of feed contents, internal URLs, tokens accidentally present in feeds, or other sensitive contextual data to third-party providers.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

When --llm is enabled, the tool sends the generated digest content to a configured LLM provider, which may be a remote third party. The code does not present an explicit warning or confirmation at the point of transmission, so users may unintentionally exfiltrate feed contents, metadata, or internally aggregated summaries to an external service.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The file generates user-facing markdown strings in German, including headings, status text, and the LLM prompt suggestion, and the comment indicates a German/Berlin default tailored to a specific person. This forces a specific language/locale without any opt-in, fallback choice, or documented region-specific justification, which matches the language/locale policy violation category.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/lib/llm.py (reported line 46)May include surrounding context.

python
if not api_key:
        raise LLMSummaryError("PERPLEXITY_API_KEY nicht gesetzt")

    url = "https://api.perplexity.ai/chat/completions"
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This code path sends digest content to Perplexity for summarization without any visible consent, disclosure, or data-classification check in the implementation shown. If feeds contain sensitive internal links, titles, or notes, users may unknowingly transfer that data to a third-party service.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/lib/llm.py (reported line 62)May include surrounding context.

python
}

    try:
        r = requests.post(url, json=payload, headers=headers, timeout=60)
        r.raise_for_status()
        data = r.json()
        return data["choices"][0]["message"]["content"]

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/lib/llm.py (reported line 95)May include surrounding context.

python
}

    try:
        r = requests.post(url, json=payload, headers=headers, timeout=60)
        r.raise_for_status()
        data = r.json()
        return data["choices"][0]["message"]["content"]

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/lib/llm.py (reported line 118)May include surrounding context.

python
}

    try:
        r = requests.post(url, json=payload, headers=headers, timeout=60)
        r.raise_for_status()
        data = r.json()
        return data["choices"][0]["message"]["content"]

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/lib/llm.py (reported line 79)May include surrounding context.

python
if not api_key:
        raise LLMSummaryError("OPENAI_API_KEY nicht gesetzt")

    url = "https://api.openai.com/v1/chat/completions"
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The OpenAI path transmits the digest to an external API with no user-facing confirmation or minimization controls evident in this code. In a feed-digest skill, aggregated content may come from multiple sources and could unexpectedly include confidential or proprietary information that is then disclosed to a third party.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

This markdown file includes examples that write digest results to a user-specified file and later shows installation into /etc/cron.d/, which affects persistent system configuration. The README explains how to perform these actions but does not explicitly warn that they modify files or system cron state, which is the kind of user-data/system-integrity impact SQP-2 asks markdown files to disclose.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The natural-language description and usage guidance are presented in German throughout the skill file. This can constitute a language-policy issue when the skill forces a specific language without user opt-in or justification.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The instruction requires the summary to be produced in German by default, switching to English only if the source data is English. This is a natural-language locale constraint that does not appear to offer the user a choice, which can conflict with language or locale preference policies.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

This markdown file contains user-facing instructions exclusively in German, and there is no indication that the skill supports other languages or that German is a justified regional requirement. Under the policy rule, forcing a specific language without user opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.