Back to skill

Security audit

alarm

Security checks for vulnerabilities and agentic risk

Overview

This Feishu reminder skill largely does what it advertises, but it needs review because it can send credentials, voice files, and message content to configurable API hosts without validating the destination.

Review before installing in production. Keep Feishu and SenseAudio base URLs pinned to trusted HTTPS endpoints, protect the process environment and SQLite database, restrict who can invoke reminder creation and --no-confirm, and set retention/deletion rules for stored message and audio-derived data.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/asr.py:13
Finding
Unrestricted API endpoint overrides can expose credentials and sensitive content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/asr.py:13-30`, `scripts/feishu_api.py:10-44`, `scripts/config.py:13-14` **Vulnerability Type**: Unvalidated security-sensitive endpoint configuration **Risk Level**: Medium ### Vulnerable Code `scripts/asr.py:13-30`: ```python base_url = get_optional('SENSEAUDIO_BASE_URL', 'https://api.senseaudio.cn').rstrip('/') self.api_url = f'{base_url}/v1/audio/transcriptions' self.api_key = get_required('SENSEAUDIO_API_KEY') self.model = get_optional('SENSEAUDIO_ASR_MODEL', 'sense-asr') def transcribe(self, audio_path: str | Path, language: str | None = 'zh') -> dict[str, Any]: path = Path(audio_path) with path.open('rb') as f: files = {'file': (path.name, f)} data: dict[str, Any] = {'model': self.model, 'response_format': 'json'} if language and self.model != 'sense-asr-deepthink': data['language'] = language resp = requests.post( self.api_url, headers={'Authorization': f'Bearer {self.api_key}'}, data=data, files=files, timeout=120, ) ``` `scripts/feishu_api.py:10-44`: ```python self.base_url = get_optional('FEISHU_BASE_URL', 'https://open.feishu.cn') self.app_id = get_required('FEISHU_APP_ID') self.app_secret = get_required('FEISHU_APP_SECRET') def tenant_access_token(self) -> str: resp = requests.post( f'{self.base_url}/open-apis/auth/v3/tenant_access_token/internal', headers={'Content-Type': 'application/json; charset=utf-8'}, json={'app_id': self.app_id, 'app_secret': self.app_secret}, timeout=30, ) body = resp.json() if not resp.ok or body.get('code', 0) != 0: raise RuntimeError(f'Feishu tenant token request failed: {body}') token = (body.get('tenant_access_token') or '').strip() if not token: raise RuntimeError(f'Feishu tenant token was empty: {body}') return token def send_text_message(self, receive_id ...[truncated 2919 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse configured URLs with a standard URL parser before use. 2. Require the `https` scheme for production endpoints. 3. Allow only documented trusted hostnames, such as the official SenseAudio and Feishu hosts, by default. 4. Reject URLs containing embedded credentials, fragments, unexpected ports, or non-network schemes. 5. Disable redirects for credential-bearing requests or validate every redirect target and require the same trusted origin. 6. If custom endpoints are required for development, place them behind an explicit development-only option and display a clear warning that credentials and user content will be transmitted there. 7. Consider separate low-privilege credentials for custom ASR providers. 8. Add automated tests confirming that HTTP, loopback, link-local, private-network, and unapproved external destinations are rejected. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/reminder_store.py:50
Finding
Sensitive reminder and raw ASR data is persisted without enforced restrictive permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/reminder_store.py:9-23`, `scripts/reminder_store.py:50-58`, `scripts/main.py:109-125` **Vulnerability Type**: Insecure local storage of sensitive information **Risk Level**: Low ### Vulnerable Code `scripts/reminder_store.py:9-23`: ```python SCHEMA = ''' CREATE TABLE IF NOT EXISTS reminders ( id INTEGER PRIMARY KEY AUTOINCREMENT, source_text TEXT NOT NULL, summary TEXT NOT NULL, receive_id TEXT NOT NULL, receive_id_type TEXT NOT NULL, sender_open_id TEXT, sender_name TEXT, deadline_iso TEXT NOT NULL, reminder_iso TEXT NOT NULL, confirm_text TEXT NOT NULL, reminder_text TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', created_at TEXT NOT NULL, sent_at TEXT, extra_json TEXT ); ``` `scripts/reminder_store.py:50-58`: ```python def __init__(self, db_path: str | Path) -> None: self.db_path = str(db_path) Path(self.db_path).parent.mkdir(parents=True, exist_ok=True) self._init_db() def _connect(self) -> sqlite3.Connection: conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row return conn ``` `scripts/main.py:109-125`: ```python extra_payload = { 'message_id': message_id, 'source_type': source_type, 'source_audio_path': source_audio_path or None, } if transcript_raw is not None: extra_payload['asr_raw'] = transcript_raw store = get_store() reminder_id = store.add({ 'source_text': text, 'summary': parsed.summary, 'receive_id': receive_id, 'receive_id_type': receive_id_type, 'sender_open_id': sender_open_id, 'sender_name': sender_name, 'deadline_iso': parsed.deadline_dt.isoformat(), 'reminder_iso': parsed.reminder_dt.isoformat(), 'confirm_text': parsed.confirm_text, 'reminder_text': parsed.reminder_text, 'created_at': now.isoformat(), 'extra_json': json.dumps(extra_payload, ensure_ascii=False), }) ``` ### Technical Analysis The SQLite ...[truncated 1705 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create the database directory with owner-only mode `0700`. 2. Create the database with owner-only mode `0600` and verify permissions after creation. 3. Reject symbolic-link database targets where the deployment model makes symlink attacks possible. 4. Store only fields necessary for reminder delivery. 5. Do not persist complete raw ASR responses or source audio paths by default. 6. Add configurable retention limits and securely delete sent or expired reminders after the retention period. 7. Provide an administrative deletion operation for user data. 8. Document the sensitivity of the database and require it to reside on protected storage. 9. Consider application-level encryption when local administrators or storage snapshots are outside the trusted boundary. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/main.py:177
Finding
Non-atomic reminder delivery allows duplicate messages<![CDATA[ ## Vulnerability Details **File Location**: `scripts/main.py:177-190`, `scripts/reminder_store.py:89-112` **Vulnerability Type**: Race condition and non-idempotent state transition **Risk Level**: Low ### Vulnerable Code `scripts/main.py:177-190`: ```python def cmd_poll_due(args: argparse.Namespace) -> dict: tz_name = get_optional('FEISHU_SMART_ALARM_TZ', 'Asia/Shanghai') now = parse_now(args.now, tz_name) store = get_store() due_items = store.list_due(now.isoformat()) client = FeishuClient() sent_ids: list[int] = [] for item in due_items: client.send_text_message( receive_id=item.receive_id, receive_id_type=item.receive_id_type, text=item.reminder_text, ) store.mark_sent(item.id, now.isoformat()) sent_ids.append(item.id) ``` `scripts/reminder_store.py:89-112`: ```python def list_due(self, now_iso: str) -> list[Reminder]: with self._connect() as conn: rows = conn.execute( ''' SELECT * FROM reminders WHERE status = 'pending' AND reminder_iso <= ? ORDER BY reminder_iso ASC, id ASC ''', (now_iso,), ).fetchall() return [Reminder(**dict(row)) for row in rows] def mark_sent(self, reminder_id: int, sent_at_iso: str) -> None: with self._connect() as conn: conn.execute( 'UPDATE reminders SET status = ?, sent_at = ? WHERE id = ?', ('sent', sent_at_iso, reminder_id), ) conn.commit() ``` ### Technical Analysis Selection, delivery, and state transition are separate operations: 1. A pending reminder is selected. 2. The network message is sent. 3. The reminder is marked as sent. No transaction or atomic claim prevents another polling process from selecting the same pending row between these operations. Multiple `run-loop` or `poll-due` processes can therefore deliver the same reminder concurrently. There is also an ...[truncated 1365 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Atomically claim due reminders before sending them. 2. Within a write transaction, change each selected row from `pending` to `processing` using a compare-and-set condition such as `WHERE id = ? AND status = 'pending'`. 3. Send only rows successfully claimed by the current worker. 4. Record a worker identifier, claim timestamp, attempt count, and last error. 5. Recover stale `processing` records after a bounded timeout. 6. Use a stable idempotency key with the remote API if Feishu supports one. 7. Distinguish `sent`, `failed`, and `delivery_unknown` states so uncertain delivery is not silently retried without policy controls. 8. Add concurrency tests with multiple polling processes and crash-recovery tests around the network-send/database-update boundary. ]]>
Vulnerability Patterns
  • 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
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (30)

Credential Access

High
Category
Privilege Escalation
Content
---
name: feishu-smart-alarm
description: 读取飞书/lark 的文本或语音消息,识别是否包含需要提醒的待办和截止时间,并根据消息语义和时间跨度自动判断一个偏宽松的提醒时间。适用于飞书机器人处理“今天 5 点前给我”“明天下午三点提醒我”“发语音说周五前记得提交”这类消息。支持先用 senseaudio asr 把语音转文字,再分析并建立提醒;启用 asr 时会把传入的本地音频上传到配置的 senseaudio 接口。会将提醒持久化到本地 sqlite,并使用飞书应用凭证向原会话发送确认消息和到点提醒。当前版本硬性要求通过进程环境变量提供 feishu_app_id、feishu_app_secret、senseaudio_api_key;不会读取 .env / .env.local,也不会临时提示输入。
metadata:
  required_env_vars:
    - FEISHU_APP_ID
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
---
name: feishu-smart-alarm
description: 读取飞书/lark 的文本或语音消息,识别是否包含需要提醒的待办和截止时间,并根据消息语义和时间跨度自动判断一个偏宽松的提醒时间。适用于飞书机器人处理“今天 5 点前给我”“明天下午三点提醒我”“发语音说周五前记得提交”这类消息。支持先用 senseaudio asr 把语音转文字,再分析并建立提醒;启用 asr 时会把传入的本地音频上传到配置的 senseaudio 接口。会将提醒持久化到本地 sqlite,并使用飞书应用凭证向原会话发送确认消息和到点提醒。当前版本硬性要求通过进程环境变量提供 feishu_app_id、feishu_app_secret、senseaudio_api_key;不会读取 .env / .env.local,也不会临时提示输入。
metadata:
  required_env_vars:
    - FEISHU_APP_ID
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
---
name: feishu-smart-alarm
description: 读取飞书/lark 的文本或语音消息,识别是否包含需要提醒的待办和截止时间,并根据消息语义和时间跨度自动判断一个偏宽松的提醒时间。适用于飞书机器人处理“今天 5 点前给我”“明天下午三点提醒我”“发语音说周五前记得提交”这类消息。支持先用 senseaudio asr 把语音转文字,再分析并建立提醒;启用 asr 时会把传入的本地音频上传到配置的 senseaudio 接口。会将提醒持久化到本地 sqlite,并使用飞书应用凭证向原会话发送确认消息和到点提醒。当前版本硬性要求通过进程环境变量提供 feishu_app_id、feishu_app_secret、senseaudio_api_key;不会读取 .env / .env.local,也不会临时提示输入。
metadata:
  required_env_vars:
    - FEISHU_APP_ID
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
p4.add_argument('--sender-open-id', default='', help='原消息发送者 open_id,可选')
    p4.add_argument('--sender-name', default='', help='原消息发送者名称,可选')
    p4.add_argument('--message-id', default='', help='原消息 ID,可选')
    p4.add_argument('--no-confirm', action='store_true', help='只建提醒,不发送确认消息')
    p4.add_argument('--now', default='', help='当前时间,ISO 格式,可选')
    p4.set_defaults(func=cmd_create_reminder)
Confidence
85% confidence
Finding
The '--no-confirm' parameter allows creation of persisted reminders without sending any user-visible acknowledgment. In a bot or agent context, this can enable stealthy scheduling of future outbound messages if an upstream component passes attacker-influenced parameters, reducing the chance that the target notices unauthorized actions at creation time.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
p5.add_argument('--sender-open-id', default='', help='原消息发送者 open_id,可选')
    p5.add_argument('--sender-name', default='', help='原消息发送者名称,可选')
    p5.add_argument('--message-id', default='', help='原消息 ID,可选')
    p5.add_argument('--no-confirm', action='store_true', help='只建提醒,不发送确认消息')
    p5.add_argument('--now', default='', help='当前时间,ISO 格式,可选')
    p5.add_argument('--language', default='zh', help='ASR 语言,默认 zh')
    p5.set_defaults(func=cmd_create_reminder_audio)
Confidence
85% confidence
Finding
In the audio flow, '--no-confirm' similarly permits silent creation of reminders from transcribed content, combining external input processing with later automated message delivery. If upstream callers are attacker-controlled or insufficiently validated, this increases the risk of covert persistence and deferred messaging to Feishu recipients.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The description states the skill reads Feishu/Lark text or voice messages and all examples, confirmation text, and behavior are specified only in Chinese. This effectively forces a specific language/locale without any opt-in or documented justification that the skill is intended only for a China-specific or Chinese-only deployment.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The documentation explicitly instructs operators to download Feishu voice messages to a local file and send that file to a third-party ASR service, but it does not warn about privacy implications, data retention, or consent requirements. Because voice messages can contain sensitive personal or business information, this omission can lead to unauthorized local storage and external processing of user data in real deployments.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The transcribe method defaults the language parameter to 'zh', causing the skill to prefer a specific language unless the caller explicitly overrides it. This is a natural-language locale policy concern because the code imposes a language choice by default rather than offering neutral auto-detection or explicit user opt-in.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The code uploads local audio file contents to an external SenseAudio endpoint using a bearer token, but there is no in-code consent, disclosure, destination validation, or restriction on what audio may be sent. In this skill’s context, users may submit voice messages that contain sensitive personal or business information, so silent exfiltration to a third-party service creates a real privacy and data-handling risk even if it is part of intended functionality.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The RuntimeError message is written entirely in Chinese and states behavior to the user without any opt-in or alternative locale handling. For a general-purpose skill file, this creates a language policy concern because it forces a specific language in user-facing output without documented justification.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The code loads sensitive credentials via FEISHU_APP_ID and FEISHU_APP_SECRET, but there is no confirmation prompt, logging, or inline documentation warning that the skill accesses secrets. For code files, access to sensitive environment variables should have some form of disclosure unless clearly documented elsewhere, which is not visible in this file.

External Transmission

Medium
Category
Data Exfiltration
Content
self.app_secret = get_required('FEISHU_APP_SECRET')

    def tenant_access_token(self) -> str:
        resp = requests.post(
            f'{self.base_url}/open-apis/auth/v3/tenant_access_token/internal',
            headers={'Content-Type': 'application/json; charset=utf-8'},
            json={'app_id': self.app_id, 'app_secret': self.app_secret},
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The requests.post calls send app credentials to obtain a tenant token and later transmit message text and recipient identifiers to Feishu over the network. This file contains no user-facing disclosure, confirmation, or explanatory comment about these outbound transmissions, so users are not warned that data will be sent to an external service.

External Transmission

Medium
Category
Data Exfiltration
Content
def send_text_message(self, receive_id: str, text: str, receive_id_type: str = 'chat_id') -> dict:
        token = self.tenant_access_token()
        resp = requests.post(
            f'{self.base_url}/open-apis/im/v1/messages',
            params={'receive_id_type': receive_id_type},
            headers={
Confidence
92% confidence
Finding
The destination URL for sending bearer-authenticated messages is built from FEISHU_BASE_URL, an externally supplied environment variable, with no validation that it points to the official Feishu service. If an attacker or misconfiguration controls that variable, the code will transmit the tenant access token and message content to an arbitrary server, causing credential exfiltration and message data leakage. The skill context makes this more dangerous because it handles potentially sensitive reminder text derived from chat or voice content and uses privileged app credentials automatically.

Tainted flow: 'token' from requests.post (line 25, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
def send_text_message(self, receive_id: str, text: str, receive_id_type: str = 'chat_id') -> dict:
        token = self.tenant_access_token()
        resp = requests.post(
            f'{self.base_url}/open-apis/im/v1/messages',
            params={'receive_id_type': receive_id_type},
            headers={
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The transcribe-audio command sends a user-provided local audio file to SenseAudioASR for transcription, which is a network/data-processing operation involving potentially sensitive voice content. Although the code prints results afterward, there is no prior warning, confirmation, comment, or docstring here disclosing that audio content will be processed by an ASR service.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The create_reminder_from_text flow writes source text, sender identifiers, names, timestamps, and extra metadata into a reminder database. This is a user-data persistence operation, but this file provides no warning in comments, docstrings, or command help that message contents and identifiers will be stored.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
When no_confirm is false, the code sends a confirmation message through FeishuClient, which is an external communication action involving user-related content. The command help mentions that it sends a confirmation message, but the code does not otherwise disclose the external outbound operation or any privacy implications of transmitting parsed content.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The poll-due command iterates over stored reminders and sends reminder_text to recipients via FeishuClient, which is an external messaging action. While the command name and help indicate reminders are sent, there is no warning or documentation here about transmitting stored reminder content to external recipients.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The transcribe-audio command defaults --language to 'zh', making Chinese the implicit processing language unless the user overrides it. This is a locale/language policy concern because the skill silently prefers a specific language rather than asking the user or auto-detecting with consent.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The analyze-audio command sets --language to 'zh' by default, which imposes a specific language preference during transcription and analysis. Because the user is not prompted to choose a language and the file does not justify a China/Chinese-only scope, this is a natural-language policy issue.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
p4.add_argument('--sender-open-id', default='', help='原消息发送者 open_id,可选')
    p4.add_argument('--sender-name', default='', help='原消息发送者名称,可选')
    p4.add_argument('--message-id', default='', help='原消息 ID,可选')
    p4.add_argument('--no-confirm', action='store_true', help='只建提醒,不发送确认消息')
    p4.add_argument('--now', default='', help='当前时间,ISO 格式,可选')
    p4.set_defaults(func=cmd_create_reminder)
Confidence
65% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
p4.add_argument('--sender-open-id', default='', help='原消息发送者 open_id,可选')
    p4.add_argument('--sender-name', default='', help='原消息发送者名称,可选')
    p4.add_argument('--message-id', default='', help='原消息 ID,可选')
    p4.add_argument('--no-confirm', action='store_true', help='只建提醒,不发送确认消息')
    p4.add_argument('--now', default='', help='当前时间,ISO 格式,可选')
    p4.set_defaults(func=cmd_create_reminder)
Confidence
65% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The create-reminder-audio command also defaults --language to 'zh', forcing a specific language choice for audio processing unless the user manually changes it. This creates a language/locale policy violation because the default is applied silently rather than through user choice or documented regional limitation.

Static analysis

No suspicious patterns detected.