Back to skill

Security audit

飞书个人记账(含面板app)

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to do Feishu accounting setup and bookkeeping, but it asks for broad Feishu access and handles App Secrets in ways that can leak them.

Install only if you are comfortable granting this skill broad Feishu Base permissions and storing a long-lived App Secret locally. Use a least-privilege Feishu app if possible, avoid pasting secrets into chat/logs, rotate any secret already used with this workflow, verify or build the APK yourself, and manually confirm parsed receipt data before recording it.

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

Error
Location
scripts/apply_permissions.py:35
Finding
Feishu Application Receives Permissions Beyond the Skill's Operational Requirements<![CDATA[ ## Vulnerability Details **File Location**: `scripts/apply_permissions.py:35-56`; related mandatory instructions in `SKILL.md:60-66` **Vulnerability Type**: Excessive tenant-level permissions and violation of least privilege **Risk Level**: High ### Vulnerable Code ```python # 记账系统需要的所有 base 权限 REQUIRED_SCOPES = [ "base:app:read", "base:app:update", "base:app:create", "base:table:read", "base:table:create", "base:table:update", "base:table:delete", "base:field:read", "base:field:create", "base:field:update", "base:field:delete", "base:record:read", "base:record:create", "base:record:update", "base:record:delete", "base:view:read", "base:view:write_only", "bitable:app:readonly", "bitable:app", ] ``` The Skill instructions explicitly require all of these permissions: ```text base:app:read,base:app:update,base:app:create, base:table:read,base:table:create,base:table:update,base:table:delete, base:field:read,base:field:create,base:field:update,base:field:delete, base:record:read,base:record:create,base:record:update,base:record:delete, base:view:read,base:view:write_only,bitable:app:readonly,bitable:app ``` ### Technical Analysis The setup workflow legitimately needs to create a Base, create a table, create fields, and update select-field options. Normal accounting operations need record creation and, where deletion is enabled, record read and deletion. The audited scripts do not demonstrate a need for several requested capabilities, including: - `base:table:update` - `base:table:delete` - `base:field:delete` - `base:record:update` - `base:view:read` - `base:view:write_only` - Broad and overlapping `bitable:app` access The broad `bitable:app` permission also overlaps with the granular Base permissions, making the effective authorization boundary less restrictive than the individual operations suggest. Requiring all 19 permissions as mandatory gives the application destruc ...[truncated 1500 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Inventory the exact Feishu API methods used by each script and request only the scopes documented as necessary for those methods. 2. Remove unused table, field, record-update, and view permissions. 3. Avoid combining broad `bitable:app` access with granular scopes unless Feishu documentation proves it is strictly required. 4. Separate setup permissions from runtime permissions: - Use temporary setup authorization for Base, table, and field creation. - Use a narrower runtime application or credential for record creation, reading, and explicitly requested deletion. 5. Make destructive permissions optional. Request `base:record:delete` only if the user enables remote deletion. 6. Display a plain-English explanation for every requested scope before authorization. 7. Add automated tests that compare the declared permission list against the API methods actually called, preventing future scope expansion without review. 8. Rotate the App Secret and revoke the old permission grant after reducing the scope set. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/setup_bitable.py:107
Finding
App Secret Is Exposed Through Command Arguments, Standard Output, Chat, Plaintext Files, and APK Local Storage<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup_bitable.py:107-110,148-163`; related instructions in `SKILL.md:52-59,75-82,94-121`; APK storage design in `references/apk-architecture.md:39-40` **Vulnerability Type**: Insecure credential handling and plaintext secret storage **Risk Level**: High ### Vulnerable Code and Instructions The setup script accepts the App Secret directly through a command-line argument: ```python def main(): parser = argparse.ArgumentParser(description="飞书记账系统搭建脚本(单表版)") parser.add_argument("--app-id", required=True, help="飞书应用 App ID") parser.add_argument("--app-secret", required=True, help="飞书应用 App Secret") args = parser.parse_args() ``` It then prints the secret in human-readable and machine-readable output: ```python print("\n🎉 搭建完成!请保存以下凭证:") print(f"\n📋 App ID: {args.app_id}") print(f"📋 App Secret: {args.app_secret}") print(f"📋 Base Token: {base_token}") print(f"📋 明细表 Table ID: {table_id}") print(f"\n📊 多维表格链接: https://bytedance.feishu.cn/base/{base_token}") # 输出 JSON 供 AI 解析 print("\n---JSON_OUTPUT_START---") print(json.dumps({ "app_id": args.app_id, "app_secret": args.app_secret, "base_token": base_token, "table_id": table_id, }, ensure_ascii=False)) print("---JSON_OUTPUT_END---") ``` The Skill instructs the Agent to pass the secret on the command line and save it in a plaintext file: ```bash python3 scripts/setup_bitable.py \ --app-id "cli_用户的AppID" \ --app-secret "用户的AppSecret" ``` ```bash cat > /path/to/feishu-accounting/.env << 'EOF' FEISHU_APP_ID=你的App_ID FEISHU_APP_SECRET=你的App_Secret FEISHU_BASE_TOKEN=你的Base_Token FEISHU_DETAIL_TABLE_ID=你的明细表ID EOF ``` The optional APK architecture also specifies plaintext browser-style persistence: ```text 1. Login → input App ID / Secret / Base Token / Table ID → store in localStorage 2. init() → getToken() → fetchAll() → retrieve all detail records ``` ### Technical Analysis Secrets passed as command-line argum ...[truncated 2765 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Stop accepting App Secrets directly through command-line arguments. 2. Read secrets from one of the following: - A protected interactive input using `getpass.getpass()`. - A file descriptor supplied by a secret manager. - An operating-system credential store. 3. Never print the App Secret in human-readable or JSON output. Return only non-secret identifiers such as the Base Token and Table ID. 4. Do not repeat the App Secret in Agent chat. Inform the user that it has been stored securely without echoing its value. 5. If a local credential file remains necessary: - Create it atomically. - Set mode `0600` before writing sensitive content. - Verify ownership. - Add it to `.gitignore`. - Refuse to use it if group or world permissions are present. 6. Prefer an operating-system keyring or dedicated secret-management service over `.env`. 7. Replace APK `localStorage` with Android Keystore-backed encrypted storage. 8. Avoid placing a tenant-level App Secret in a dashboard client. Use a backend or constrained short-lived user token where feasible. 9. Redact secrets from exceptions, telemetry, execution transcripts, and debug logs. 10. Rotate any credential already processed through the current workflow. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:124
Finding
Mutable Prebuilt APK Is Recommended Without Integrity or Provenance Verification<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:124-131`; related architecture description in `references/apk-architecture.md:1-10,39-40` **Vulnerability Type**: Unverified third-party binary and supply-chain exposure **Risk Level**: Medium ### Vulnerable Instruction ```text - Download release version (recommended, ready to use): https://github.com/NaeemTC/feishu-accounting-skill/releases/latest/download/app-release.apk - Build from source: requires Node.js + Android SDK; clone the repository and run bash sync.sh ``` The architecture reference establishes that this APK receives and stores sensitive credentials: ```text 1. Login → input App ID / Secret / Base Token / Table ID → store in localStorage 2. init() → getToken() → fetchAll() → retrieve all detail records ``` ### Technical Analysis The URL points to a mutable `latest` release rather than a version-pinned immutable artifact. The Skill provides no expected SHA-256 digest, Android signing-certificate fingerprint, signature-verification procedure, or reproducible-build instructions that would let the user establish that the downloaded binary corresponds to reviewed source. The complete APK source described by `references/apk-architecture.md` is not present in the audited project directory. The audited package therefore cannot establish what the recommended binary actually executes. This is security-sensitive because the APK is expected to receive the Feishu App Secret and retrieve the user's complete accounting dataset. A malicious or compromised release could access these values while appearing to perform the declared dashboard function. This finding does not establish that the current GitHub APK is malicious. It establishes that the Skill's recommended installation procedure does not provide sufficient integrity or provenance controls for a credential-bearing application. ### Attack Path 1. An attacker compromises the repository release account, release workflow, signing key, ...[truncated 1198 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the mutable `latest` URL with an immutable, version-specific release URL. 2. Publish the expected SHA-256 digest for every APK and require verification before installation. 3. Publish the Android signing-certificate fingerprint and document how users can verify it. 4. Include the complete APK source in the reviewed repository or link it to an immutable commit. 5. Provide reproducible build instructions and compare locally built artifacts against published binaries. 6. Use a protected, auditable release workflow with signed tags and narrowly scoped publishing credentials. 7. Avoid entering tenant-level App Secrets into a mobile client. Prefer short-lived, constrained credentials. 8. Provide an authenticated update mechanism that verifies signatures before installing updates. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/cleanup_bitable.py:67
Finding
Bulk Cleanup Utility Irreversibly Deletes Remote Records Without Confirmation or Dry-Run Protection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cleanup_bitable.py:67-116` **Vulnerability Type**: Unsafe destructive operation **Risk Level**: Medium ### Vulnerable Code ```python def main(): parser = argparse.ArgumentParser(description="清空记账多维表格数据(单表版)") parser.add_argument("--app-id", required=True, help="飞书应用 App ID") parser.add_argument("--app-secret", required=True, help="飞书应用 App Secret") parser.add_argument("--base-token", required=True, help="多维表格 Base Token") parser.add_argument("--table-name", default="明细表", help="要清空的表名(默认:明细表)") args = parser.parse_args() print("🚀 开始清理记账数据...") token = get_tenant_token(args.app_id, args.app_secret) print("✅ 获取 Tenant Token 成功") tables = list_tables(token, args.base_token) target = None for t in tables: if t["name"] == args.table_name: target = t break if not target: print(f"❌ 未找到表「{args.table_name}」,现有表:{[t['name'] for t in tables]}") sys.exit(1) print(f"📋 目标表:{target['name']} ({target['id']})") total_deleted = 0 batch = 0 offset = 0 while True: batch += 1 data = list_records(token, args.base_token, target["id"], offset) records = data.get("data", []) record_ids = data.get("record_id_list", []) if not records: print("✅ 全部记录已清理完毕" if total_deleted > 0 else "ℹ️ 表中没有记录") break print(f" 第{batch}页 (offset={offset}): {len(records)} 条", end="") deleted = 0 for rec_id in record_ids: time.sleep(0.1) if delete_record(token, args.base_token, target["id"], rec_id): deleted += 1 else: print(" ⚠️ 删除失败") total_deleted += deleted print(f" → 删了 {deleted} 条") offset += len(records) print(f"\n✅ 总计清理 {total_deleted} 条记录") ``` ### Technical Analysis The utility selects the first table whose name exactly match ...[truncated 2044 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Default to a dry run that only prints the target Base, table ID, table name, and record count. 2. Require an explicit destructive flag such as `--delete-all`. 3. Require a second confirmation value tied to the target, such as: - `--confirm-base-token <token>` - `--confirm-table-id <id>` 4. For interactive use, require the operator to type the full table ID or a generated confirmation phrase. 5. Verify expected schema fields before considering the table eligible for cleanup. 6. Export all records to a timestamped backup before deletion. 7. Prefer batch deletion with an auditable operation identifier if the Feishu API supports it. 8. Log the Base Token in redacted form, table ID, record count, initiator, and timestamp without logging application secrets. 9. Abort when more than one table has the requested name rather than silently selecting the first match. 10. Document recovery procedures and retention expectations. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (41)

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

Critical
Category
Data Flow
Content
url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    data = json.dumps({"app_id": FEISHU_APP_ID, "app_secret": FEISHU_APP_SECRET}).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=15) as resp:
        result = json.loads(resp.read())
    if result.get("code") != 0:
        raise RuntimeError(f"获取 Tenant Token 失败: {result}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
"Authorization": f"Bearer {token}", "Content-Type": "application/json",
    }, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            result = json.loads(resp.read())
            if result.get("code") == 0:
                return {"success": True}
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
"Authorization": f"Bearer {token}", "Content-Type": "application/json",
    }, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            result = json.loads(resp.read())
            if result.get("code") == 0:
                return {"success": True}
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
url_fields = (f"https://open.feishu.cn/open-apis/base/v3/bases/{base}"
                      f"/tables/{tbl}/fields")
        req_fields = urllib.request.Request(url_fields, headers={"Authorization": f"Bearer {token}"})
        with urllib.request.urlopen(req_fields, timeout=15) as resp:
            fields_data = json.loads(resp.read())
        field_map = {}
        for f in fields_data.get("data", {}).get("fields", []):
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
f"/tables/{tbl}/records/{rid}")
        del_req = urllib.request.Request(del_url, method="DELETE",
                                         headers={"Authorization": f"Bearer {token}"})
        with urllib.request.urlopen(del_req, timeout=15) as resp:
            del_r = json.loads(resp.read())
        if del_r.get("code") == 0:
            return {"success": True, "record_id": rid}
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The overview claims a complete two-stage package and emphasizes a permanent-token synchronization model, but the supplied behavior centers on setup/admin actions and dynamic token retrieval. This inconsistency is security-relevant because it obscures actual privileged operations and can defeat informed consent during deployment.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The overview claims a complete two-stage package and emphasizes a permanent-token synchronization model, but the supplied behavior centers on setup/admin actions and dynamic token retrieval. This inconsistency is security-relevant because it obscures actual privileged operations and can defeat informed consent during deployment.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The overview claims a complete two-stage package and emphasizes a permanent-token synchronization model, but the supplied behavior centers on setup/admin actions and dynamic token retrieval. This inconsistency is security-relevant because it obscures actual privileged operations and can defeat informed consent during deployment.

Tp4

High
Category
MCP Tool Poisoning
Confidence
91% confidence
Finding
The overview claims a complete two-stage package and emphasizes a permanent-token synchronization model, but the supplied behavior centers on setup/admin actions and dynamic token retrieval. This inconsistency is security-relevant because it obscures actual privileged operations and can defeat informed consent during deployment.

Missing User Warnings

High
Confidence
98% confidence
Finding
The documentation directs plaintext handling of App Secret and other credentials without adequate warnings about sensitivity, storage risks, or least-privilege alternatives. This is dangerous because users may normalize insecure secret management for tokens that can authorize broad Feishu operations.

Ssd 3

High
Confidence
98% confidence
Finding
The skill creates a natural-language credential exposure workflow by telling the agent to restate user-supplied secrets and save them locally in plaintext. This is more dangerous than ordinary config handling because chat transcripts and agent logs become additional secret exposure surfaces.

Credential Access

High
Category
Privilege Escalation
Content
```bash
# 在技能目录创建 .env(record_bill.py 会自动从同目录读取)
cat > /path/to/feishu-accounting/.env << 'EOF'
FEISHU_APP_ID=你的App_ID
FEISHU_APP_SECRET=你的App_Secret
FEISHU_BASE_TOKEN=你的Base_Token
Confidence
99% confidence
Finding
The explicit instruction to create a .env file containing App ID, App Secret, and table tokens is direct credential access and storage in plaintext. If the host, repository, backups, or logs are exposed, these credentials can be reused to access or modify Feishu resources.

