Back to skill

Security audit

daily-tech-digest

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent news-digest purpose, but its scripts can automatically sync the whole Obsidian vault to Git and delete or rewrite files, creating review-worthy privacy and data-loss risk.

Review this skill before installing. Use it only with a dedicated Obsidian vault or repository, disable or modify automatic Git push, avoid git add ., keep backups before organizer runs, and only enable cron after confirming the dependent skills and phone-push behavior are trusted.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/daily_tech_digest.py:34
Finding

Server-Side Request Forgery Through Unrestricted News URLs

Content
View full analysis

Vulnerability Details

File Location: scripts/daily_tech_digest.py, lines 34-52, 157-160, and 220
Vulnerability Type: Unrestricted outbound URL fetching and SSRF
Risk Level: High

Vulnerable Code

python
def fetch_url(url: str) -> str | None:
    """Fetch URL and return HTML text."""
    try:
        req = Request(url, headers={"User-Agent": UA})
        with urlopen(req, timeout=TIMEOUT) as resp:
            raw = resp.read()
            for enc in ("utf-8", "gbk", "gb2312"):
                try:
                    return raw.decode(enc, errors="replace")
                except (LookupError, UnicodeDecodeError):
                    continue
            return raw.decode("utf-8", errors="replace")
    except (URLError, HTTPError, OSError) as e:
        log(f"Fetch failed {url}: {e}")
        return None

The external URL is subsequently passed directly into the fetching routine:

python
content = extract_article_content(item['url'], max_length=400)

Technical Analysis

News items are obtained from the external local dependency daily-tech-broadcast. Each item can provide an arbitrary url, which is passed to urllib.request.urlopen without validation.

The implementation does not:

  • Restrict requests to HTTPS.
  • Apply an allowlist of trusted news domains.
  • Reject URLs containing embedded credentials.
  • Reject non-HTTP URL schemes such as file:.
  • Resolve and reject loopback, private, link-local, multicast, or reserved IP addresses.
  • Revalidate the destination after HTTP redirects.
  • Limit the maximum response size before reading it into memory.

Consequently, an attacker who controls or compromises the news feed can cause the skill to request resources that are not legitimate public news articles.

Attack Path

  1. An attacker compromises the daily-tech-broadcast data source or otherwise causes it to return a crafted news item.
  2. The ...[truncated 1710 chars]
Remediation
View remediation

Remediation Suggestions

  1. Permit only https URLs and reject all other schemes.
  2. Maintain an explicit allowlist of approved news hostnames.
  3. Reject URLs with usernames, passwords, malformed ports, or ambiguous host representations.
  4. Resolve the hostname before connecting and reject all loopback, private, link-local, multicast, unspecified, and reserved IPv4 and IPv6 addresses.
  5. Disable automatic redirects or validate the scheme, hostname, and resolved address after every redirect.
  6. Protect against DNS rebinding by connecting only to the validated resolved address while preserving the intended TLS hostname.
  7. Apply response-size and content-type limits before reading the body.
  8. Run the fetcher with restricted network access so it cannot reach localhost, cloud metadata addresses, or private network ranges.
  9. Treat all retrieved article text as untrusted when embedding it in Markdown or forwarding it to downstream systems.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/daily_tech_digest.py:430
Finding

Unrelated Vault Content May Be Committed and Pushed to Git

Content
View full analysis

Vulnerability Details

File Location: scripts/daily_tech_digest.py, lines 430-447; scripts/daily_news_organizer.py, lines 221-238
Vulnerability Type: Excessive Git staging scope and unintended data disclosure
Risk Level: High

Vulnerable Code

The digest generator stages the entire vault:

python
def git_commit_and_push(date_str):
    """Git commit and push."""
    try:
        original_dir = os.getcwd()
        os.chdir(OBSIDIAN_VAULT)

        subprocess.run(["git", "pull"], capture_output=True, timeout=30)
        subprocess.run(["git", "add", "."], capture_output=True, timeout=10)
        subprocess.run(
            ["git", "commit", "-m", f"Add daily technology digest: {date_str}"],
            capture_output=True,
            timeout=10
        )
        result = subprocess.run(["git", "push"], capture_output=True, timeout=30)

The organizer repeats the same broad staging operation:

python
def git_commit_and_push(date_str, article_count):
    """Git commit and push."""
    try:
        original_dir = os.getcwd()
        os.chdir(OBSIDIAN_VAULT)

        import subprocess
        subprocess.run(["git", "pull"], capture_output=True, timeout=30)
        subprocess.run(["git", "add", "."], capture_output=True, timeout=10)
        subprocess.run(
            ["git", "commit", "-m", f"Organize daily news: {date_str} ({article_count} archived)"],
            capture_output=True,
            timeout=10
        )
        result = subprocess.run(["git", "push"], capture_output=True, timeout=30)

Technical Analysis

Both scripts change the working directory to the shared Obsidian vault and execute git add .. This stages every eligible changed or untracked file under the repository, not only files generated by this skill.

The operation is unconditional in the normal execution flow and is followed by git commit and git push. The scripts do not ...[truncated 1872 chars]

Remediation
View remediation

Remediation Suggestions

  1. Make Git synchronization explicitly opt-in rather than unconditional.
  2. Stage only the files created or intentionally modified by the current run. For example, pass the exact generated note and index paths to git add --.
  3. Never use git add ., git add -A, or an equivalent repository-wide operation in a shared vault.
  4. Resolve and verify every staged path against approved digest and archive directories.
  5. Inspect git diff --cached --name-only before committing and abort if any unexpected path is present.
  6. Maintain restrictive ignore rules for secrets, temporary files, environment files, credentials, and unrelated private content.
  7. Verify the Git remote URL and repository ownership before pushing.
  8. Use a dedicated repository or worktree for generated digest content rather than sharing a repository with private notes.
  9. Check return codes for git pull, git add, and git commit; abort subsequent operations on failure.
  10. If unintended disclosure has already occurred, remove the data from repository history, rotate exposed credentials, and assess all clones and mirrors of the remote.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/daily_news_organizer.py:136
Finding

Archive Category Path Traversal Allows Writes and Deletions Outside the Archive

Content
View full analysis

Vulnerability Details

File Location: scripts/daily_news_organizer.py, lines 136-153
Vulnerability Type: Directory traversal through an unvalidated category name
Risk Level: High

Vulnerable Code

python
def save_article_notes(articles: list[dict], date_str: str) -> list[str]:
    """Save each news item as an independent note."""
    saved_paths = []

    for article in articles:
        note_content, safe_title = generate_article_note(article, date_str)

        category_folder_name = CATEGORY_FOLDERS.get(
            article['category'],
            article['category']
        )
        category_folder = (
            Path(OBSIDIAN_VAULT) / ARCHIVE_FOLDER / category_folder_name
        )
        category_folder.mkdir(parents=True, exist_ok=True)

        filename = f"{date_str}_{safe_title}.md"
        file_path = category_folder / filename

        if file_path.exists():
            file_path.unlink()

        for old_file in category_folder.glob(
            f"{date_str}_{safe_title}_*.md"
        ):
            old_file.unlink()

        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(note_content)

Technical Analysis

Known category names are mapped through CATEGORY_FOLDERS, but unknown categories fall back to the raw value from article['category']:

python
CATEGORY_FOLDERS.get(article['category'], article['category'])

The organizer obtains this value by parsing the category heading from the daily Markdown digest. There is no allowlist enforcement and no rejection of path separators or traversal components such as ...

pathlib does not automatically confine joined paths to their apparent parent. A category such as ../../target causes the resulting path to resolve outside OBSIDIAN_VAULT/ARCHIVE_FOLDER. The code then creates directories, overwrites the generated filename, and deletes matching files in that attacker- ...[truncated 2381 chars]

Remediation
View remediation

