Back to skill

Security audit

Lark Bot

Security checks for vulnerabilities and agentic risk

Overview

This Lark bot skill is mostly purpose-aligned, but its webhook receiver can accept unauthenticated requests in common setups, so it needs review before installation.

Review and fix the webhook code before using this skill on any reachable network: require LARK_APP_SECRET, reject missing or invalid signatures, bind locally unless deliberately exposed, and avoid copying the SKILL.md webhook example as-is. Also confirm the Lark app permissions are limited to the chats, Bitable tables, and Wiki spaces you intend to modify.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/webhook_server.py:19
Finding

Webhook authentication fails open when the application secret is absent

Content
View full analysis

Vulnerability Details

File Location: scripts/webhook_server.py:19-22, with network exposure at scripts/webhook_server.py:104
Vulnerability Type: Fail-open webhook authentication
Risk Level: High

Vulnerable Code

python
def verify_signature(timestamp, nonce, signature):
    if not APP_SECRET:
        return True
    
    string = f"{timestamp}{nonce}{APP_SECRET}"
    expected = hashlib.sha1(string.encode()).hexdigest()
    return hmac.compare_digest(expected, signature)

The service is also bound to every available network interface:

python
server = HTTPServer(('', PORT), Handler)

Technical Analysis

The signature-verification function explicitly accepts every request when LARK_APP_SECRET is unset or empty. This is a fail-open authentication design: a missing security control silently disables authentication rather than preventing the service from starting.

The server binds to all interfaces rather than loopback, so the unauthenticated endpoint may be reachable by other local-network hosts, containers, or the public Internet if the port is forwarded or exposed through the tunnel recommended by the Skill documentation.

An attacker does not need to know the Lark application secret. They can omit all signature headers and submit a syntactically valid callback body. The request will pass verify_signature and be processed as if it were authentic.

Attack Path

  1. An operator starts scripts/webhook_server.py without setting LARK_APP_SECRET.
  2. The server listens on all interfaces on port 19800 by default.
  3. An attacker identifies the exposed webhook endpoint.
  4. The attacker sends a forged POST request without valid Lark signature headers, for example an event_callback containing an attacker-controlled text message.
  5. verify_signature returns True solely because APP_SECRET is empty.
  6. The handler accepts the forged event and writes attacker-con ...[truncated 928 chars]
Remediation
View remediation

Remediation Suggestions

  1. Refuse to start the webhook service unless LARK_APP_SECRET is present and non-empty.
  2. Change verify_signature so missing configuration or missing signature headers always returns False.
  3. Bind to 127.0.0.1 by default and require an explicit configuration option to listen on public interfaces.
  4. Validate the request timestamp against a short acceptance window and track nonces or event identifiers to prevent replay attacks.
  5. Continue using constant-time comparison for signatures.
  6. Set a strict maximum request-body size before reading Content-Length.
  7. Place the service behind a TLS-terminating reverse proxy with request limits when public exposure is required.
  8. Add automated tests confirming that missing secrets, missing headers, invalid signatures, stale timestamps, and replayed events are rejected.

A fail-closed pattern should be used:

python
def verify_signature(timestamp, nonce, signature):
    if not APP_SECRET or not timestamp or not nonce or not signature:
        return False

    string = f"{timestamp}{nonce}{APP_SECRET}"
    expected = hashlib.sha1(string.encode()).hexdigest()
    return hmac.compare_digest(expected, signature)

def main():
    if not APP_SECRET:
        raise RuntimeError("LARK_APP_SECRET must be configured")

    server = HTTPServer(("127.0.0.1", PORT), Handler)
    server.serve_forever()

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:380
Finding

Documented webhook example processes callbacks without invoking signature verification

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:380-418
Vulnerability Type: Unauthenticated webhook processing in runnable documentation
Risk Level: High

Vulnerable Code

The example defines a signature-verification function:

python
def verify_signature(timestamp, nonce, signature):
    string = f"{timestamp}{nonce}{APP_SECRET}"
    h = hashlib.sha1(string.encode()).hexdigest()
    return h == signature

However, its request handler proceeds directly from parsing the body to processing callback data without calling that function:

python
class Handler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        body = self.rfile.read(content_length).decode()
        
        data = json.loads(body)
        
        if data.get("type") == "url_verification":
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(json.dumps({
                "challenge": data.get("challenge")
            }).encode())
            return
        
        if data.get("type") == "event_callback":
            event = data.get("event", {})
            msg_type = event.get("msg_type")
            
            if msg_type == "text":
                text = event.get("message", {}).get("text", "")
                print(f"Received message: {text}")
        
        self.send_response(200)
        self.end_headers()

