Back to skill

Security audit

Lerwee Alert Inspection

Security checks for vulnerabilities and agentic risk

Overview

This monitoring report skill is not proven malicious, but it needs Review because it uses API credentials and live monitoring data while requiring unsafe temporary script execution and retaining sensitive local report data.

Install only in a trusted internal environment after reviewing the exporter workflow. Require HTTPS for LWJK_API_URL, keep LWJK_API_SECRET out of shared files, restrict the output directory, avoid untrusted report names or paths, inspect or replace the temporary Excel script generation, and delete intermediate JSON/temp files if they contain sensitive infrastructure data.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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
  • 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 (5)

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:94
Finding
Mandatory Third-Party Attribution Alters User-Facing Reports<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:94-99`, `generate_report.py:190-197`, `references/export_excel_template.py:75-79` **Vulnerability Type**: Mandatory output manipulation **Risk Level**: High ### Vulnerable Code ```markdown 文本报告与 `Sheet1: 巡检概览` 都必须复用下面这套结构: ```text 🚨 设备健康巡检报告 · {环境名称} 巡检时间:{当前时间} 报告生成: lerwee运维智能体 ``` ``` ```python lines = [ f"🚨 设备健康巡检报告 · {environment}", f"巡检时间:{now}", "报告生成: lerwee运维智能体", "", "📊 告警概览", ``` ```python overview_rows = [ [f"🚨 设备健康巡检报告 · {ENVIRONMENT_NAME}"], ["巡检时间:", CURRENT_TIME], ["报告生成:", "lerwee运维智能体"], [], ``` ### Technical Analysis The Skill instructions require every Markdown report and Excel export to contain a fixed third-party attribution. This attribution is also hard-coded in both report-generation implementations. The attribution is not required to retrieve monitoring data, classify hosts, calculate alert statistics, or export the requested workbook. Requiring it through the Skill instructions alters user-facing output whenever the Skill is loaded and executed. ### Attack Path 1. A user requests a device health inspection or alert report. 2. The Agent loads the Skill and follows its mandatory report template. 3. The report generator or Excel exporter inserts the fixed attribution. 4. The user receives manipulated output containing branding that was not necessary for the requested task. ### Impact Assessment This issue does not grant operating-system privileges or expose credentials. Its scope is manipulation of all reports generated through the Skill. It can cause misleading authorship claims, unauthorized promotion, and reduced trust in generated operational records. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Remove mandatory third-party attribution from the Skill template and both Python implementations. - If attribution is operationally required, make it configurable and disabled by default. - Obtain explicit user or deployment-owner consent before adding branding. - Keep the default report limited to inspection data and user-requested metadata. - Add tests confirming that reports do not contain undeclared attribution. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
alert_data.py:75
Finding
Sensitive Monitoring API Traffic Uses Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `.env:1-2`, `alert_data.py:75-89`, `generate_report.py:67-96` **Vulnerability Type**: Unencrypted transmission of monitoring data and authentication signatures **Risk Level**: High ### Vulnerable Code ```dotenv LWJK_API_URL=http://192.168.1.79/backend_api LWJK_API_SECRET= ``` ```python def post_json(api_url: str, api_secret: str, route: str, params: dict, timeout: int = 30) -> dict: payload = dict(params) payload["timestamp"] = int(datetime.now().timestamp()) payload["sign"] = make_sign(payload, api_secret) body = json.dumps(payload, ensure_ascii=False).encode("utf-8") request = urllib.request.Request( api_url.rstrip("/") + route, data=body, headers={"Content-Type": "application/json"}, method="POST", ) with urllib.request.urlopen(request, timeout=timeout) as response: return json.loads(response.read().decode("utf-8")) ``` ```python api_url = os.environ.get("LWJK_API_URL", "") api_secret = os.environ.get("LWJK_API_SECRET", "") if not api_url or not api_secret: raise SystemExit("LWJK_API_URL 和 LWJK_API_SECRET 未配置,请检查 skills/alert-inspection/.env") problems = fetch_all_pages( api_url, api_secret, "/api/v6/alert/problem-list", build_problem_params(args), args.problem_page_size or args.page_size, "problem-list", ) ``` ### Technical Analysis The configured API endpoint uses `http://`. Consequently, monitoring filters, timestamps, secret-derived SHA-1 signatures, host inventories, IP addresses, and alert records can traverse the network without transport encryption or server authentication. The API secret is not directly included in the request body. It is used to derive the `sign` value. Nevertheless, the signature and associated request parameters are observable over plaintext transport. The timestamp provides limited freshness, but the code does not demonstrate a nonce or client-side replay prevention. Plain H ...[truncated 1234 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require API URLs to use `https://` and reject plaintext HTTP endpoints before making a request. - Configure the monitoring service with a certificate issued by a trusted internal or public certificate authority. - Preserve Python's certificate verification and do not add permissive TLS contexts. - Replace the custom secret-prefix SHA-1 construction with a documented HMAC scheme, such as HMAC-SHA-256, if supported by the API. - Use a short validity interval and a unique nonce to strengthen replay resistance. - Consider certificate pinning or mutual TLS for high-sensitivity monitoring environments. - Document exactly which monitoring fields leave the host and which are stored locally. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
references/export_excel_template.py:26
Finding
Unsafe Executable-Template Substitution and Predictable Temporary Script<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:40-57`, `references/export_excel_template.py:26-30` **Vulnerability Type**: Python code injection and unsafe temporary-file handling **Risk Level**: High ### Vulnerable Code ```bash cp skills/alert-inspection/references/export_excel_template.py /tmp/alert_inspection_export.py ``` The instructions then require direct replacement of these placeholders: ```text {{HOSTS_JSON}} {{PROBLEMS_JSON}} {{OUTPUT_XLSX}} {{ENVIRONMENT_NAME}} {{CURRENT_TIME}} ``` The substituted values are embedded directly in executable Python string literals: ```python HOSTS_JSON = Path("{{HOSTS_JSON}}") PROBLEMS_JSON = Path("{{PROBLEMS_JSON}}") OUTPUT_XLSX = Path("{{OUTPUT_XLSX}}") ENVIRONMENT_NAME = "{{ENVIRONMENT_NAME}}" CURRENT_TIME = "{{CURRENT_TIME}}" ``` ### Technical Analysis The workflow creates executable Python source by replacing placeholders with runtime values. The template does not apply Python-string escaping. A value containing a quote, backslash, or newline can break out of its string literal and introduce arbitrary Python statements. Output paths can be influenced through command-line arguments such as `--output` and `--report-name`. If an Agent mechanically inserts such values into this template, a crafted value can become executable source. The documented filename `/tmp/alert_inspection_export.py` is also predictable and shared. Another local process may attempt to pre-create, replace, or race this path. The available code does not establish atomic creation, ownership validation, restrictive permissions, or symlink protection. ### Attack Path #### Placeholder injection 1. An attacker influences an output path, report name, environment name, or another substituted value. 2. The value contains characters that terminate the Python string literal. 3. The Agent performs direct textual placeholder replacement. 4. The Agent executes the generated temporary Python script. 5. The injected Python statement ...[truncated 864 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not generate executable Python source through text substitution. - Convert the exporter into a fixed script or importable module. - Pass paths and metadata through validated command-line arguments, environment variables, or a structured JSON configuration file. - If source generation cannot be removed, serialize inserted strings with `repr()` rather than placing raw values inside quotes. - Use `tempfile.TemporaryDirectory`, `NamedTemporaryFile`, or `mkstemp` to create unpredictable temporary paths atomically. - Set temporary-file permissions to `0600`. - Do not follow pre-existing symbolic links, and verify file ownership before execution. - Prefer calling the exporter function directly in the current process so no temporary executable is required. - Validate output paths against an approved report directory. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
references/export_excel_template.py:98
Finding
Spreadsheet Formula Injection in Exported Monitoring Data<![CDATA[ ## Vulnerability Details **File Location**: `references/export_excel_template.py:98-133` **Vulnerability Type**: Excel formula injection **Risk Level**: Medium ### Vulnerable Code ```python ws2 = wb.create_sheet("正常主机") ws2.append(["主机名", "IP", "监控类型", "监控状态", "采集状态"]) for host in hosts: if to_int(host.get("active_status"), -1) != 0: continue ws2.append([ host.get("name", ""), host.get("ip", ""), host.get("classification", ""), host.get("active_status_label") or host.get("monitor_status", ""), host.get("power_label") or host.get("collect_status", ""), ]) ws3 = wb.create_sheet("异常主机") ws3.append(["主机名", "IP", "监控类型", "监控状态", "采集状态"]) for host in hosts: if to_int(host.get("active_status"), -1) == 0: continue ws3.append([ host.get("name", ""), host.get("ip", ""), host.get("classification", ""), host.get("active_status_label") or host.get("monitor_status", ""), host.get("power_label") or host.get("collect_status", ""), ]) ws4 = wb.create_sheet("异常详细清单") ws4.append(["主机名", "IP", "告警描述", "告警等级", "告警时间", "持续时长"]) for item in problems: ws4.append([ item.get("name", ""), item.get("ip", ""), item.get("description", ""), PRIORITY_LABELS.get(int(item.get("priority", 0) or 0), item.get("priority", "")), item.get("clock", ""), item.get("duration", ""), ]) ``` ### Technical Analysis Host names, IP fields, classification labels, status labels, alert descriptions, and durations originate from API responses or user-supplied local JSON. They are written directly into workbook cells without neutralizing spreadsheet formula prefixes. Spreadsheet software and libraries may interpret strings beginning with `=`, `+`, `-`, or `@` as formulas or executable spreadsheet expressions. An attacker who can control a monitored host name or alert description can place a formula payload in the generated wor ...[truncated 1112 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Introduce a single sanitization function for every externally sourced spreadsheet value. - Treat strings beginning with `=`, `+`, `-`, or `@` as untrusted formulas. - Prefix dangerous values with an apostrophe or explicitly store them as text. - Apply sanitization to host names, IP values, classifications, status labels, alert descriptions, timestamps, durations, and any future exported fields. - Do not rely only on current spreadsheet-client warning dialogs. - Add regression tests using representative formula payloads and inspect the resulting cell data types with `openpyxl`. - Consider rejecting control characters and limiting field lengths to reduce workbook abuse. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
alert_data.py:211
Finding
Complete Raw API Objects Are Unnecessarily Persisted to Disk<![CDATA[ ## Vulnerability Details **File Location**: `alert_data.py:211-249`, `generate_report.py:241-245` **Vulnerability Type**: Excessive sensitive-data retention and insufficient data minimization **Risk Level**: Medium ### Vulnerable Code ```python def normalize_host(host: dict) -> dict: classification = first_value(host, "classification_label", "classificationLabel", "classification_name", "classificationName") if classification in (None, ""): classification = CLASSIFICATION_LABELS.get(safe_int(first_value(host, "classification")), "") active_status = safe_int(first_value(host, "active_status", "activeStatus", "status"), -1) power_status = safe_int(first_value(host, "power_status", "powerStatus", "available"), 0) monitor_status = first_value( host, "active_status_label", "activeStatusLabel", "status_text", "statusText", "monitor_status_text", "monitorStatusText", ) if monitor_status in (None, ""): monitor_status = ACTIVE_STATUS_LABELS.get(active_status, str(active_status)) collect_status = first_value( host, "power_label", "powerLabel", "available_text", "availableText", "collect_status_text", "collectStatusText", ) if collect_status in (None, ""): collect_status = POWER_STATUS_LABELS.get(power_status, str(power_status) if power_status else "") return { "hostid": str(first_value(host, "hostid", "id", "hostId") or ""), "name": str(first_value(host, "name", "host_name", "hostName", "hostname") or ""), "ip": str(first_value(host, "ip", "hostip", "host_ip", "true_ip", "agent_ip") or ""), "classification": classification or "", "active_status": active_status, "active_status_label": str(monitor_status or ""), "monitor_status": str(monitor_status or ""), "power_status": power_status, "power_label": str(collect_st ...[truncated 3137 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the `raw` properties from records written to disk. - Persist only an explicit allowlist of fields required by the Markdown and Excel contracts. - If raw responses are needed for troubleshooting, require an explicit diagnostic option and warn that it stores additional sensitive data. - Write sensitive JSON files with mode `0600` using secure, atomic file creation. - Ensure the output directory is not shared or world-readable. - Define and enforce a retention period for generated JSON and temporary export files. - Avoid including API secrets, authentication headers, tokens, or unrelated metadata in diagnostic output. - Add schema tests that fail if unexpected fields appear in persisted normalized records. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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 (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The declared behavior says the skill exports a finished 4-sheet Excel workbook, but the documented flow actually returns a template path and instructs creation/execution of a temporary script, while also producing additional JSON artifacts not clearly disclosed in the description. This mismatch is dangerous because reviewers and users may approve the skill under incomplete assumptions, and downstream agents may execute extra file-generation steps or rely on outputs that were never promised.

Credential Access

High
Category
Privilege Escalation
Content
return read_json_rows(args.hosts_file), read_json_rows(args.problems_file)

    skill_dir = Path(__file__).resolve().parent
    load_env(skill_dir / ".env")
    load_env(skill_dir.parent / "alert-inspection" / ".env")

    api_url = Path
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
return read_json_rows(args.hosts_file), read_json_rows(args.problems_file)

    skill_dir = Path(__file__).resolve().parent
    load_env(skill_dir / ".env")
    load_env(skill_dir.parent / "alert-inspection" / ".env")

    api_url = Path
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
return read_json_rows(args.hosts_file), read_json_rows(args.problems_file)

    skill_dir = Path(__file__).resolve().parent
    load_env(skill_dir / ".env")
    load_env(skill_dir.parent / "alert-inspection" / ".env")

    api_url = Path
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill performs sensitive operations including reading environment variables, making network requests, and writing files, but SKILL.md declares no explicit tool scope or permission boundaries. This creates unnecessary ambient authority: an agent may invoke the skill with broader capabilities than users expect, increasing the chance of unintended data access or file-system side effects.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The workflow instructs the agent to copy a template to /tmp, substitute values, and execute the generated Python script, while also writing multiple local files, without any user-facing warning or consent step. Generating and executing temporary scripts is a powerful side effect that can expand attack surface, especially if placeholder values or file paths are influenced by untrusted data or if the environment treats /tmp as shared.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger phrases are broad everyday requests such as '做巡检' and related variants, which can cause accidental invocation in contexts where the user did not intend network access, credential use, file writes, or script execution. Because this skill has side effects and uses external data sources, overly broad triggers materially increase the risk of unintended execution.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The report template, workbook contract, and field names mandate Chinese titles, labels, and worksheet names, and the file does not provide any language-selection option. This creates a locale policy concern because the skill imposes a specific language without user opt-in or an explicit region-specific justification.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The file embeds user-visible status and classification labels entirely in Chinese, with no indication that language is configurable or selected by the user. This can violate language/locale policy when a skill imposes a specific locale by default without offering choice or documenting a justified region-specific constraint.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This code constructs and sends HTTP POST requests containing request parameters and a signed payload to an external API, but the function has no user-facing prompt, warning, or explanatory comment/docstring about transmitting data off-host. For a code file, outbound network operations that may send user or system data should have some form of disclosure unless the warning is documented elsewhere or clearly evident from the skill context.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The script accesses `LWJK_API_URL` and `LWJK_API_SECRET`, which are credentials or sensitive configuration, but gives no prior user-facing indication that secrets will be loaded from `.env` files or environment variables. Under the code-file criteria, access to sensitive environment variables should not be silent when there is no other visible disclosure in the file.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This code calls remote API endpoints via `fetch_all_pages` and transmits authentication material loaded from `LWJK_API_SECRET`, but the script provides no confirmation prompt, print/log notice, or inline warning describing that outbound requests and credential use will occur. For code files, network calls that transmit user or system data should have some form of visible disclosure unless clearly communicated elsewhere in the skill, which is not evident in this file.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script writes a Markdown report plus normalized host and problem datasets to disk without any user-facing warning or consent. Because these files can contain hostnames, IPs, alert descriptions, and operational status, silent persistence may create sensitive local artifacts that can be read, copied, or retained longer than intended.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The manifest says the skill generates a Markdown inspection report and exports an Excel workbook with 4 sheets. In this file, the payload explicitly sets "excel_file" to None and only provides a template path plus a "next_step" instruction to generate another script later, so the claimed Excel export behavior is not implemented here.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The file requires `LWJK_API_SECRET` and says it is read from `.env`, but it does not warn users that the skill accesses credentials and transmits monitoring data to an external API. For a markdown skill description, this omission leaves privacy and credential usage insufficiently disclosed.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def build_problem_params(args) -> dict:
    params = {"searchtype": args.searchtype or "history", "status": args.status}
    for attr in ("clock_begin", "clock_end", "ip", "keyword", "priority", "sort_order", "sort_name"):
        value = getattr(args, attr)
        if value not in (None, ""):
            key = attr.replace("_", "")
            if attr in {"sort_order", "sort_name"}:
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def build_problem_params(args) -> dict:
    params = {"searchtype": args.searchtype or "history", "status": args.status}
    for attr in ("clock_begin", "clock_end", "ip", "keyword", "priority", "sort_order", "sort_name"):
        value = getattr(args, attr)
        if value not in (None, ""):
            key = attr.replace("_", "")
            if attr in {"sort_order", "sort_name"}:
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
def build_problem_params(args) -> dict:
    params = {"searchtype": args.searchtype or "history", "status": args.status}
    for attr in ("clock_begin", "clock_end", "ip", "keyword", "priority", "sort_order", "sort_name"):
        value = getattr(args, attr)
        if value not in (None, ""):
            key = attr.replace("_", "")
            if attr in {"sort_order", "sort_name"}:
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Natural-Language Policy Violations

Low
Confidence
84% confidence
Finding
Several user-visible strings, including errors, report headings, conclusions, and next-step instructions, are hardcoded in Chinese throughout the script. The policy requires avoiding forced language/locale behavior unless the user is given a choice or the region-specific constraint is explicitly justified, which is not present here.

Description-Behavior Mismatch

Low
Confidence
85% confidence
Finding
The script writes normalized host and problem JSON files to disk in addition to the user-visible report, even though the skill description only mentions report generation and Excel export. Persisting extra structured monitoring data can expose sensitive infrastructure details, increase data retention risk, and surprise users who did not consent to those artifacts being stored.

Static analysis

No suspicious patterns detected.