Back to skill

Security audit

Ticktick Cli

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a real TickTick task-management integration, but it stores and uses powerful TickTick credentials and session cookies in ways that need careful review before installation.

Install only if you are comfortable granting this skill read/write access to your TickTick tasks and projects. Treat the config file and sessionCookie as sensitive account credentials, avoid pasting real secrets into command lines where logs or shell history may retain them, and manually confirm task IDs before completion, abandonment, batch operations, or attachment uploads.

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

Warning
Location
ticktick/cli.py:67
Finding
OAuth Client Secret Exposed Through Command-Line Arguments## Vulnerability Details **File Location**: `ticktick/cli.py:29-31`, `ticktick/cli.py:67-68`, `SKILL.md:53-59` **Vulnerability Type**: Sensitive information exposure through process arguments **Risk Level**: Medium ### Vulnerable Code ```python if args.client_id and args.client_secret: setup_credentials(args.client_id, args.client_secret) ``` ```python auth_p.add_argument("--client-id", dest="client_id", metavar="<id>", help="TickTick OAuth client ID") auth_p.add_argument("--client-secret", dest="client_secret", metavar="<secret>", help="TickTick OAuth client secret") ``` The documented workflow explicitly instructs users to supply the secret in the command line: ```bash python -m ticktick.cli auth --client-id <YOUR_CLIENT_ID> --client-secret <YOUR_CLIENT_SECRET> python -m ticktick.cli auth --client-id <ID> --client-secret <SECRET> --manual ``` ### Technical Analysis Command-line arguments are not an appropriate transport for confidential values. Depending on the operating system and environment, arguments may be exposed through: - Shell history files. - Process-listing tools while the command is running. - Process accounting or endpoint telemetry. - Terminal session recording. - Automation logs, CI output, or command wrappers. - Agent execution traces that retain the complete command. The client secret is legitimately required for this OAuth client, but accepting it exclusively through an ordinary `argparse` option unnecessarily expands its exposure. The issue is particularly relevant for an Agent Skill because command invocations may be logged or retained outside the CLI itself. ### Attack Path 1. A user or Agent follows the authentication instructions in `SKILL.md`. 2. The OAuth client secret is included literally in the command's argument vector. 3. The command is retained in shell history, execution telemetry, process accounting, or an Agent log. ...[truncated 908 chars]
Remediation
## Remediation Suggestions - Replace the ordinary `--client-secret` argument with an interactive hidden prompt using `getpass.getpass()`. - If noninteractive operation is required, accept the secret through a protected file descriptor, an operating-system keyring, or a narrowly scoped secret-manager integration. - Avoid environment variables as the default because they may also be exposed through process environments, crash reports, or automation logs. - If backward compatibility requires retaining `--client-secret`, mark it as deprecated and display a warning that it may expose the value through process metadata and command history. - Update `README.md` and `SKILL.md` so examples never place real secrets directly in command lines. - Advise existing users to remove commands containing secrets from shell history and rotate secrets if they may have been logged. - Ensure Agent integrations redact secret-bearing arguments from invocation logs and traces.

T09 · Insecure Skill Coding Practices