Technical Analysis

Defining a verification function does not protect the endpoint unless every relevant request invokes it before parsing or processing security-sensitive event data. The example never reads Lark signature headers and never calls verify_signature.

This defect is especially significant because the same documentation instructs users to expose the local webhook service through a tu ...[truncated 1588 chars]

Remediation
View remediation

Remediation Suggestions

  1. Replace the documented example with a fail-closed implementation that validates every callback before event dispatch.
  2. Require LARK_APP_SECRET at startup rather than allowing an empty value.
  3. Extract the timestamp, nonce, and signature headers and reject requests when any required value is absent.
  4. Use hmac.compare_digest for signature comparison.
  5. Add timestamp freshness and replay protection.
  6. Limit Content-Length before reading the request body and return 400 for malformed JSON.
  7. Bind to loopback when a tunnel or reverse proxy is used.
  8. Ensure the documentation and executable script use the same reviewed authentication implementation to prevent divergence.
  9. Include a warning that the endpoint must not be exposed until signature verification and TLS termination are configured.

Authentication must occur before callback dispatch:

python
timestamp = self.headers.get("X-Lark-Request-Timestamp", "")
nonce = self.headers.get("X-Lark-Request-Nonce", "")
signature = self.headers.get("X-Lark-Signature", "")

if not verify_signature(timestamp, nonce, signature):
    self.send_response(401)
    self.end_headers()
    return

data = json.loads(body)
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
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (29)

Tainted flow: 'req' from os.environ.get (line 43, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/bitable_tasks.py (reported line 33)May include surrounding context.

python
data = json.dumps({"app_id": APP_ID, "app_secret": APP_SECRET}).encode()
    req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/json'})
    
    with urllib.request.urlopen(req, timeout=10) as resp:
        result = json.loads(resp.read())
        _cache['token'] = result['tenant_access_token']
        _cache['exp'] = time.time() + result.get('expire', 7200) - 300

Tainted flow: 'req' from os.environ.get (line 43, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/bitable_tasks.py (reported line 52)May include surrounding context.

python
'Content-Type': 'application/json'
        }
    )
    return json.loads(urllib.request.urlopen(req, timeout=15).read())

def list_records():
    return api("GET", "/records?page_size=100")

Tainted flow: 'req' from os.environ.get (line 31, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/send_message.py (reported line 19)May include surrounding context.

python
url = "https://open.larksuite.com/open-apis/auth/v3/tenant_access_token/internal"
    data = json.dumps({"app_id": APP_ID, "app_secret": APP_SECRET}).encode()
    req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/json'})
    with urllib.request.urlopen(req, timeout=10) as resp:
        return json.loads(resp.read())['tenant_access_token']

def send_message(text):

Tainted flow: 'req' from os.environ.get (line 31, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/send_message.py (reported line 40)May include surrounding context.

python
'Content-Type': 'application/json'
        }
    )
    return json.loads(urllib.request.urlopen(req).read())

if __name__ == '__main__':
    if len(sys.argv) < 2:

Tainted flow: 'req' from os.environ.get (line 38, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/wiki.py (reported line 30)May include surrounding context.

python
data = json.dumps({"app_id": APP_ID, "app_secret": APP_SECRET}).encode()
    req = urllib.request.Request(url, data=data, headers={'Content-Type': 'application/json'})
    
    with urllib.request.urlopen(req, timeout=10) as resp:
        result = json.loads(resp.read())
        _cache['token'] = result['tenant_access_token']
        _cache['exp'] = time.time() + result.get('expire', 7200) - 300

Tainted flow: 'req' from os.environ.get (line 38, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/wiki.py (reported line 47)May include surrounding context.

python
'Content-Type': 'application/json'
        }
    )
    return json.loads(urllib.request.urlopen(req, timeout=15).read())

