Back to skill

Security audit

feishu-task-management-skill

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to manage Feishu tasks as described, but it ships credential-bearing config and real member directory data and uses unsafe credential/OAuth handling.

Do not install this version into a normal environment unless the publisher first removes all shipped credentials and real member data, rotates the exposed Feishu app secret and tokens, restricts API destinations to official Feishu hosts, fixes the OAuth callback/state handling, and stores any required local secrets and member cache with owner-only permissions.

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

T09 · Insecure Skill Coding Practices

Error
Location
toolkit/config/runtime.json:2
Finding

Live Feishu application credentials and a user OAuth token are committed to the project

Content
View full analysis

Vulnerability Details

File Location: toolkit/config/runtime.json:2-9
Vulnerability Type: Hardcoded credentials and plaintext authentication tokens
Risk Level: Critical

Vulnerable Code

The credential values are redacted below to prevent further disclosure:

json
{
  "app_id": "[REDACTED_APP_ID]",
  "app_secret": "[REDACTED_APP_SECRET]",
  "base_url": "https://open.feishu.cn/open-apis",
  "default_member_open_id": "[REDACTED_OPEN_ID]",
  "refresh_token": "",
  "timezone": "Asia/Shanghai",
  "user_access_token": "[REDACTED_JWT_ACCESS_TOKEN]",
  "user_scope": "auth:user.id:read task:task:read task:task:write",
  "user_token_expires_at": "2026-03-10T09:53:23.253756+00:00"
}

Technical Analysis

A real-looking Feishu application ID, application secret, user access token, user identity, and granted OAuth scopes are stored directly in the distributed runtime configuration. Anyone who can read the project artifact or its source history can extract these credentials without executing the Skill.

The user token declares task read and write scopes. Its configured expiration date is in the past as of this audit, which reduces its present direct utility, but does not make publishing it acceptable. More importantly, the application secret has no local expiration metadata and can be exchanged for a tenant access token by the implementation:

python
payload = {"app_id": self.config.app_id, "app_secret": self.config.app_secret}
response = self.request(
    "POST",
    "/auth/v3/tenant_access_token/internal",
    body=payload,
    use_auth=False,
)

This behavior is necessary when authenticating to Feishu, but embedding usable credentials in the Skill package is not necessary and violates least-secret and secure-deployment principles.