Warning
Location
ticktick/auth.py:29
Finding
Plaintext Credential Bundle Uses Fail-Open Permission Hardening## Vulnerability Details **File Location**: `ticktick/auth.py:15-16`, `ticktick/auth.py:29-42`, `ticktick/auth.py:90-96`, `ticktick/commands/attach.py:13-20` **Vulnerability Type**: Insecure storage of sensitive authentication material **Risk Level**: Medium ### Vulnerable Code ```python CONFIG_DIR = Path.home() / ".clawdbot" / "credentials" / "ticktick-cli" CONFIG_FILE = CONFIG_DIR / "config.json" ``` ```python def save_config(config: dict) -> None: CONFIG_DIR.mkdir(parents=True, exist_ok=True) CONFIG_FILE.write_text(json.dumps(config, indent=2)) try: os.chmod(CONFIG_DIR, 0o700) os.chmod(CONFIG_FILE, 0o600) except OSError: pass ``` ```python def setup_credentials(client_id: str, client_secret: str) -> None: config = load_config() or {} config["clientId"] = client_id config["clientSecret"] = client_secret config["redirectUri"] = DEFAULT_REDIRECT_URI save_config(config) print("Credentials saved successfully.") ``` ```python def _get_session_config() -> tuple[str, str]: config = load_config() if not config or not config.get("sessionCookie"): raise RuntimeError( "sessionCookie not found in config. " "Add it to ~/.clawdbot/credentials/ticktick-cli/config.json" ) return config["sessionCookie"], config.get("v2DeviceId", "clawagent00000000000001") ``` The same JSON configuration can contain the OAuth client secret, access token, refresh token, and TickTick browser session cookie. ### Technical Analysis `save_config()` serializes all configuration fields as plaintext. The file is written before restrictive permissions are explicitly applied. Its initial permissions therefore depend on the current process umask and filesystem behavior. The subsequent `chmod` operations are wrapped in a broad `except OSError` block that silently ignores all failures. The program can co ...[truncated 2616 chars]
Remediation
## Remediation Suggestions - Store the client secret, refresh token, access token, and session cookie in an operating-system keyring or dedicated secret manager. - If file storage is unavoidable, create the file with mode `0600` from the outset using low-level atomic file creation rather than writing first and changing permissions afterward. - Create and verify the credential directory with mode `0700`. - Write updates to a securely created temporary file in the same protected directory, flush and synchronize it, then atomically replace the destination. - Reject symbolic links and verify that the destination is a regular file owned by the current user. - Verify final directory and file modes after creation. - Treat any failure to establish ownership or permissions as a fatal error; do not silently catch and ignore `OSError`. - Store the browser session cookie separately from OAuth client credentials and tokens to reduce the impact of a single-file compromise. - Remove the session cookie automatically when attachment functionality is disabled or the user logs out, or provide a dedicated command to clear it. - Avoid printing credential values in diagnostics and redact server responses where they could contain authentication details. - Document the plaintext-storage risk and advise users to rotate exposed tokens or cookies if file permissions were ever incorrect.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (21)

Credential Access

High
Category
Privilege Escalation
Content
if not config:
        raise RuntimeError("Not authenticated. Run 'ticktick auth' to set up credentials.")
    if not config.get("accessToken"):
        raise RuntimeError("No access token found. Run 'ticktick auth' to authenticate.")

    # Check expiry with 5-minute buffer (timestamps stored as JS milliseconds)
    expiry = config.get("tokenExpiry")
Confidence
83% confidence
Finding
This module stores and retrieves long-lived OAuth secrets, including access tokens, refresh tokens, and the client secret, from a plaintext JSON file in the user's home directory. Although file permissions are tightened best-effort, the secrets remain recoverable by local compromise, backups, malware running as the same user, or misconfigured filesystems, which could allow unauthorized access to the user's TickTick account.

Tainted flow: 'file_bytes' from pathlib.Path.read_bytes (line 51, file read) → requests.post (network output)