Remediation Suggestions

  1. Reject every category that is not an exact key in CATEGORY_FOLDERS; do not fall back to the untrusted category value.
  2. Map accepted logical category names to fixed, developer-controlled directory names.
  3. Reject category values containing path separators, . or .. components, null characters, or absolute paths.
  4. Resolve the archive root and candidate destination with Path.resolve().
  5. Verify confinement before any filesystem operation, for example by confirming that the resolved destination is relative to the resolved archive root.
  6. Apply the same confinement check to every file before opening or deleting it.
  7. Avoid wildcard deletion where possible. Track exact files generated by previous runs and delete only explicitly validated paths.
  8. Run the organizer under an account whose filesystem permissions are limited to the intended digest and archive directories.
  9. Treat the daily digest as untrusted input even when it is normally produced by another local skill.
  10. Add tests covering absolute paths, nested traversal, mixed separators, Unicode separator variants, unknown categories, and symlink-based escapes.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (29)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill description omits important operational behavior, including cross-skill execution/dependencies and remote synchronization through Git. Hidden dependencies and side effects reduce transparency and can cause users to trust outputs or actions without understanding that other skills and external systems are being invoked.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill description omits important operational behavior, including cross-skill execution/dependencies and remote synchronization through Git. Hidden dependencies and side effects reduce transparency and can cause users to trust outputs or actions without understanding that other skills and external systems are being invoked.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The script deletes the original daily digest file without warning after generating derived notes and an index. This is particularly dangerous because the source document is the canonical input; if parsing omitted content or generated notes are corrupted, the original evidence is gone.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The README explicitly states that the skill will automatically collect news, organize it into Obsidian, and push it to a phone surface, but it does not disclose what data leaves the local environment, what services receive it, or whether any identifiers, reading preferences, or vault contents may be transmitted. In an automation skill that performs scheduled outbound actions, missing disclosure and consent guidance increases the risk of unintended data exposure and makes operators less able to assess privacy and network implications before enabling it.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill advertises and documents capabilities that imply shell execution, filesystem reads/writes, and network access, but it does not declare any tool scope or permissions boundaries. In an agent ecosystem, this increases the chance of over-privileged execution and makes it harder for users or the platform to review whether the skill should be allowed to modify files, access the network, or run shell commands.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The trigger phrases are broad enough to match ordinary user requests about tech news or summaries, which can cause the skill to activate unintentionally. Because this skill performs file writes, reorganization, scheduled operations, and possible push notifications, accidental invocation could lead to unexpected side effects beyond simply answering a query.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill description does not prominently warn that it writes into the user's Obsidian vault, reorganizes content, archives material, and may delete long-form originals after processing. In a personal knowledge base context, undisclosed modification and deletion behavior is especially risky because users may treat the skill as a read-only summarizer while it performs destructive content management.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script unconditionally deletes any existing note with the same generated filename and also removes duplicate-suffixed files. Because filenames are derived from truncated and sanitized titles, distinct articles can collide, causing silent overwrites and deletion of previously stored notes.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

Repository synchronization, especially push to a remote, exceeds the narrow task of organizing daily news and introduces data publication behavior. In the context of a personal knowledge vault, this broader capability can unintentionally expose unrelated or sensitive content if combined with broad staging.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

For a daily tech digest organizer, parsing markdown and writing Obsidian notes are expected. Spawning external processes for Git operations and calling another skill via 'python3' adds execution capabilities beyond straightforward content organization, and these capabilities are not justified by the manifest text itself.

Content

No source excerpt is available for this finding.

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/daily_news_organizer.py (reported line 238)May include surrounding context.