Vague Triggers

High
Confidence
97% confidence
Finding
The usage triggers are overly broad and allow normal phrases or even image upload alone to initiate bookkeeping actions. Because the skill can write local files and sync to Feishu, loose triggering materially increases the risk of unauthorized or accidental data modification.

Missing User Warnings

High
Confidence
98% confidence
Finding
The skill explicitly instructs automatic writes based on OCR/vision results from receipts without any confirmation warning. In financial software, this is especially risky because model mistakes directly alter user records and could be abused with crafted images to poison data.

Credential Access

High
Category
Privilege Escalation
Content
```python
# ❌ 错误:setdefault 不覆盖已存在的环境变量
# 如果父 shell 已有 FEISHU_APP_ID(旧应用的),.env 的值会被忽略
os.environ.setdefault(k.strip(), v.strip())

# ✅ 正确:直接赋值,.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
```python
# ❌ 错误:setdefault 不覆盖已存在的环境变量
# 如果父 shell 已有 FEISHU_APP_ID(旧应用的),.env 的值会被忽略
os.environ.setdefault(k.strip(), v.strip())

# ✅ 正确:直接赋值,.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
```python
# ❌ 错误:setdefault 不覆盖已存在的环境变量
# 如果父 shell 已有 FEISHU_APP_ID(旧应用的),.env 的值会被忽略
os.environ.setdefault(k.strip(), v.strip())

# ✅ 正确:直接赋值,.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
```python
# ❌ 错误:setdefault 不覆盖已存在的环境变量
# 如果父 shell 已有 FEISHU_APP_ID(旧应用的),.env 的值会被忽略
os.environ.setdefault(k.strip(), v.strip())