High
Category
Data Flow
Content
x_device = json.dumps({"platform": "web", "version": 6430, "id": v2_device_id})

        # Do NOT set Content-Type — requests sets it with the multipart boundary automatically
        resp = requests.post(
            url,
            headers={
                "Cookie": f"t={session_cookie}",
Confidence
80% confidence
Finding
File contents flow to a network sink. This may indicate data exfiltration of sensitive files.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This markdown file describes abandoning tasks, including bulk abandonment, which can alter or remove task state in a potentially irreversible way. The surrounding documentation gives usage examples but does not warn users about the destructive nature of the operation or advise caution before running it.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The README instructs users to extract a live browser session cookie and place it into a local config file without warning that this credential can grant account access if stolen. Encouraging manual handling of session tokens increases the risk of credential leakage through shell history, screenshots, backups, or insecure file permissions, especially because session cookies are often equivalent to active authentication.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill documents capabilities that imply shell execution, filesystem access, and network use, but it declares no explicit tool scope or permission boundaries. In an agent environment, this increases the chance the skill will be invoked with broader authority than necessary, enabling unintended command execution, local file access, or credential handling beyond the user’s expectations.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger keywords are very broad and overlap with normal task-management language in both English and Chinese, making accidental activation more likely. In a skill that can authenticate, modify tasks, upload attachments, and perform destructive actions, overbroad routing can cause the wrong skill to handle user intents and execute unintended state-changing operations.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill advertises `abandon` and `batch-abandon` operations without a prominent warning that they are destructive and may be hard to reverse. In an agent setting, this can lead to accidental bulk task state changes from ambiguous prompts or mis-selection of tasks, causing loss of workflow integrity and user trust.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The attachment workflow instructs use of a web session cookie (`t`) and device identifier outside normal OAuth, but it does not clearly warn that these are sensitive session secrets that can grant account access if exposed. Combining file upload, cookie handling, and plaintext config storage materially raises the risk of credential leakage, account compromise, and privacy exposure of attached files.

External Transmission

Medium
Category
Data Exfiltration
Content
from .auth import get_valid_token

API_BASE = "https://api.dida365.com/open/v1"

PRIORITY_MAP = {"none": 0, "low": 1, "medium": 3, "high": 5}
PRIORITY_REVERSE = {0: "none", 1: "low", 3: "medium", 5: "high"}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from .auth import get_valid_token

API_BASE = "https://api.dida365.com/open/v1"

PRIORITY_MAP = {"none": 0, "low": 1, "medium": 3, "high": 5}
PRIORITY_REVERSE = {0: "none", 1: "low", 3: "medium", 5: "high"}
Confidence
60% 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
84% confidence
Finding
The delete_task method performs an irreversible DELETE request against the remote task service, but this function contains no confirmation prompt, warning, or explanatory comment indicating the destructive effect. In a code file, destructive operations should have some visible disclosure unless the warning is provided elsewhere as part of the skill's stated behavior.

Tainted flow: 'auth_code' from input (line 253, user input) → requests.post (network output)

Medium
Category
Data Flow
Content
).decode()
    redirect_uri = config.get("redirectUri", DEFAULT_REDIRECT_URI)

    resp = requests.post(
        f"{OAUTH_BASE}/token",
        headers={
            "Authorization": f"Basic {credentials}",
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
90% confidence
Finding
The command reads a session cookie from a local credentials file and uses it to authenticate requests, but this file provides no user-facing warning, prompt, or explanatory comment that a stored credential will be accessed. For code-file warning checks, access to sensitive environment variables or credentials should have some visible disclosure unless clearly covered elsewhere.

External Transmission

Medium
Category
Data Exfiltration
Content
file_name = file_path.name
        file_bytes = file_path.read_bytes()

        url = f"https://api.ticktick.com/api/v1/attachment/upload/{project_id}/{task['id']}/{attachment_id}"
        x_device = json.dumps({"platform": "web", "version": 6430, "id": v2_device_id})

        # Do NOT set Content-Type — requests sets it with the multipart boundary automatically
Confidence
60% 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
95% confidence
Finding
This code transmits the contents of a local file to the TickTick API using a multipart HTTP POST, but there is no confirmation prompt or prior user-facing disclosure in the file that local file data will be sent to a remote service. Although attachment behavior is implied by the command name, the code itself lacks an explicit warning or explanatory comment about the network transmission of user data.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The call to complete_task marks a task as completed, which changes user data and may be hard to notice or reverse in this command path. The file provides only a success message after the action, but no confirmation prompt, pre-action warning, or explanatory comment/docstring disclosing the state-changing behavior.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The file presents all user-facing instructions in Chinese, which effectively enforces a single language for this skill's documentation. Under the policy, language constraints should be optional, user-selectable, or explicitly justified if region-specific.

Unverifiable Dependency: setuptools has 10 known advisory(ies) (CVE-2013-1633 (Setuptools vulnerable to Man-in-the-middle attacks); CVE-2025-47273 (setuptools has a path traversal vulnerability in PackageIndex.download that lead); CVE-2024-6345 (setuptools vulnerable to Command Injection via package URL) +7 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: wheel has 4 known advisory(ies) (CVE-2026-24049 (Wheel Affected by Arbitrary File Permission Modification via Path Traversal in w); CVE-2022-40898 (pypa/wheel vulnerable to Regular Expression denial of service (ReDoS)); CVE-2022-40898 (An issue discovered in Python Packaging Authority (PyPA) Wheel 0.37.1 and earlie) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: pytest has 2 known advisory(ies) (CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Static analysis

No suspicious patterns detected.