Attack Path

  1. An attacker downloads the Skill artifact, reads the repository, or obtains a historical copy.
  2. The attacker extracts ` ...[truncated 1210 chars]
Remediation
View remediation

Remediation Suggestions

  1. Revoke and rotate the exposed Feishu application secret immediately.
  2. Revoke all exposed user and refresh tokens, even if they appear expired.
  3. Remove toolkit/config/runtime.json from the repository and purge it from source history and release artifacts.
  4. Add the runtime configuration and equivalent secret files to .gitignore.
  5. Ship only a credential-free example such as runtime.example.json.
  6. Prefer environment variables, an operating-system keychain, or a dedicated secret manager.
  7. Add automated secret scanning to CI and pre-commit checks.
  8. Review Feishu access logs for use of the exposed app ID and tokens.
  9. Reduce the application's configured Feishu permissions and authorized contact scope to the minimum required.

T09 · Insecure Skill Coding Practices

Error
Location
toolkit/src/feishu_task_toolkit/config.py:94
Finding

Attacker-controlled base URL can receive Feishu secrets and bearer tokens

Content
View full analysis

Vulnerability Details

File Location: toolkit/src/feishu_task_toolkit/config.py:94-106; toolkit/src/feishu_task_toolkit/http.py:28-33,68-88; toolkit/src/feishu_task_toolkit/cli_config.py:35-36,258-279
Vulnerability Type: Credential exfiltration through an unrestricted network destination
Risk Level: High

Vulnerable Code

python
base_url = (
    env.get("FEISHU_BASE_URL")
    or runtime.get("base_url")
    or cls.base_url
).strip() or cls.base_url

return cls(
    app_id=app_id,
    app_secret=app_secret,
    user_access_token=user_access_token,
    refresh_token=refresh_token,
    user_token_expires_at=user_token_expires_at,
    user_scope=user_scope,
    base_url=base_url.rstrip("/"),
    timezone=timezone,
    default_member_open_id=default_member_open_id,
)
python
def request(
    self,
    method: str,
    path: str,
    params: dict[str, Any] | None = None,
    body: dict[str, Any] | None = None,
    use_auth: bool = True,
) -> dict[str, Any]:
    url = f"{self.config.base_url}{path}"

    headers = {"Content-Type": "application/json; charset=utf-8"}
    if use_auth:
        headers["Authorization"] = (
            f"Bearer {self.get_auth_token_for_path(path)}"
        )

    data = None
    if body is not None:
        data = json.dumps(body, ensure_ascii=False).encode("utf-8")

    req = request.Request(
        url=url,
        data=data,
        method=method.upper(),
        headers=headers,
    )
    with request.urlopen(req) as response:
        payload = json.loads(response.read().decode("utf-8"))

Tenant-token acquisition sends the application secret through the same configurable destination:

python
payload = {
    "app_id": self.config.app_id,
    "app_secret": self.config.app_secret,
}
response = self.request(
    "POST",
    "/auth/v3/tenant_access_token/internal",
    body=payload,
 
...[truncated 2222 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove end-user configurability of base_url in production builds.
  2. Enforce https and an exact allowlist of official Feishu API hostnames.
  3. Reject URLs containing user information, fragments, non-default ports, or unexpected path prefixes.
  4. Resolve redirects manually or disable them for credential-bearing requests; never forward authorization headers across origins.
  5. Separate authentication endpoints from ordinary API endpoints so the application secret can only be sent to a hardcoded Feishu authentication origin.
  6. If test endpoint substitution is needed, expose it only through dependency injection in tests, not production environment variables or persisted configuration.
  7. Add tests proving that HTTP URLs, loopback addresses, private network addresses, and unapproved domains are rejected.

T09 · Insecure Skill Coding Practices

Error
Location
toolkit/src/feishu_task_toolkit/cli_config.py:14
Finding

OAuth authorization codes are redirected to a third-party placeholder and callbacks without state are accepted

Content
View full analysis

Vulnerability Details

File Location: toolkit/src/feishu_task_toolkit/cli_config.py:14,71-85,151-177
Vulnerability Type: Insecure OAuth redirect and incomplete state validation
Risk Level: High

Vulnerable Code

python
DEFAULT_REDIRECT_URI = "https://example.com/feishu/oauth/callback"
python
def parse_oauth_callback_input(raw: str, expected_state: str) -> str:
    value = raw.strip()
    if not value:
        return ""
    if "://" not in value:
        raise ValueError(
            "OAuth callback input must be the full callback URL"
        )
    parsed = urlparse(value)
    query = parse_qs(parsed.query)
    code = (query.get("code") or [""])[0]
    state = (query.get("state") or [""])[0]
    if expected_state and state and state != expected_state:
        raise ValueError("OAuth callback state mismatch")
    if not code:
        raise ValueError("OAuth callback does not contain code")
    return code
python
state = state_fn()
authorize_url = build_authorize_url(
    client_id=app_id,
    redirect_uri=redirect_uri,
    scope=scope,
    state=state,
)
print_fn(f"Authorization URL: {authorize_url}")
callback_input = input_fn(
    "Enter the complete callback URL: "
).strip()
authorization_code = (
    parse_oauth_callback_input(
        callback_input,
        expected_state=state,
    )
    if callback_input
    else ""
)

if authorization_code:
    token_payload = exchange_code_fn(
        app_id,
        app_secret,
        authorization_code,
        redirect_uri,
        "",
    )

Technical Analysis

The configuration wizard uses example.com as its default OAuth redirect URI and directs users to register that URI in the Feishu developer console. Authorization servers deliver the authorization code to the redirect origin before the user can copy the resulting URL. The code is therefore transmitte ...[truncated 2102 chars]

Remediation
View remediation

Remediation Suggestions

  1. Replace the third-party placeholder with a loopback redirect URI handled by a temporary local callback server, following OAuth native-application guidance.
  2. Alternatively, use a verified HTTPS callback endpoint controlled by the application owner.
  3. Never instruct users to register a domain not controlled by them.
  4. Require the returned state to exist and match exactly:
    python
    if not expected_state or not secrets.compare_digest(state, expected_state):
        raise ValueError("OAuth callback state mismatch")
    
  5. Implement PKCE with a cryptographically random verifier and an S256 challenge, and pass the verifier during token exchange.
  6. Validate the callback URL's scheme, hostname, port, and path against the exact configured redirect URI before extracting parameters.
  7. Avoid printing full authorization URLs where logs may retain state values.
  8. Add tests for missing state, duplicate parameters, wrong callback origins, replayed codes, and malformed URLs.

T09 · Insecure Skill Coding Practices

Warning
Location
toolkit/src/feishu_task_toolkit/config.py:137
Finding

Runtime secrets are written without enforcing restrictive file permissions and can be supplied through process-visible arguments

Content
View full analysis

Vulnerability Details

File Location: toolkit/src/feishu_task_toolkit/config.py:137-139; toolkit/src/feishu_task_toolkit/cli_config.py:28-35,250-279; SKILL.md:47-50
Vulnerability Type: Insecure local secret storage and command-line credential exposure
Risk Level: Medium

Vulnerable Code

python
def write_json(path: Path, payload: Any) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        json.dumps(
            payload,
            ensure_ascii=False,
            indent=2,
            sort_keys=True,
        ) + "\n",
        encoding="utf-8",
    )
python
set_parser = subparsers.add_parser("set")
set_parser.add_argument("--app-id", required=True)
set_parser.add_argument("--app-secret", required=True)
set_parser.add_argument("--user-access-token", default=None)
set_parser.add_argument("--refresh-token", default=None)
python
payload = {
    "app_id": args.app_id,
    "app_secret": args.app_secret,
    "user_access_token": (
        args.user_access_token
        if args.user_access_token is not None
        else (existing.user_access_token if existing else "")
    ),
    "refresh_token": (
        args.refresh_token
        if args.refresh_token is not None
        else (existing.refresh_token if existing else "")
    ),
    # Additional configuration omitted.
}
write_json(paths.runtime_config_file, payload)

The Skill documentation explicitly recommends passing the secret as an argument:

text
python3 feishu-task-management/toolkit/scripts/feishu_config.py set \
  --app-id ... --app-secret ...

Technical Analysis

Path.write_text() creates or replaces the runtime credential file using permissions derived from the process umask. The code does not explicitly require owner-only access, verify ownership, reject symbolic links, or atomically replace the file.

On a system wit ...[truncated 1604 chars]

Remediation
View remediation

Remediation Suggestions

  1. Store secrets outside the project directory in a platform-appropriate secret manager or keychain.
  2. If a file is unavoidable, create it atomically with mode 0600 and verify owner-only permissions after creation.
  3. Ensure the containing directory is owner-only, normally mode 0700.
  4. Reject symbolic links and verify that the target is a regular file owned by the current user.
  5. Use a temporary file in the same directory, set its permissions before writing, flush it, and atomically replace the destination.
  6. Remove secret-bearing CLI options. Read secrets through getpass, standard input from a protected source, environment injection by a secret manager, or an operating-system credential store.
  7. Update SKILL.md so examples never place credentials in command-line arguments.
  8. Document local permission requirements and add tests that assert owner-only modes.

T09 · Insecure Skill Coding Practices

Warning
Location
toolkit/data/feishu_members.json:1
Finding

Real Feishu member directory data is distributed in the Skill artifact

Content
View full analysis

Vulnerability Details

File Location: toolkit/data/feishu_members.json:1-63
Vulnerability Type: Committed personal and organizational directory data
Risk Level: Medium

Vulnerable Code

Sensitive values are redacted below to avoid repeating personal data:

json
{
  "generated_at": "2026-03-10T07:53:36.309441+00:00",
  "member_count": 4,
  "members": [
    {
      "department_ids": ["[REDACTED_DEPARTMENT_ID]"],
      "departments": [],
      "email": "",
      "en_name": "",
      "mobile": "",
      "name": "[REDACTED_PERSON_NAME]",
      "nickname": "",
      "open_id": "[REDACTED_FEISHU_OPEN_ID]",
      "status": "active",
      "user_id": "[REDACTED_USER_ID]"
    }
  ],
  "version": 1
}

The actual file contains four named individuals together with Feishu open IDs, internal user IDs, and a shared department identifier.

Technical Analysis

Member synchronization is a declared feature, and retrieving directory records from Feishu is necessary for local name resolution. Distributing a populated synchronized directory as part of the Skill package is not necessary.

The file reveals personal names and stable organizational identifiers. Stable identifiers can facilitate targeted phishing, employee enumeration, cross-artifact correlation, and unauthorized member targeting if combined with compromised credentials.

The implementation also stores all available email and mobile fields returned by Feishu:

python
return {
    "open_id": record.get("open_id", ""),
    "user_id": record.get("user_id", ""),
    "name": record.get("name", ""),
    "en_name": record.get("en_name", ""),
    "nickname": record.get("nickname", ""),
    "email": record.get("email", ""),
    "mobile": record.get("mobile", ""),
    "department_ids": _dedupe(list(department_ids)),
    "departments": _dedupe(list(departments)),
    "status": record.get("status", "active"),
}
`
...[truncated 1187 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the populated member directory and sync metadata from source control and distributed packages.
  2. Replace them with empty templates containing no real identities.
  3. Add synchronized data files to .gitignore and packaging exclusions.
  4. Purge historical copies from repository history and prior release artifacts where feasible.
  5. Store local member data in a user-specific protected data directory rather than inside the Skill installation.
  6. Enforce owner-only permissions on synchronized data.
  7. Minimize stored fields to those required for resolution, preferably open ID plus a minimal display label.
  8. Avoid storing mobile numbers and email addresses unless an explicit requirement justifies them.
  9. Define retention and deletion behavior for stale member records.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The declared description centers on Feishu task management, with member resolution described as a supporting feature for task APIs. However, the supplied code chunk only exposes member-related commands: sync, stats, resolve, and validate-aliases. It loads app config and an HTTP client to sync member data, reads local member/sync metadata files, resolves member queries via a local directory, and validates alias mappings. There is no evidence here of any task API operations such as create, inspect, update, complete, reopen, delete, or modifying task members. Because the code’s actual behavior is a member-directory utility rather than a task-management toolkit, the description does not accurately represent this chunk.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · references/api-alignment.md (reported line 16)May include surrounding context.

md
- `GET /task/v2/tasks/{task_guid}`
- `GET /task/v2/tasks`
- `PATCH /task/v2/tasks/{task_guid}`
- `DELETE /task/v2/tasks/{task_guid}`
- `POST /task/v2/tasks/{task_guid}/add_members`
- `POST /task/v2/tasks/{task_guid}/remove_members`

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · toolkit/src/feishu_task_toolkit/config.py (reported line 84)May include surrounding context.

python
@classmethod
    def load(cls, paths: ToolkitPaths, environ: dict[str, str] | None = None) -> "AppConfig":
        env = environ if environ is not None else dict(os.environ)
        runtime = load_json(paths.runtime_config_file, {})
        app_id = (env.get("FEISHU_APP_ID") or runtime.get("app_id") or "").strip()
        app_secret = (env.get("FEISHU_APP_SECRET") or runtime.get("app_secret") or "").strip()

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill instructs use of local Python scripts that inherently read configuration, access local files, and call Feishu APIs, yet the manifest does not declare any explicit tool scope or permissions boundary. This makes the skill's effective capabilities opaque to reviewers and increases the chance of over-privileged execution or unintended access to secrets, filesystem contents, and networked resources.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The configuration guidance tells users to store app secrets and optional OAuth user tokens locally but does not warn about the sensitivity of those credentials or recommend secure storage practices. If stored insecurely, these values can be recovered from shell history, config files, logs, or shared workstations and then used to access Feishu data or act as a user.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The runtime configuration sets the timezone to "Asia/Shanghai", which imposes a locale-specific behavior in a globally scoped config file. Under the policy, locale constraints should either be user-selectable or clearly documented as region-specific and justified; neither is evident in this file.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The user-facing prompts and status messages in the interactive guide are entirely in Chinese, including required input instructions and confirmations. This imposes a specific language on all users without opt-in or indication that the tool is intentionally limited to a Chinese-speaking audience.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The guidance text instructs users to write FEISHU_APP_SECRET and optional OAuth tokens to the runtime configuration file. This is a safety-relevant handling of credentials, but the message provides no warning about storing secrets on disk or protecting that file.

Content

No source excerpt is available for this finding.

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · toolkit/src/feishu_task_toolkit/http.py (reported line 33)May include surrounding context.

python
"POST",
            "/auth/v3/tenant_access_token/internal",
            body=payload,
            use_auth=False,
        )
        token = response.get("tenant_access_token")
        if not token:

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · toolkit/src/feishu_task_toolkit/http.py (reported line 62)May include surrounding context.

python
"POST",
            "/auth/v3/tenant_access_token/internal",
            body=payload,
            use_auth=False,
        )
        token = response.get("tenant_access_token")
        if not token:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The sync_members function persists a member directory to local disk, including personal data fields such as names, emails, mobile numbers, department mappings, and identifiers, without any consent prompt, disclosure mechanism, retention control, or minimization in this file. In the context of a task-management skill that explicitly maintains a locally synced member table for later identity resolution, this creates a real privacy and data-handling risk if the host environment is shared, compromised, or logs/backups expose the files.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
86% confidence
Finding

This JSON file stores member names exclusively in Chinese characters while the corresponding English-name fields are blank, effectively enforcing a single locale in the natural-language content. Under the language/locale policy rule, this is a potential violation because the file provides no opt-in, alternative locale, or documented regional justification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The helper creates directories and writes JSON content to a file, and in this module it is plausibly used for runtime configuration and token-related data. There is no confirmation prompt, warning comment, or visible disclosure near the write path about persisting potentially sensitive data.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
91% confidence
Finding

The origin payload always uses the zh_cn locale key for platform_i18n_name, which imposes a specific language/locale in a natural-language field. There is no visible opt-in, fallback, or documented justification in this file for restricting the locale to Chinese.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.