def list_spaces():
    url = "https://open.larksuite.com/open-apis/wiki/v2/spaces"

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The description substantially overstates the functionality present in the supplied code. The code does correctly read sensitive configuration from environment variables and supports message sending to Lark/Feishu. However, it does not implement message receiving, any knowledge base APIs, or any Bitable/task-management functionality. Its actual primary purpose is a narrow outbound messaging utility rather than a complete bot integration solution. This is a material description-versus-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description promises a full Lark bot integration covering message receiving and sending, knowledge base operations, and Bitable task management, with sensitive configuration sourced from environment variables. The supplied code does match the environment-variable configuration aspect and partially matches message receiving via a webhook endpoint. However, it does not implement outbound messaging, bot reply APIs, knowledge base access, or any Bitable functionality. Its actual scope is much narrower: an inbound webhook server with signature verification and simple event logging. Because the primary purpose and supported capabilities are materially less complete than declared, this is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The description overstates the scope. The code does match the part about reading sensitive configuration from environment variables and performing knowledge base-related operations. However, it does not implement Feishu/Lark bot messaging integration, message receiving, or any Bitable task management. Its actual purpose is a narrow Wiki/Docx command-line helper, not a complete bot integration solution. This is a material description-behavior mismatch because major declared capabilities are absent.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 34)May include surrounding context.

核心功能

1. 获取 Access Token

python
import os

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The example defines a signature verification function but never calls it before processing webhook requests. That means any party able to reach the endpoint can send forged event payloads, trigger bot logic, inject untrusted content, or cause unauthorized downstream actions if users extend the TODO section.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · config.example.json (reported line 2)May include surrounding context.

json
{
  "example": "复制此文件为 .env 并填入真实配置",
  "LARK_APP_ID": "",
  "LARK_APP_SECRET": "",
  "LARK_APP_TOKEN": "",

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The webhook signature check is explicitly bypassed when LARK_APP_SECRET is unset, causing the server to accept unauthenticated requests. In a webhook receiver, this allows any external party who can reach the endpoint to forge events, trigger bot logic, and potentially inject malicious content into downstream automation.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill uses environment variables and makes outbound network requests to Lark APIs, but it does not declare any explicit tool scope or permissions boundary. In an agent ecosystem, this weakens transparency and can cause the runtime to grant broader capabilities than users expect, increasing the chance of unauthorized secret use or data transmission.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This markdown file documents a delete_record operation that removes Bitable records, but the surrounding skill description and notes do not warn users that records can be deleted or that the action may be irreversible. For markdown files, destructive behaviors that could affect user data should be disclosed clearly.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The manifest description at L003 and overview at L017 claim the skill supports '消息收发' (sending and receiving messages). However, the only receive-side implementation in the file is a sample webhook server that acknowledges callbacks and prints text, with a TODO placeholder instead of actual message-processing or reply behavior. This is a semantic mismatch between the claimed complete receive capability and the code actually provided.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The code accesses sensitive environment variables for Lark credentials and tokens, but there is no accompanying comment, docstring warning, or user-facing disclosure that the script depends on and uses these secrets. Under the code-file criteria, access to sensitive credentials should have some visible disclosure unless already clearly documented.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

The script sends authentication data to obtain a token and then performs HTTP requests to the Lark Bitable API, including task contents and bearer credentials. Although this is part of the tool's functionality, the file contains no explicit note that user-provided task data will be transmitted to an external service.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This code performs HTTP requests to Lark APIs using environment-sourced credentials and sends the user-supplied message text to an external service. While the file has a brief usage docstring, it does not disclose that message content and authentication data will be transmitted over the network, nor does it provide any user-facing notice before sending.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The service prints full user message content and sender identifiers to logs, which can expose sensitive chat data, credentials, personal information, or business content to anyone with log access. Because webhook handlers often process untrusted user input at scale, verbose logging increases privacy and data leakage risk, especially in shared infrastructure or centralized logging systems.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This code reads sensitive credentials from environment variables and sends them to the Lark API to obtain an access token, then performs authenticated HTTP requests without any confirmation prompt, logging, or explanatory comments warning the user about credential use and remote data transmission. Because the script also sends user-provided document content to an external service, it should disclose that behavior somewhere visible to the user.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

Natural-language instructions, headings, comments, and examples are all presented in Chinese, and the skill does not indicate that this language choice is optional or region-specific. Per the policy, forcing a specific language without user opt-in can be a natural-language policy violation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

The markdown describes multiple outbound API actions that transmit user-provided text and document content to Lark services, including chat messages and wiki/doc content writes. Although this is part of the skill's purpose, the document does not explicitly warn users that provided content will be sent to external Lark APIs and stored remotely.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The example field contains setup instructions only in Chinese ('复制此文件为 .env 并填入真实配置'), which imposes a specific language on users without opt-in or an alternative locale. This matches the policy category for language or locale constraints expressed in natural-language content.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The natural-language description and usage strings are entirely in Chinese, and the runtime output messages are also fixed to Chinese. This imposes a specific language/locale without any opt-in or explanation that the tool is intended only for a Chinese-speaking or region-specific context.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.