Back to skill

Security audit

Business Information Inquiry Tool‌

Security checks for vulnerabilities and agentic risk

Overview

This enterprise-report skill is mostly purpose-aligned, but it has review-worthy risks from broad local credential discovery, unsafe API-key handling, and unescaped HTML report content.

Review this skill before installing in shared, enterprise, or sensitive workspaces. Prefer providing TAVILY_API_KEY only through an explicit environment variable, avoid placing unrelated secrets in .env files the skill can read, and do not host or redistribute generated HTML reports unless the unescaped-field issue is fixed or the report is sanitized.

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

T01 · Skill Instruction Hijacking

Error
Location
scripts/html_generator.py:243
Finding
Mandatory Promotional and Named Contact Content in Generated Reports<![CDATA[ ## Vulnerability Details **File Location**: `scripts/html_generator.py:243-248` **Vulnerability Type**: Forced output modification and promotional content injection **Risk Level**: High ### Vulnerable Code ```python def generate_footer(version, data, time_str): sources = data.get('data_sources', '公开数据、企业年报') report_date = time_str.split(' ')[0] if ' ' in time_str else time_str return f'''<div class="footer-brand">KINGDEE · 企业信息调研报告</div> <div class="footer-divider"></div> <p>Generated by <strong>KD-Enterprise-Info Skill v{version}</strong> | 反馈建议,请联系金蝶总部张贺老师</p> <p style="margin-top: 6px;">数据来源:<strong>{escape_html(sources)}</strong> | 报告日期:<strong>{report_date}</strong></p> <p style="margin-top: 6px; opacity: 0.75;">免责声明:本报告基于公开数据整理,仅供参考,不构成投资建议。</p>''' ``` The generated footer is inserted unconditionally through the following placeholder: ```python 'FOOTER_CONTENT': generate_footer(version, cleaned_data, time_str), ``` ### Technical Analysis Every generated company report receives a fixed brand attribution and a solicitation directing users to a specifically named contact. Callers cannot disable or replace this content through the documented interface. Although the Skill declares a Kingdee-inspired report style, the unavoidable insertion of named contact information is not necessary to search public company information or render an HTML report. This changes the requested output for an unrelated promotional purpose and creates misleading attribution risks when reports are redistributed. ### Attack Path 1. A user requests a company-information report. 2. The Agent invokes `generate_html()`. 3. `generate_html()` always calls `generate_footer()`. 4. The returned promotional footer is assigned to `FOOTER_CONTENT`. 5. Template replacement embeds the named contact solicitation into the final report. 6. Any recipient opening or receiving the report sees the unsolicited attribution and con ...[truncated 533 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the named person and contact solicitation from `generate_footer()`. 2. Use a neutral default footer limited to the report date, source disclaimer, and optional software version. 3. Make all branding and attribution explicitly opt-in through caller-controlled configuration. 4. Document any enabled attribution behavior before report generation. 5. Ensure the caller can suppress the footer entirely when producing white-label or internal reports. 6. Add regression tests confirming that no personal contact information or promotional text appears unless explicitly requested. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/html_generator.py:280
Finding
HTML and JavaScript Injection Through Unescaped Report Fields<![CDATA[ ## Vulnerability Details **File Location**: `scripts/html_generator.py:280-420` **Vulnerability Type**: HTML injection and stored cross-site scripting **Risk Level**: High ### Vulnerable Code The warning value and numerous values derived from company names or external search results are inserted without HTML escaping: ```python warning_box = '' if fallback_hint and fallback_hint not in (MISSING_DEFAULT, '无公开数据'): warning_box = f'<div class="warning-box">{fallback_hint}</div>' placeholders = { 'PAGE_TITLE': f'企业调研报告 - {escape_html(company_name)}', 'CSS_CONTENT': css_content, 'WARNING_BOX': warning_box, 'HEADER_ICON': '📊', 'HEADER_TITLE': '企业信息调研报告', 'COMPANY_NAME': company_name, 'DATA_DATE_LABEL': '数据截至时间', 'DATA_DATE_VALUE': cleaned_data.get('data_date', time_str.split(' ')[0]), 'META_LABEL': '生成时间', 'GENERATE_TIME': time_str, 'INSIGHT_TEXT_1': insight.get('market_size', MISSING_DEFAULT), 'INSIGHT_TEXT_2': insight.get('growth_driver', MISSING_DEFAULT), 'INSIGHT_TEXT_3': insight.get('policy_impact', MISSING_DEFAULT), 'INSIGHT_TEXT_4': insight.get('trend', MISSING_DEFAULT), 'INSIGHT_TEXT_5': insight.get('competition', MISSING_DEFAULT), 'INSIGHT_TEXT_6': insight.get('tech_direction', MISSING_DEFAULT), 'PROFILE_CONTENT_1': profile.get('introduction', MISSING_DEFAULT), 'PROFILE_CONTENT_2': profile.get('development', MISSING_DEFAULT), 'PROFILE_CONTENT_3': profile.get('culture', MISSING_DEFAULT), 'PROFILE_CONTENT_4': profile.get('honors', MISSING_DEFAULT), 'BASIC_VALUE_1': basic.get('name', company_name), 'BASIC_VALUE_2': basic.get('credit_code', MISSING_DEFAULT), 'BASIC_VALUE_3': basic.get('legal_person', MISSING_DEFAULT), 'BASIC_VALUE_4': basic.get('reg_capital', MISSING_DEFAULT), 'BASIC_VALUE_5': basic.get('paid_capital', MISSING_DEFAULT), 'BASIC_VALUE_6': basic.get('established', MISSING_DEFAULT), 'BASIC_VALUE_7': basic.get('status', ...[truncated 3628 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape every untrusted textual value at the final rendering boundary using `escape_html()`. 2. Replace manual `str.replace()` rendering with an auto-escaping template engine. 3. Treat only internally generated, reviewed fragments such as `FINANCE_CARD` as trusted HTML. 4. Require explicit safe-marking for trusted fragments; never infer trust from field names. 5. Escape `company_name`, `fallback_hint`, error messages, dates, data sources, and every company-data field. 6. Avoid escaping values twice. In particular, construct digital-system descriptions as plain text and escape only once at output. 7. Add a restrictive Content Security Policy when reports are served over HTTP, for example by disallowing inline scripts and limiting network destinations. 8. If rich HTML input is genuinely required, process it with a strict allowlist sanitizer rather than accepting arbitrary markup. 9. Add automated tests using payloads containing: - `<script>` elements. - SVG and image event handlers. - Broken or nested tags. - Encoded HTML entities. - `javascript:` URLs. - Attribute-breaking quotes. 10. Test all placeholders, including error and fallback paths, rather than only helper-generated sections. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/enterprise_search.py:55
Finding
Tavily API Key Exposed in Request URL and Discovered From Overly Broad Secret Paths<![CDATA[ ## Vulnerability Details **File Location**: `scripts/enterprise_search.py:55-86` **Vulnerability Type**: Insecure credential discovery and transmission **Risk Level**: Medium ### Vulnerable Code ```python def get_tavily_key(): """从环境变量读取 Tavily API Key""" env_paths = [ '/root/.openclaw/workspace/.env', '~/.openclaw/workspace/.env', '~/.qclaw/workspace/.env', '~/.maxclaw/workspace/.env', '~/.kimiclaw/workspace/.env', '~/.env', './.env', ] for path in env_paths: expanded = os.path.expanduser(path) if os.path.exists(expanded): try: with open(expanded, 'r') as f: for line in f: if line.startswith('TAVILY_API_KEY='): return line.strip().split('=', 1)[1] except: continue return os.environ.get('TAVILY_API_KEY') def _call_tavily_api(query, max_results=3): """调用 Tavily Search API(可选增强)""" key = get_tavily_key() if not key: return None try: url = f'https://api.tavily.com/search?api_key={key}&query={urllib.parse.quote(query)}&max_results={max_results}' req = urllib.request.Request(url) with urllib.request.urlopen(req, timeout=10) as resp: result = json.loads(resp.read().decode('utf-8')) ``` The documentation also encourages storing the key in a shared workspace file: ```bash echo "TAVILY_API_KEY=your_tavily_api_key_here" >> ~/.openclaw/workspace/.env ``` ### Technical Analysis The optional Tavily integration legitimately needs a Tavily credential. However, the implementation exceeds the minimum file-access scope by searching multiple global and product-specific workspace locations, including a root-owned workspace, the user's home `.env`, and the current directory. The parser only returns a value whose name is exactly `TAVILY_API_KEY`; the reviewed code does not transmit unrela ...[truncated 1932 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer the process environment variable `TAVILY_API_KEY` as the sole default credential source. 2. If file-based configuration is required, accept one explicit caller-configured path rather than probing multiple home and workspace directories. 3. Validate that the selected credential file: - Is a regular file rather than a symbolic link. - Is owned by the expected user. - Has restrictive permissions. 4. Avoid searching `/root`, unrelated product workspaces, the generic home `.env`, and the current working directory automatically. 5. Use Tavily's officially supported authorization header or request-body authentication mechanism instead of putting the key in the URL. 6. Ensure errors, debug output, and telemetry redact API keys. 7. Do not silently suppress all exceptions. Return a sanitized failure status that distinguishes configuration, network, and response-parsing errors without including the secret. 8. Document exactly when a network request occurs, what company query is transmitted, and which credential source is used. 9. Add tests confirming that credentials never appear in request URLs, logs, exceptions, or returned data. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (30)

Credential Access

High
Category
Privilege Escalation
Content
# 方式一:环境变量
export TAVILY_API_KEY=your_tavily_api_key_here

# 方式二:写入 .env 文件(自动识别)
echo "TAVILY_API_KEY=your_tavily_api_key_here" >> ~/.openclaw/workspace/.env
```
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
# 方式一:环境变量
export TAVILY_API_KEY=your_tavily_api_key_here

# 方式二:写入 .env 文件(自动识别)
echo "TAVILY_API_KEY=your_tavily_api_key_here" >> ~/.openclaw/workspace/.env
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill description materially misrepresents behavior: it claims built-in 11-dimension search and HTML generation, while the implementation reportedly depends on externally supplied results, performs narrower optional network access, and reads .env/environment variables. Security controls and user trust depend on accurate declarations; when behavior and documentation diverge, operators may authorize a skill under false assumptions, leading to secret access, unexpected data flows, and unsafe orchestration decisions.

Ae1

High
Category
analysis-evasion
Content
6. **AI 直接提取结构化数据**:AI 阅读全部搜索结果,直接构建 JSON 字典(示例见附表),传入 `html_generator.py` 生成报告。**不调用正则、不调用 query_enterprise**。
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
6. **AI 直接提取结构化数据**:AI 阅读全部搜索结果,直接构建 JSON 字典(示例见附表),传入 `html_generator.py` 生成报告。**不调用正则、不调用 query_enterprise**。
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
6. **AI 直接提取结构化数据**:AI 阅读全部搜索结果,直接构建 JSON 字典(示例见附表),传入 `html_generator.py` 生成报告。**不调用正则、不调用 query_enterprise**。
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
6. **AI 直接提取结构化数据**:AI 阅读全部搜索结果,直接构建 JSON 字典(示例见附表),传入 `html_generator.py` 生成报告。**不调用正则、不调用 query_enterprise**。
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
def get_tavily_key():
    """从环境变量读取 Tavily API Key"""
    env_paths = [
        '/root/.openclaw/workspace/.env',
        '~/.openclaw/workspace/.env',
        '~/.qclaw/workspace/.env',
        '~/.maxclaw/workspace/.env',
Confidence
95% confidence
Finding
This hard-coded path is part of a routine that probes local .env files for API secrets, which is credential access beyond the narrow functional need of processing provided search text. In an agent runtime, such probing increases risk because the skill may access host secrets unrelated to the user's immediate task.

Credential Access

High
Category
Privilege Escalation
Content
"""从环境变量读取 Tavily API Key"""
    env_paths = [
        '/root/.openclaw/workspace/.env',
        '~/.openclaw/workspace/.env',
        '~/.qclaw/workspace/.env',
        '~/.maxclaw/workspace/.env',
        '~/.kimiclaw/workspace/.env',
Confidence
95% confidence
Finding
This line contributes to a multi-path search for local secret files, expanding the skill's credential access surface. Even if only one variable is extracted, the act of traversing common secret locations is inappropriate for a narrowly scoped enterprise search module.

Credential Access

High
Category
Privilege Escalation
Content
env_paths = [
        '/root/.openclaw/workspace/.env',
        '~/.openclaw/workspace/.env',
        '~/.qclaw/workspace/.env',
        '~/.maxclaw/workspace/.env',
        '~/.kimiclaw/workspace/.env',
        '~/.env',
Confidence
95% confidence
Finding
By checking additional home-directory .env locations, the skill reaches into broader host configuration than users would reasonably expect. This increases the chance of unauthorized secret use and makes the module more dangerous in shared or enterprise environments.

Credential Access

High
Category
Privilege Escalation
Content
'/root/.openclaw/workspace/.env',
        '~/.openclaw/workspace/.env',
        '~/.qclaw/workspace/.env',
        '~/.maxclaw/workspace/.env',
        '~/.kimiclaw/workspace/.env',
        '~/.env',
        './.env',
Confidence
95% confidence
Finding
This path continues the pattern of credential discovery across multiple environments, which is a least-privilege violation. In combination with outbound HTTP requests, it can silently enable third-party data transfer using locally discovered credentials.

Credential Access

High
Category
Privilege Escalation
Content
'~/.openclaw/workspace/.env',
        '~/.qclaw/workspace/.env',
        '~/.maxclaw/workspace/.env',
        '~/.kimiclaw/workspace/.env',
        '~/.env',
        './.env',
    ]
Confidence
95% confidence
Finding
The code probes yet another local .env path for secrets, reinforcing a broad credential-access behavior not necessary for core functionality. Such host secret discovery is especially sensitive in agent skills, where trust boundaries should be narrow and explicit.

Credential Access

High
Category
Privilege Escalation
Content
'~/.qclaw/workspace/.env',
        '~/.maxclaw/workspace/.env',
        '~/.kimiclaw/workspace/.env',
        '~/.env',
        './.env',
    ]
    for path in env_paths:
Confidence
95% confidence
Finding
Checking ~/.env expands access into a generic personal configuration file that may contain many unrelated credentials. This is dangerous because it normalizes broad secret inspection for a task that could function without touching user-level secret stores.

Credential Access

High
Category
Privilege Escalation
Content
'~/.maxclaw/workspace/.env',
        '~/.kimiclaw/workspace/.env',
        '~/.env',
        './.env',
    ]
    for path in env_paths:
        expanded = os.path.expanduser(path)
Confidence
95% confidence
Finding
Reading ./.env from the current working directory can unexpectedly pull credentials from unrelated projects, enabling network behavior with secrets outside the skill's ownership. This creates confusing and potentially unauthorized coupling between the skill and whatever repository it runs in.

Hidden Instructions

High
Category
Prompt Injection
Content
<body>
    <div class="container">

        <!-- ==================== 导航栏 ==================== -->
        <nav class="navbar animate-in">
            <div class="logo">{{HEADER_ICON}} {{HEADER_TITLE}}</div>
            <div class="nav-meta">
Confidence
70% confidence
Finding
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The document emphasizes automatic web search and HTML report generation, but does not prominently warn that external retrieval and local/report artifact creation will occur. Users may not realize their query will cause network activity and persisted output, which can expose sensitive investigation targets or create unwanted artifacts.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The README says the agent will auto-enable the skill whenever broad intent phrases like '企业调研' or '公司信息查询' appear with a company name. This creates a realistic risk of unintended activation, which can trigger network searches and report generation without sufficiently explicit user consent.

Natural-Language Policy Violations

Medium
Confidence
81% confidence
Finding
L136 明确写明海外企业查询“建议使用企业英文全称查询”,但整体文档、示例、输出风格与数据来源说明均默认中文语境,未说明是否支持用户选择其他语言输出。根据语言/locale 政策,若技能默认固定语言而无用户选择或充分的区域适用性说明,属于自然语言策略风险。

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill declares no explicit tool scope or permissions despite describing capabilities that involve environment-variable access, local file reads, and optional network calls. This creates an authorization and transparency gap: an agent or reviewer cannot reliably determine what resources the skill may access, increasing the chance of over-privileged execution or unintended secret exposure.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The skill says it supports natural requests such as “帮我查一下华为” and “做一份金蝶国际的企业背景调查报告”, but it does not define a constrained trigger boundary or exclusion conditions. Phrases like “帮我查一下…” are common everyday speech and could cause unintended invocation if used outside a clearly scoped command context.

Intent-Code Divergence

Medium
Confidence
90% confidence
Finding
The compliance comment claims the module does not collect privacy data, but the implementation later reads local .env files that may contain secrets or sensitive configuration. This mismatch is dangerous because it can mislead reviewers and users about the module's actual access to local sensitive data.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
The function scans multiple local .env locations outside the immediate needs of a simple enterprise search helper, which broadens its access to secrets present on the host. Even though it only looks for TAVILY_API_KEY, reading arbitrary .env files violates least privilege and can expose unrelated sensitive configuration if the code is later extended, logged, or repurposed.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill obtains Tavily credentials from local .env files without an obvious user-facing disclosure or consent boundary at the point of use. In an agent/skill context, silent secret discovery is risky because users may not expect the tool to inspect host configuration to enable networked behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
if not key:
        return None
    try:
        url = f'https://api.tavily.com/search?api_key={key}&query={urllib.parse.quote(query)}&max_results={max_results}'
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req, timeout=10) as resp:
            result = json.loads(resp.read().decode('utf-8'))
Confidence
87% confidence
Finding
The module sends user-derived enterprise search queries to the Tavily API, which is an external transmission to a third-party service. In this skill's context that behavior is somewhat expected, but it still carries data handling and privacy risk, especially because activation is tied to silently discovered credentials rather than explicit user opt-in.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The file-level description and all user-facing text are written exclusively in Chinese, indicating the skill is designed to generate reports in a fixed language. Under the policy, forcing a specific language without explicit user opt-in or documented regional justification is a natural-language policy concern.

Static analysis

No suspicious patterns detected.