Back to skill

Security audit

Weather Digest

Security checks for vulnerabilities and agentic risk

Overview

The skill is a weather report generator using NOAA/NWS data, with optional automation and sharing recipes that should be enabled only with user intent.

Install only if you are comfortable sharing configured latitude/longitude locations with NOAA/NWS. Treat Slack, heartbeat, cron, LaunchAgent, Things, and Reminders examples as optional automation: review destinations, use restricted channels, and know how to disable scheduled jobs. Avoid publishing or embedding the generated HTML until dynamic fields are HTML-escaped, and prefer a pinned dependency set for reproducible installs.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
weather_digest.py:135
Finding

Unescaped Dynamic Data in Generated HTML

Content
View full analysis
str: if not alerts: return "
No active alerts
" blocks = [] for alert in alerts: event = alert.get("event") or "Alert" severity = alert.get("severity") or "Unknown" expiry = _format_expiration(alert.get("expires")) headline = alert.get("headline") or "Details forthcoming" instructions = _trim_text(alert.get("instructions"), limit=220) expiry_html = f"
Expires {expiry}
" if expiry else "" instructions_html = ( f"
{instructions}
" if instructions else "" ) blocks.append( f"
{event}" f"{severity}
{expiry_html}" f"
{headline}
{instructions_html}
" ) return "".join(blocks) ``` ```python def build_html(reports: list[dict], theme: str = "midnight") -> str: today = dt.datetime.now().strftime("%A, %B %d %Y") cards = [] for report in reports: city_meta = "" if report.get("city") and report.get("state"): city_meta = f"
Nearest location: {report['city']}, {report['state']}
" summary_li = "".join( f"
  • {line.replace('**', '')}
  • " for line in report["summary_lines"] ) alerts_html = format_alerts_html(report["alerts"]) cards.append( f"

    {report['display_name']}

    {city_meta}" f"

    Outlook

      {summary_li}
    Remediation
    View remediation
    {escape(str(line.replace('**', '')), quote=True)}" for line in report["summary_lines"] ) ``` 3. Prefer a template engine with automatic escaping enabled instead of constructing HTML through f-strings. 4. Validate the configuration schema and enforce expected types and reasonable length limits for names and coordinates. 5. Add regression tests covering `<`, `>`, `&`, quotes, malformed tags, event-handler attributes, and script-like payloads. 6. Apply the same correction to both copies of `weather_digest.py` or remove the duplicate implementation to prevent future security fixes from diverging. ]]>

    T08 · Insecure Dependencies

    Note
    Location
    requirements.txt:1
    Finding

    Unbounded and Unhashed Third-Party Dependency

    Content
    View full analysis
    =2.31.0 ``` The setup documentation instructs users to install this dependency with: ```bash pip install -r requirements.txt ``` ### Technical Analysis The requirement specifies only a minimum version. Consequently, installation can resolve to any later release available from the configured Python package index. No lock file, exact version constraint, or package hash binds installation to the dependency version reviewed during this audit. The package name `requests` is legitimate, and the audit found no evidence of typosquatting, dependency confusion, or an unsafe package source. The concern is supply-chain reproducibility: future versions can change behavior independently of the audited Skill, and package integrity is delegated entirely to the package index and installation environment. ### Attack Path 1. A future dependency release is compromised, malicious, or otherwise introduces unsafe behavior. 2. A user follows the documented installation procedure. 3. Because the requirement permits any version at or above `2.31.0`, `pip` resolves the affected release. 4. The dependency and its transitive dependencies are installed into the Skill environment. 5. Third-party code executes when imported or used by `weather_digest.py`. This path requires a compromised or unsafe dependency release or package distribution channel; no such compromise was identified during the static audit. ### Impact Assessment A compromised dependency executes with the same privileges as the user running the Skill. It could theoretically read user-accessible files, alter generated output, intercept configured coordinates and NOAA/NWS responses, or initiate additional network connections. The current evidence s ...[truncated 301 chars]
    Remediation
    View remediation
    ``` 2. Generate a lock file that includes all transitive dependencies. 3. Record package hashes and install with integrity enforcement: ```bash python -m pip install --require-hashes -r requirements.txt ``` 4. Use an isolated virtual environment, as already recommended by the project documentation. 5. Introduce a controlled dependency-update process that includes vulnerability scanning, review, and testing before changing pinned versions. 6. Keep both dependency files synchronized or consolidate them into one authoritative requirements file. ]]>
    Vulnerability Patterns
    • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
    • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
    • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
    • Rogue AgentSelf-Modification, Session Persistence
    • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
    Findings (27)

    Undeclared Tool Scope

    Medium
    Category
    MCP Least Privilege
    Confidence
    90% confidence
    Finding

    The skill documentation instructs users to run a Python script that reads a configuration file, writes output to an arbitrary path, and makes outbound network requests, but the manifest declares no explicit tool scope such as permissions or allowed-tools. This creates an authorization gap: consumers of the skill cannot easily tell what capabilities the skill expects, and a modified or misused implementation could access files or external resources beyond the intended weather use case.

    Content

    No source excerpt is available for this finding.

    Context-Inappropriate Capability

    Medium
    Category
    Not specified by scanner
    Confidence
    86% confidence
    Finding

    The OpenClaw heartbeat instructions tell the agent to run the digest and direct-message the Markdown file to Dan when severe alerts exist. Direct personal messaging is a separate communication capability not mentioned in the manifest description, which only covers digest generation and alert monitoring.

    Content

    No source excerpt is available for this finding.

    Context-Inappropriate Capability

    Medium
    Category
    Not specified by scanner
    Confidence
    89% confidence
    Finding

    The manifest describes generating a daily weather digest from NOAA/NWS data with customizable locations and alert monitoring. This section instructs posting digest content to Slack via an arbitrary webhook URL, which expands the skill into outbound messaging/integration behavior not stated in the manifest.

    Content

    No source excerpt is available for this finding.

    Missing User Warnings

    Medium
    Category
    Not specified by scanner
    Confidence
    95% confidence
    Finding

    The webhook instructions send digest content to Slack without any warning about external disclosure, retention, or workspace visibility. Even if weather data is usually low sensitivity, digests may include user-specific locations, operational context, or alerting patterns that should not be transmitted off-system without informed consent.

    Content

    No source excerpt is available for this finding.

    External Transmission

    Medium
    Category
    Data Exfiltration
    Confidence
    95% confidence
    Finding

    This command performs external transmission of generated digest content via a Slack webhook. External posting is not inherently malicious, but it creates a real data egress path that could leak user locations, severe-alert monitoring targets, or other operational details if misconfigured or enabled without clear notice.

    Content

    Scanner excerpt · weather digest/automation.md (reported line 38)May include surrounding context.

    1. Extend the wrapper to post summaries via webhook:
      bash
      SUMMARY=$(head -n 30 outputs/digest-$(date +%Y%m%d).md)
      curl -X POST -H 'Content-type: application/json' \
        --data '{"text":"Weather Digest\n```'     --data '{"text":"Weather Digest\n```\n'$(tail -n +2 outputs/digest-$(date +%Y%m%d).md | head -n 20)\n```"}' $SLACK_WEBHOOK_URL
      
      Adjust tail/head lines to control preview length. Use Block Kit if you prefer cards.

    Session Persistence

    Medium
    Category
    Rogue Agent
    Confidence
    75% 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 · weather digest/automation.md (reported line 44)May include surrounding context.

    Adjust tail/head lines to control preview length. Use Block Kit if you prefer cards.

    LaunchAgent (macOS GUI)

    • Save this plist at ~/Library/LaunchAgents/com.dan.weatherdigest.plist:
      xml
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
      

    Session Persistence

    Medium
    Category
    Rogue Agent
    Confidence
    75% 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 · weather digest/automation.md (reported line 47)May include surrounding context.

    Adjust tail/head lines to control preview length. Use Block Kit if you prefer cards.

    LaunchAgent (macOS GUI)

    • Save this plist at ~/Library/LaunchAgents/com.dan.weatherdigest.plist:
      xml
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
      

    Session Persistence

    Medium
    Category
    Rogue Agent
    Confidence
    75% 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 · weather digest/automation.md (reported line 48)May include surrounding context.

    Adjust tail/head lines to control preview length. Use Block Kit if you prefer cards.

    LaunchAgent (macOS GUI)

    • Save this plist at ~/Library/LaunchAgents/com.dan.weatherdigest.plist:
      xml
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
      

    Session Persistence

    Medium
    Category
    Rogue Agent
    Confidence
    75% 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 · weather digest/automation.md (reported line 65)May include surrounding context.

    Adjust tail/head lines to control preview length. Use Block Kit if you prefer cards.

    LaunchAgent (macOS GUI)

    • Save this plist at ~/Library/LaunchAgents/com.dan.weatherdigest.plist:
      xml
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
      

    Session Persistence

    Medium
    Category
    Rogue Agent
    Confidence
    75% 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 · weather digest/automation.md (reported line 67)May include surrounding context.

    Adjust tail/head lines to control preview length. Use Block Kit if you prefer cards.

    LaunchAgent (macOS GUI)

    • Save this plist at ~/Library/LaunchAgents/com.dan.weatherdigest.plist:
      xml
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
      

    Session Persistence

    Medium
    Category
    Rogue Agent
    Confidence
    75% 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 · weather digest/automation.md (reported line 47)May include surrounding context.

    • Save this plist at ~/Library/LaunchAgents/com.dan.weatherdigest.plist:
      xml
      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
      <plist version="1.0">
      <dict>
        <key>Label</key><string>com.dan.weatherdigest</string>
      

    Session Persistence

    Medium
    Category
    Rogue Agent
    Confidence
    75% 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 · weather digest/automation.md (reported line 67)May include surrounding context.

    ``` Load with `launchctl load ~/Library/LaunchAgents/com.dan.weatherdigest.plist`.

    Delivering to Things 3 / Apple Reminders

    • Use things CLI to drop the Markdown into Today list for manual review:

    Tainted flow: 'html' from pathlib.Path.read_text (line 306, file read) → pathlib.Path.write_text (file write)

    Medium
    Category
    Data Flow
    Confidence
    88% confidence
    Finding

    The generated HTML embeds unescaped data from external NOAA/NWS responses and user-controlled location names directly into HTML elements. If the resulting file is opened in a browser, malicious HTML or script content in alert headlines, instructions, city names, or display names could execute as stored XSS in the local report.

    Content

    Scanner excerpt · weather digest/weather_digest.py (reported line 308)May include surrounding context.

    python
    if args.html_path:
            html = build_html(reports, theme=args.theme)
            html_path = Path(args.html_path)
            html_path.write_text(html)
            print(f"HTML digest written to {html_path} (theme: {args.theme})")
        if args.json_path:
            json_payload = build_json_document(reports)
    

    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 · weather digest/weather_digest.py (reported line 54)May include surrounding context.

    python
    def get_point_metadata(lat: float, lon: float) -> dict:
        url = f"https://api.weather.gov/points/{lat},{lon}"
        data = fetch_json(url)
        return data["properties"]
    

    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 · weather digest/weather_digest.py (reported line 85)May include surrounding context.

    python
    def get_point_metadata(lat: float, lon: float) -> dict:
        url = f"https://api.weather.gov/points/{lat},{lon}"
        data = fetch_json(url)
        return data["properties"]
    

    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 · weather_digest.py (reported line 54)May include surrounding context.

    python
    def get_point_metadata(lat: float, lon: float) -> dict:
        url = f"https://api.weather.gov/points/{lat},{lon}"
        data = fetch_json(url)
        return data["properties"]
    

    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 · weather_digest.py (reported line 85)May include surrounding context.

    python
    def get_point_metadata(lat: float, lon: float) -> dict:
        url = f"https://api.weather.gov/points/{lat},{lon}"
        data = fetch_json(url)
        return data["properties"]
    

    Tainted flow: 'markdown' from pathlib.Path.read_text (line 301, file read) → pathlib.Path.write_text (file write)

    Medium
    Category
    Data Flow
    Confidence
    65% confidence
    Finding

    Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

    Content

    Scanner excerpt · weather digest/weather_digest.py (reported line 303)May include surrounding context.

    python
    reports = gather_reports(locations)
        markdown = build_markdown(reports)
        output_path = Path(args.output)
        output_path.write_text(markdown)
        print(f"Markdown digest written to {output_path}")
        if args.html_path:
            html = build_html(reports, theme=args.theme)
    

    Tainted flow: 'markdown' from pathlib.Path.read_text (line 301, file read) → pathlib.Path.write_text (file write)

    Medium
    Category
    Data Flow
    Confidence
    65% confidence
    Finding

    Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

    Content

    Scanner excerpt · weather_digest.py (reported line 303)May include surrounding context.

    python
    reports = gather_reports(locations)
        markdown = build_markdown(reports)
        output_path = Path(args.output)
        output_path.write_text(markdown)
        print(f"Markdown digest written to {output_path}")
        if args.html_path:
            html = build_html(reports, theme=args.theme)
    

    Tainted flow: 'html' from pathlib.Path.read_text (line 306, file read) → pathlib.Path.write_text (file write)

    Medium
    Category
    Data Flow
    Confidence
    87% confidence
    Finding

    The HTML output embeds untrusted external data from the NOAA/NWS API directly into HTML without escaping fields such as display_name, city/state, headline, event, and instructions. If that upstream data or the config content contains HTML/script payloads, opening the generated report in a browser could trigger stored XSS in the local report.

    Content

    Scanner excerpt · weather_digest.py (reported line 308)May include surrounding context.

    python
    if args.html_path:
            html = build_html(reports, theme=args.theme)
            html_path = Path(args.html_path)
            html_path.write_text(html)
            print(f"HTML digest written to {html_path} (theme: {args.theme})")
        if args.json_path:
            json_payload = build_json_document(reports)
    

    Tainted flow: 'json_payload' from pathlib.Path.read_text (line 311, file read) → pathlib.Path.write_text (file write)

    Medium
    Category
    Data Flow
    Confidence
    65% confidence
    Finding

    Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

    Content

    Scanner excerpt · weather digest/weather_digest.py (reported line 313)May include surrounding context.

    python
    if args.json_path:
            json_payload = build_json_document(reports)
            json_path = Path(args.json_path)
            json_path.write_text(json.dumps(json_payload, indent=2))
            print(f"JSON digest written to {json_path}")
    

    Tainted flow: 'json_payload' from pathlib.Path.read_text (line 311, file read) → pathlib.Path.write_text (file write)

    Medium
    Category
    Data Flow
    Confidence
    65% confidence
    Finding

    Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

    Content

    Scanner excerpt · weather_digest.py (reported line 313)May include surrounding context.

    python
    if args.json_path:
            json_payload = build_json_document(reports)
            json_path = Path(args.json_path)
            json_path.write_text(json.dumps(json_payload, indent=2))
            print(f"JSON digest written to {json_path}")
    

    Unpinned Dependencies

    Low
    Category
    Supply Chain
    Confidence
    96% confidence
    Finding

    The dependency is specified as requests>=2.31.0, which allows future releases to be installed without review and prevents reproducible builds. This can introduce vulnerable or breaking versions over time, especially for a network-facing skill that likely fetches external NOAA/NWS data.

    Content

    Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

    text
    requests>=2.31.0
    

    Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

    Low
    Category
    Supply Chain
    Confidence
    91% confidence
    Finding

    The manifest references requests without pinning an exact version, so there is no way to verify whether the installed package includes fixes for known advisories affecting some releases. Because this skill likely performs outbound HTTP requests, using an unverified version of requests increases the risk of exposure to request-handling or credential-leak issues if a vulnerable release is resolved at install time.

    Content

    No source excerpt is available for this finding.

    Context-Inappropriate Capability

    Low
    Category
    Not specified by scanner
    Confidence
    84% confidence
    Finding

    The manifest describes a weather digest generator with alert monitoring, but this section extends the skill into task/reminder creation in external productivity tools. That capability is not an obvious requirement for producing weather reports and represents an unjustified expansion of scope.

    Content

    No source excerpt is available for this finding.

    Static analysis

    No suspicious patterns detected.