python
os.chdir(OBSIDIAN_VAULT)
        
        import subprocess
        subprocess.run(["git", "pull"], capture_output=True, timeout=30)
        subprocess.run(["git", "add", "."], capture_output=True, timeout=10)
        subprocess.run(
            ["git", "commit", "-m", f"整理每日新闻: {date_str} ({article_count}条归档)"],

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/daily_tech_digest.py (reported line 438)May include surrounding context.

python
os.chdir(OBSIDIAN_VAULT)
        
        import subprocess
        subprocess.run(["git", "pull"], capture_output=True, timeout=30)
        subprocess.run(["git", "add", "."], capture_output=True, timeout=10)
        subprocess.run(
            ["git", "commit", "-m", f"整理每日新闻: {date_str} ({article_count}条归档)"],

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/daily_news_organizer.py (reported line 239)May include surrounding context.

python
import subprocess
        subprocess.run(["git", "pull"], capture_output=True, timeout=30)
        subprocess.run(["git", "add", "."], capture_output=True, timeout=10)
        subprocess.run(
            ["git", "commit", "-m", f"整理每日新闻: {date_str} ({article_count}条归档)"],
            capture_output=True,

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/daily_tech_digest.py (reported line 439)May include surrounding context.

python
import subprocess
        subprocess.run(["git", "pull"], capture_output=True, timeout=30)
        subprocess.run(["git", "add", "."], capture_output=True, timeout=10)
        subprocess.run(
            ["git", "commit", "-m", f"整理每日新闻: {date_str} ({article_count}条归档)"],
            capture_output=True,

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/daily_news_organizer.py (reported line 240)May include surrounding context.

python
import subprocess
        subprocess.run(["git", "pull"], capture_output=True, timeout=30)
        subprocess.run(["git", "add", "."], capture_output=True, timeout=10)
        subprocess.run(
            ["git", "commit", "-m", f"整理每日新闻: {date_str} ({article_count}条归档)"],
            capture_output=True,
            timeout=10

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
72% confidence
Finding

Automatically executing git push can exfiltrate all staged repository changes to a remote without any user confirmation. In this skill's context, the repository is an Obsidian vault that may contain unrelated private notes, so combining automatic staging with push materially increases the chance of unintended data disclosure.

Content

Scanner excerpt · scripts/daily_news_organizer.py (reported line 245)May include surrounding context.

python
capture_output=True,
            timeout=10
        )
        result = subprocess.run(["git", "push"], capture_output=True, timeout=30)
        
        if result.returncode == 0:
            log("✅ Git 同步完成")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

The script launches another skill script from a hard-coded workspace path, extending trust across skill boundaries without validation. If that external script is modified, replaced, or less trusted, this organizer inherits its behavior and may leak content or perform unintended actions when passing task data to it.

Content

Scanner excerpt · scripts/daily_news_organizer.py (reported line 278)May include surrounding context.

python
# 调用 today-task 技能推送
        skills_path = "/home/sandbox/.openclaw/workspace/skills"
        push_script = f"{skills_path}/today-task/scripts/task_push.py"
        proc = subprocess.run(
            ["python3", push_script, "--data", temp_path],
            capture_output=True,
            text=True,

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script deletes the original daily digest after processing even though the described behavior is to collect and organize news, not destroy the source artifact. This is dangerous because parsing bugs, malformed input, or later review needs can make the source brief valuable, and deletion causes silent data loss.

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’s natural-language description and all user-facing outputs are hardcoded in Chinese, including the generated note title, categories, and push content. There is no indication that the user can opt into this locale or that the skill is intentionally limited to a Chinese-language or region-specific context.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script mutates sys.path and imports executable code from another skill at runtime, creating a cross-skill execution channel outside its declared role. This widens the attack surface because compromise or unexpected behavior in the referenced skill directly affects this one, undermining isolation and review assumptions.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill automatically performs git pull/add/commit/push on the entire Obsidian vault, which exceeds the stated purpose of generating a daily digest. That creates an unnecessary capability to modify and exfiltrate unrelated vault content to a configured remote, especially dangerous because the vault may contain sensitive notes beyond this skill's output.

Content

No source excerpt is available for this finding.

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/daily_tech_digest.py (reported line 440)May include surrounding context.

python
subprocess.run(["git", "pull"], capture_output=True, timeout=30)
        subprocess.run(["git", "add", "."], capture_output=True, timeout=10)
        subprocess.run(
            ["git", "commit", "-m", f"添加每日科技简报: {date_str}"],
            capture_output=True,
            timeout=10

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/daily_tech_digest.py (reported line 445)May include surrounding context.

python
capture_output=True,
            timeout=10
        )
        result = subprocess.run(["git", "push"], capture_output=True, timeout=30)
        
        if result.returncode == 0:
            log("✅ Git 同步完成")

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Launching another skill's script as a subprocess gives this digest skill indirect execution capability over external integration logic not contained in this file. In context, this is more dangerous because the launched script can perform device-side actions and the user may not realize this skill is delegating authority to another component.

Content

No source excerpt is available for this finding.

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/daily_tech_digest.py (reported line 475)May include surrounding context.

python
# 调用 today-task 技能推送
        push_script = f"{SKILLS_PATH}/today-task/scripts/task_push.py"
        proc = subprocess.run(
            ["python3", push_script, "--data", temp_path],
            capture_output=True,
            text=True,

Static analysis

No suspicious patterns detected.