Back to skill

Security audit

Self Skill Evolver

Security checks for vulnerabilities and agentic risk

Overview

This looks like a legitimate local skill-analysis tool, but it needs review because it can inspect broader local paths and stores/deletes telemetry with weak safeguards.

Install only if you are comfortable with a local tool that scans skill source files and keeps a SQLite telemetry database in your home directory. Use it on trusted skill names and paths, avoid passing sensitive context or raw errors into monitoring, and treat HTML report output as unsafe if skill names or recommendations may contain untrusted text.

Vulnerability Patterns
  • 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
  • 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
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
skill_evolver/analyzer.py:58
Finding
Unrestricted Skill Path Allows Analysis Outside the Configured Skill Directory<![CDATA[ ## Vulnerability Details **File Location**: `skill_evolver/analyzer.py:58-63, 70-74, 267` **Vulnerability Type**: Path traversal and unauthorized local file inspection **Risk Level**: Medium ### Vulnerable Code ```python def __init__(self, skills_dir: Optional[str] = None): if skills_dir is None: # 默认使用 ~/.openclaw/skills self.skills_dir = Path.home() / ".openclaw" / "skills" else: self.skills_dir = Path(skills_dir) def analyze_skill(self, skill_name: str) -> SkillAnalysisResult: """ 分析单个技能 """ skill_path = self.skills_dir / skill_name result = SkillAnalysisResult( skill_name=skill_name, skill_path=str(skill_path) ) ``` The resulting path is subsequently used for recursive file discovery: ```python def _analyze_python_code(self, result: SkillAnalysisResult, skill_path: Path): """分析 Python 代码文件""" python_files = list(skill_path.rglob("*.py")) ``` ### Technical Analysis The analyzer constructs the target path by directly joining the configured Skill directory with the caller-controlled `skill_name`. It does not reject: - Parent-directory components such as `..` - Absolute paths - Symbolic links that resolve outside the Skill root - Alternate paths that normalize outside the intended directory With `pathlib`, joining a base path with an absolute path discards the base path. Relative traversal components can similarly escape the configured directory after filesystem resolution. The escaped path is used to read `SKILL.md`, parse `package.json`, and recursively discover and parse every Python file beneath the target. Although the implementation does not print complete source contents, it can disclose directory existence, resolved paths, filenames, syntax errors, line numbers, and code-quality information about files outside the authorized Skill repository. Recursive traversal can also impose substantial CPU and memory costs when pointed at a large directory. ### Atta ...[truncated 1324 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Resolve and normalize the configured Skill root during initialization: ```python self.skills_dir = Path(skills_dir).expanduser().resolve() ``` 2. Treat Skill names as identifiers rather than paths. Reject absolute paths, empty names, `.` and `..` components, and path separators where they are not required. 3. Resolve the candidate path and verify that it remains under the authorized root: ```python root = self.skills_dir.resolve() candidate = (root / skill_name).resolve(strict=False) try: candidate.relative_to(root) except ValueError: raise ValueError("Skill path must remain inside the configured Skill directory") ``` 4. Define and enforce a symbolic-link policy. If Skills must not escape through links, inspect each path component or reject linked Skill roots before recursive traversal. 5. Add maximum file-count, file-size, recursion-depth, and total-byte limits to prevent resource exhaustion. 6. Add tests covering parent traversal, absolute paths, nested traversal, symbolic links, and valid Skill identifiers. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
database/models.py:143
Finding
SQL Predicate Injection Through Unvalidated Day Values<![CDATA[ ## Vulnerability Details **File Location**: `database/models.py:143-158, 347-360` **Vulnerability Type**: SQL injection through string-formatted datetime modifiers **Risk Level**: High ### Vulnerable Code ```python def get_usage_stats(self, skill_name: str, days: int = 30) -> Dict[str, Any]: """获取技能使用统计""" with self._get_connection() as conn: cursor = conn.execute(""" SELECT COUNT(*) as total, SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success, SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed, AVG(duration_ms) as avg_duration, MAX(duration_ms) as max_duration, MIN(duration_ms) as min_duration FROM skill_usage WHERE skill_name = ? AND timestamp >= datetime('now', '-{} days') """.format(days), (skill_name,)) ``` The same pattern is used in a destructive operation: ```python def clear_old_data(self, days: int = 90): """清理旧数据""" with self._get_connection() as conn: conn.execute(""" DELETE FROM skill_usage WHERE timestamp < datetime('now', '-{} days') """.format(days)) conn.execute(""" DELETE FROM feedback WHERE timestamp < datetime('now', '-{} days') """.format(days)) conn.commit() ``` ### Technical Analysis The `days` value is interpolated directly into SQL before execution. Type annotations do not enforce runtime types in Python, so direct callers of these public methods can pass strings or objects whose string representation contains SQL syntax. The CLI reduces exposure because `argparse` parses `--days` as an integer. However, the database class is also a public Python API, and the methods do not independently enforce that `days` is an integer within a safe range. An attacker can close the `datetime()` expression and append a predicate such as `OR 1=1`, comm ...[truncated 1642 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce the runtime type and a reasonable range at every public method boundary: ```python if isinstance(days, bool) or not isinstance(days, int): raise TypeError("days must be an integer") if not 0 <= days <= 3650: raise ValueError("days is outside the permitted range") ``` 2. Bind the complete SQLite datetime modifier as a parameter: ```python modifier = f"-{days} days" cursor = conn.execute( """ SELECT ... FROM skill_usage WHERE skill_name = ? AND timestamp >= datetime('now', ?) """, (skill_name, modifier), ) ``` 3. Apply the same parameterization to both deletion queries: ```python conn.execute( """ DELETE FROM skill_usage WHERE timestamp < datetime('now', ?) """, (modifier,), ) ``` 4. Consider requiring explicit confirmation for unusually broad retention deletion. 5. Return and display affected-row counts so unexpected deletion scope is visible. 6. Add regression tests using strings, booleans, negative values, extreme integers, quote characters, comment markers, and predicate-injection payloads. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
skill_evolver/reporter.py:296
Finding
Unescaped Report Fields Permit HTML and Script Injection<![CDATA[ ## Vulnerability Details **File Location**: `skill_evolver/reporter.py:296-303, 321-324, 376-378` **Vulnerability Type**: HTML injection in generated reports **Risk Level**: Medium ### Vulnerable Code ```python def _report_to_html(self, report: Dict[str, Any]) -> str: """将报告转换为 HTML 格式""" if "error" in report: return f"<h1>错误</h1><p>分析 {report['skill_name']} 时出错: {report['error']}</p>" status_color = { "healthy": "#28a745", "warning": "#ffc107", "critical": "#dc3545" } html = f"""<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{report['skill_name']} 健康度报告</title> ``` The Skill name is inserted into the document body without escaping: ```python <body> <h1>{report['skill_name']} 健康度报告</h1> <p>生成时间: {report['generated_at']}</p> ``` Recommendation values are also appended as raw HTML: ```python for rec in report['recommendations']: html += f" <li>{rec}</li>\n" ``` ### Technical Analysis The HTML renderer builds a document through direct string interpolation. It does not perform context-sensitive HTML escaping on dynamic fields such as: - `skill_name` - `error` - `generated_at` - `status` - Recommendation entries - Other report values if a caller supplies a custom report dictionary A Skill name is accepted from CLI/API input and persisted in several database workflows. A malicious name containing closing tags and script or event-handler markup can therefore break out of the intended text context. When a generated report is opened in a browser, injected markup is interpreted as active HTML rather than displayed as text. Browser restrictions may limit access to unrelated local files when the report is opened using a `file:` URL, but script execution, report spoofing, navigation, and access to data available within the report's browser context remain possible. ### Attack Path 1. The attacker supplies a malicious Skill name through the CLI, API, or a ...[truncated 1264 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use an HTML template engine with automatic escaping enabled, such as Jinja2 with autoescape configured for HTML templates. 2. If string-based rendering is retained, escape every dynamic text value: ```python from html import escape safe_skill_name = escape(str(report["skill_name"]), quote=True) safe_error = escape(str(report.get("error", "")), quote=True) safe_recommendations = [ escape(str(item), quote=True) for item in report.get("recommendations", []) ] ``` 3. Validate structured fields such as `status` against a strict allowlist before using them in generated output. 4. Keep data values in text-node contexts. Do not insert untrusted values into style, script, URL, or raw-markup contexts. 5. Add a restrictive Content Security Policy to generated reports, for example one that denies scripts and external connections where compatible with report requirements. 6. Add regression tests containing closing tags, event handlers, quotes, ampersands, and script-like content in every dynamic report field. ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Unpinned and Unhashed Dependencies Create a Non-Reproducible Supply Chain<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:1-13` **Vulnerability Type**: Unbounded third-party dependency resolution **Risk Level**: Low ### Vulnerable Code ```text # Skill Evolver Dependencies # YAML parsing PyYAML>=6.0 # Git operations (optional) GitPython>=3.1.0 # Testing pytest>=7.0.0 # Code linting flake8>=6.0.0 ``` The documented installation process executes this dependency set: ```bash pip install -r requirements.txt ``` ### Technical Analysis All dependencies use lower-bound constraints without upper bounds, exact versions, or package hashes. Consequently, two installations performed at different times can resolve to different package versions. The same file also includes testing and linting packages that are not required for normal runtime behavior. This unnecessarily increases the number of packages and transitive dependencies installed in production environments. No malicious dependency or currently exploitable dependency version was identified from the reviewed files. The security weakness is the absence of reproducible dependency controls: a future compromised, malicious, or incompatible release satisfying the lower-bound constraint can be selected automatically during installation. ### Attack Path 1. A maintainer or user follows the documented installation command. 2. `pip` resolves the latest package versions satisfying the `>=` constraints. 3. A newly published or compromised direct or transitive dependency is selected. 4. Package build, installation, or import-time code runs with the privileges of the installing user or environment. 5. The compromised component can affect the Skill or installation environment. This path is conditional on compromise or malicious publication of an allowed dependency version; the audit did not establish that any currently listed package is malicious. ### Impact Assessment Impact depends on the privileges used for installation and the behavior of the selected package. ...[truncated 531 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Separate runtime requirements from development requirements. Keep `pytest` and `flake8` in a development-only file or dependency group. 2. Pin reviewed direct and transitive dependency versions through a lock file or compiled requirements file. 3. Require package hashes for deployment installations, for example by generating a hash-locked requirements file and using: ```bash pip install --require-hashes -r requirements.lock ``` 4. Review whether `GitPython` is genuinely required. The reviewed source does not import it, despite documentation describing it as optional. 5. Use an automated dependency update process that opens reviewed, tested changes rather than accepting arbitrary future versions at install time. 6. Perform vulnerability and provenance scanning on resolved artifacts and install only from an explicitly approved package index. 7. Build and install in an isolated virtual environment under a non-privileged account. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (26)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
A second, more specific description-behavior mismatch indicates the skill may only provide limited logging/statistics while claiming code-quality scanning, health checks, and improvement generation. It also reportedly uses a database without declared permissions/resources, compounding the risk by obscuring stateful behavior and creating hidden data-handling expectations.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
A second, more specific description-behavior mismatch indicates the skill may only provide limited logging/statistics while claiming code-quality scanning, health checks, and improvement generation. It also reportedly uses a database without declared permissions/resources, compounding the risk by obscuring stateful behavior and creating hidden data-handling expectations.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises commands and detected capabilities that imply file read/write behavior, but it does not declare any tool scope such as permissions or allowed-tools. This creates an authorization and transparency gap: consumers may grant or assume broader access than intended, and reviewers cannot verify whether filesystem access is constrained to safe paths or purposes.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The documented `clear --days 90` command suggests deletion of stored data, but the skill description provides no warning that the action may be destructive or irreversible. In an operational context, users could remove logs or audit history unintentionally, reducing forensic visibility and disrupting monitoring or reporting workflows.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This code creates and uses a persistent local SQLite database under the user's home directory to store skill usage logs and user feedback, including comments, context, error messages, and optional user identifiers. Even though there is no direct code-execution risk here, undisclosed persistence of potentially sensitive telemetry can create privacy and compliance issues, especially because the skill is designed to monitor usage and health over time.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This Python file's module docstring and user-facing descriptive strings are written exclusively in Chinese, and there is no indication that users can choose another language or that the skill is intentionally region-specific. Under the policy, forcing a specific language without opt-in is a natural-language locale violation.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The module docstring and many user-visible CLI messages are written only in Chinese, and there is no option for users to select another language or indication that the tool is intentionally region-specific. This creates a language/locale policy concern because the skill appears to enforce a specific language without user opt-in.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The `clear` subcommand invokes `db.clear_old_data(args.days)` immediately, with no confirmation prompt, dry-run mode, or secondary safeguard. In a CLI that manages stored operational data, an accidental invocation or misuse can irreversibly delete records and reduce auditability, especially if run in automation or with an unexpectedly small/negative retention value depending on downstream validation.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The monitor persists arbitrary caller-supplied context data directly to the database with no filtering, minimization, or sensitivity checks. In practice, context objects often contain prompts, inputs, identifiers, tokens, or other sensitive operational data, so this creates a privacy and data-retention risk if developers pass rich runtime state into the tracker.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The failure path stores raw exception text via tracker.failed(str(e)), which can include secrets, file paths, SQL fragments, request contents, or internal implementation details. Retaining unsanitized exception messages in a database increases the risk of sensitive data exposure to operators, logs viewers, or attackers who later gain access to telemetry storage.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The module docstrings, report labels, status text, and error messages are written entirely in Chinese, which implies the skill's generated reports are fixed to a specific language. Under the locale policy, forcing one language without user choice or documented justification is a natural-language policy violation.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The module docstring says it is responsible for generating analysis and health reports, but generate_health_report also saves derived health-score data back into the database. That is a state-changing behavior beyond straightforward report generation and is not clearly reflected in the stated reporting-only purpose.

Natural-Language Policy Violations

Low
Confidence
96% confidence
Finding
Module and class docstrings throughout the file are written in Chinese, which imposes a specific language on maintainers or users reading embedded instructions and comments. Under the policy, forcing a specific language without opt-in or justification is a natural-language policy concern.

Unpinned Dependencies

Low
Category
Supply Chain
Content
# Skill Evolver Dependencies

# YAML parsing
PyYAML>=6.0

# Git operations (optional)
GitPython>=3.1.0
Confidence
97% confidence
Finding
The dependency is specified with a lower bound only, which permits installation of any newer release, including unexpected major versions or compromised upstream releases. This weakens reproducibility and makes it harder to verify whether a known-safe version of PyYAML is being used, especially important because YAML parsers have had prior security issues around unsafe parsing behavior.

Unverifiable Dependency: PyYAML has 8 known advisory(ies) (CVE-2019-20477 (Deserialization of Untrusted Data in PyYAML); CVE-2020-1747 (Improper Input Validation in PyYAML); CVE-2020-14343 (Improper Input Validation in PyYAML) +5 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding
PyYAML has known historical advisories, and because the manifest does not pin the package, it is impossible to verify whether the resolved version is affected. In a skill that may parse skill metadata or configuration, ambiguous PyYAML versioning is more concerning because insecure parser behavior can become exploitable when handling untrusted YAML content.

Unpinned Dependencies

Low
Category
Supply Chain
Content
PyYAML>=6.0

# Git operations (optional)
GitPython>=3.1.0

# Testing
pytest>=7.0.0
Confidence
97% confidence
Finding
GitPython is unpinned, so builds may resolve to different versions over time, including versions with security regressions or newly introduced risky behavior. For a skill that evaluates and evolves other agent skills, git-related functionality can interact with repositories and local tooling, making dependency predictability more important than in a purely passive package.

Unverifiable Dependency: GitPython has 16 known advisory(ies) (CVE-2026-78676 (GitPython: Dormant multi-line git-config values are corrupted into live injected); CVE-2026-67325 (GitPython: Command Injection via git long-option prefix abbreviation bypass of C); CVE-2024-22190 (Untrusted search path under some conditions on Windows allows arbitrary code exe) +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
95% confidence
Finding
GitPython has multiple advisories, and the unpinned requirement prevents determining whether the installed version contains a known fix. Given this skill's stated role around analyzing and improving other skills, repository interaction is plausible, so uncertainty around a git library version increases exposure to dependency-related security issues.

Unpinned Dependencies

Low
Category
Supply Chain
Content
GitPython>=3.1.0

# Testing
pytest>=7.0.0

# Code linting
flake8>=6.0.0
Confidence
92% confidence
Finding
pytest is also unpinned, which creates non-reproducible environments and can silently introduce vulnerable or incompatible releases into testing workflows. While primarily a development dependency, compromised or vulnerable test tooling can still affect CI systems or local developer environments.

Unverifiable Dependency: pytest has 2 known advisory(ies) (CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
87% confidence
Finding
pytest has known advisories, and without a fixed version there is no reliable way to determine whether the environment is vulnerable. Although this is a testing dependency rather than core runtime logic, vulnerable test tooling can still impact CI pipelines, temporary file handling, or developer workstations.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pytest>=7.0.0

# Code linting
flake8>=6.0.0
Confidence
90% confidence
Finding
flake8 is unpinned, which allows arbitrary newer versions to be installed and may expose CI or developer systems to vulnerable or malicious upstream releases. The main risk is supply-chain instability and reduced reproducibility rather than direct runtime exploitation of the deployed skill.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The natural-language text in the module docstring is presented only in Chinese and does not indicate any user language or locale choice. Under the policy rule, forcing a specific language without opt-in can be a locale/language policy violation even in code comments or docstrings.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The module docstrings and inline documentation are exclusively in Chinese, with no indication that the language is configurable or intentionally limited to a Chinese-language environment. Per the policy, forcing a specific language without opt-in is a natural-language policy concern.

Intent-Code Divergence

Low
Confidence
82% confidence
Finding
The generate_health_report docstring presents the method as producing and returning a health report, but the implementation also invokes _save_health_score and updates persistent state. This creates an intent mismatch between the documented behavior and the actual side effect.

Missing User Warnings

Low
Confidence
80% confidence
Finding
This branch serializes the report and writes it to a user-specified file path, which is a safety-relevant file write. Although exporting is the function's purpose, there is no visible disclosure that directories may be created and files may be overwritten.

Missing User Warnings

Low
Confidence
79% confidence
Finding
This branch writes Markdown output to `output_path`, which is a filesystem modification. The code does not include a confirmation prompt, user-visible logging, or an explicit warning that the chosen path will be created or overwritten.

Static analysis

No suspicious patterns detected.