# ✅ 正确:直接赋值,.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
def get_tenant_token(app_id: str, app_secret: str) -> str:
    """获取 Tenant Access Token"""
    url = f"{FEISHU_HOST}/open-apis/auth/v3/tenant_access_token/internal"
    data = json.dumps({"app_id": app_id, "app_secret": app_secret}).encode("utf-8")
    req = urllib.request.Request(
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def get_tenant_token(app_id: str, app_secret: str) -> str:
    """获取 Tenant Access Token"""
    url = f"{FEISHU_HOST}/open-apis/auth/v3/tenant_access_token/internal"
    data = json.dumps({"app_id": app_id, "app_secret": app_secret}).encode("utf-8")
    req = urllib.request.Request(
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
def get_tenant_token(app_id: str, app_secret: str) -> str:
    """获取 Tenant Access Token"""
    url = f"{FEISHU_HOST}/open-apis/auth/v3/tenant_access_token/internal"
    data = json.dumps({"app_id": app_id, "app_secret": app_secret}).encode("utf-8")
    req = urllib.request.Request(
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
from datetime import date, datetime
from pathlib import Path

# ── 自动加载 skill 目录下的 .env ──────────────────────────────────────────
_SKILL_ENV = Path(__file__).parent.parent / ".env"
if _SKILL_ENV.exists():
    for line in _SKILL_ENV.read_text().splitlines():
Confidence
92% confidence
Finding
The script automatically loads secrets from a `.env` file located relative to the skill directory and injects them into the process environment without validating file ownership, permissions, or trust boundary. In an agent skill context, this increases the chance of credential misuse or unintended secret exposure, especially if the skill directory is shared, synced, or writable by other components.

Credential Access

High
Category
Privilege Escalation
Content
from pathlib import Path

# ── 自动加载 skill 目录下的 .env ──────────────────────────────────────────
_SKILL_ENV = Path(__file__).parent.parent / ".env"
if _SKILL_ENV.exists():
    for line in _SKILL_ENV.read_text().splitlines():
        line = line.strip()
Confidence
92% confidence
Finding
The `.env` path is hardcoded to the parent skill directory, encouraging local plaintext credential storage in a predictable location. Predictable, automatic secret loading makes it easier for other local processes, backups, or repository mistakes to expose Feishu credentials that grant remote data access.

Missing User Warnings

High
Confidence
98% confidence
Finding
The script prints the Feishu App Secret directly to stdout, which can leak into terminal scrollback, shell history capture, CI/CD logs, remote session transcripts, or agent telemetry. In an agent skill context, stdout is often collected and forwarded, making secret disclosure more dangerous than in an isolated local script.

Missing User Warnings

High
Confidence
99% confidence
Finding
The machine-readable JSON includes the App Secret, which enables downstream systems, logs, parsers, or other agents to ingest and potentially persist the credential automatically. Because this output is specifically structured for AI parsing, it materially increases the likelihood of silent credential propagation and compromise.

Static analysis

No suspicious patterns detected.