Back to skill

Security audit

ticktick-official-cli

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Dida365 task CLI, but it handles OAuth credentials and destructive account changes with weak safeguards that users should review before installing.

Review this before installing if your Dida365 account contains sensitive tasks or projects. Use only the official API base URL, avoid exposing command output in shared logs, restrict permissions on ~/.config/ticktick-official, revoke tokens if they appear in transcripts, and require explicit user approval before delete commands.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (5)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/ticktick_cli.py:232
Finding

OAuth Bearer Token Can Be Redirected to an Arbitrary Server

Content
View full analysis
dict[str, str]: """Build request headers.""" return { "Authorization": f"Bearer {self.config.token}", "Accept": "application/json", "User-Agent": self.config.user_agent, } def _url(self, path: str) -> str: """Build the complete request URL.""" base_url = str(self.config.base_url) return f"{base_url.rstrip('/')}/{path.lstrip('/')}" def _request( self, method: str, path: str, params: dict[str, str] | None = None, payload: dict[str, Any] | list[Any] | None = None, ) -> httpx.Response: """Send an HTTP request.""" return self.session.request( method=method.upper(), url=self._url(path), params=params, json=payload, headers=self._headers(), timeout=self.config.timeout_seconds, ) ``` ### Technical Analysis The CLI allows the API base URL to be controlled through either `--base-url` or the `TICKTICK_BASE_URL` environment variable. The resulting URL is used for authenticated requests without restricting the destination to the official Dida365 API. Although the base URL is validated as an HTTP URL by Pydantic, that validation does not enforce the official hostname and permits unencrypted HTTP. The client consequently sends the OAuth bearer token to any destination supplied through the option or environment. This exceeds the minimum privileges required by the declared functionality. The Skill states that it connects directly to official Dida365 services, so forwarding authentication to arbitrary origins is un ...[truncated 921 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/ticktick_oauth.py:54
Finding

OAuth Client Secret and Access Token Are Stored Without Enforced Restrictive Permissions

Content
View full analysis
None: path.parent.mkdir(parents=True, exist_ok=True) content = ( "# Generated by ticktick_oauth.py\n" f'export {ENV_CLIENT_ID}="{client_id}"\n' f'export {ENV_CLIENT_SECRET}="{client_secret}"\n' f'export {ENV_REDIRECT_URI}="{redirect_uri}"\n' ) path.write_text(content, encoding="utf-8") def save_token(access_token: str, path: Path = TOKEN_ENV_FILE) -> None: path.parent.mkdir(parents=True, exist_ok=True) content = ( "# Generated by ticktick_oauth.py\n" "# Source: official Dida365 OAuth\n" f'export TICKTICK_TOKEN="{access_token}"\n' ) path.write_text(content, encoding="utf-8") ``` ### Technical Analysis The application client secret and OAuth access token are stored as plaintext shell assignments. The files are written with `Path.write_text()` without explicitly creating them with mode `0600` or correcting permissions on pre-existing files. Their effective permissions therefore depend on the process umask and any permissions already assigned to the files. A permissive environment may produce credentials readable by other local accounts or processes. Plaintext storage may be necessary for a basic CLI, but unrestricted file permissions are not. The implementation does not apply the minimum local access controls expected for reusable OAuth credentials. ### Attack Path 1. A user runs `setup`, `exchange`, or `login`. 2. The script creates or replaces `app.env` or `token.env`. 3. A permissive umask or insecure pre-existing file mode leaves the credential file readable by unauthorized local principals. 4. Another local user or compromised process reads the client secret or be ...[truncated 526 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/ticktick_oauth.py:173
Finding

OAuth Access Tokens Are Printed to Standard Output

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/ticktick_oauth.py:2
Finding

Runtime Dependencies Are Not Reproducibly Pinned

Content
View full analysis
=3.10" # dependencies = [ # "httpx>=0.28.1", # "typer>=0.20.1", # "rich>=14.2.0", # ] # /// ``` The CLI similarly declares: ```python #!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.14" # dependencies = [ # "httpx>=0.28.1", # "typer>=0.20.1", # "pydantic>=2.12.5", # "rich>=14.2.0", # ] # /// ``` ### Technical Analysis The executable scripts use `uv run --script` with lower-bound-only dependency constraints. Future versions satisfying these ranges may therefore be downloaded and executed even though those versions were not part of the audited artifact. No malicious dependency or unsafe package source was identified in the reviewed code. The risk arises from non-reproducible future resolution: a compromised upstream release, account takeover, or incompatible update could alter behavior at execution time. Because imported Python packages execute initialization code in the Skill's process, dependency compromise can grant code execution with the same local privileges as the Skill. ### Attack Path 1. A future package release satisfying a `>=` constraint is published or compromised. 2. The Skill runs in an environment where the dependency is not already cached and trusted. 3. `uv` resolves and downloads the newer package. 4. Python imports the package as part of normal Skill startup. 5. Malicious package initialization code executes with the user's or Agent's privileges. ### Impact Assessment A compromised dependency could access local files available to the Skill, including its plaintext OAuth configuration and token files. It could also make network requests, alter API operations, or execute arbitrary cod ...[truncated 140 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ticktick_cli.py:357
Finding

Destructive Project and Task Deletions Lack an Enforced Confirmation Gate

Content
View full analysis
None: client = get_client(ctx) client.delete_project(project_id) console.print("OK") ``` ```python @task_app.command("delete", help="Delete a task.") def task_delete( ctx: typer.Context, project_id: str = typer.Option(..., "--project-id"), task_id: str = typer.Option(..., "--task-id"), ) -> None: client = get_client(ctx) client.delete_task(project_id, task_id) console.print("OK") ``` ### Technical Analysis `SKILL.md` states that project and task deletion is dangerous and should be confirmed before execution. The executable interface does not enforce that requirement. Supplying identifiers immediately results in an authenticated DELETE request. A documentation-only instruction is weaker than a code-level safety boundary, particularly because the CLI is intended primarily for AI Agent invocation. Erroneous argument generation, prompt manipulation, or ambiguous user requests can directly cause destructive operations. ### Attack Path 1. An Agent receives an ambiguous, erroneous, or manipulated request involving a project or task. 2. The Agent invokes `project delete` or `task delete` with the selected identifiers. 3. The CLI performs no interactive confirmation and requires no explicit force flag. 4. The authenticated API request deletes the target data. 5. The CLI prints `OK`, after the destructive action has already occurred. ### Impact Assessment An attacker or mistaken Agent invocation can delete individual tasks or entire projects available to the authenticated account. Project deletion may affect multiple tasks and associa ...[truncated 175 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
Findings (24)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description emphasizes official Dida365 task/project management, but the actual behavior includes local credential setup, browser-based OAuth, localhost callback handling, and token persistence that are not transparently disclosed as primary capabilities. This mismatch is dangerous because reviewers and users may approve the skill for routine task management without realizing it also handles secrets, opens a local listener, and stores long-lived tokens on disk.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/dida365-openapi.md (reported line 13)May include surrounding context.

md
## [Authorization](#/openapi?id=authorization)

### [Get Access Token](#/openapi?id=get-access-token)

In order to call Dida365's Open API, it is necessary to obtain an access token for the corresponding user. Dida365 uses the OAuth2 protocol to obtain the access token.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/dida365-openapi.md (reported line 15)May include surrounding context.

md
### [Get Access Token](#/openapi?id=get-access-token)

In order to call Dida365's Open API, it is necessary to obtain an access token for the corresponding user. Dida365 uses the OAuth2 protocol to obtain the access token.

#### [First Step](#/openapi?id=first-step)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/ticktick_api_client.py (reported line 22)May include surrounding context.

python
### [Get Access Token](#/openapi?id=get-access-token)

In order to call Dida365's Open API, it is necessary to obtain an access token for the corresponding user. Dida365 uses the OAuth2 protocol to obtain the access token.

#### [First Step](#/openapi?id=first-step)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/ticktick_cli.py (reported line 90)May include surrounding context.

python
### [Get Access Token](#/openapi?id=get-access-token)

In order to call Dida365's Open API, it is necessary to obtain an access token for the corresponding user. Dida365 uses the OAuth2 protocol to obtain the access token.

#### [First Step](#/openapi?id=first-step)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/dida365-openapi.md (reported line 37)May include surrounding context.

md
| Name | Description |
| --- | --- |
| code | Authorization code for subsequent access tokens |
| state | state parameter passed in the first step |

#### [Third Step](#/openapi?id=third-step)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/dida365-openapi.md (reported line 42)May include surrounding context.

md
#### [Third Step](#/openapi?id=third-step)

To exchange the authorization code for an access token, make a POST request to https://dida365.com/oauth/token with the following parameters(Content-Type: application/x-www-form-urlencoded):

| Name | Description |
| --- | --- |

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/dida365-openapi.md (reported line 58)May include surrounding context.

md
#### [Third Step](#/openapi?id=third-step)

To exchange the authorization code for an access token, make a POST request to https://dida365.com/oauth/token with the following parameters(Content-Type: application/x-www-form-urlencoded):

| Name | Description |
| --- | --- |

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · references/dida365-openapi.md (reported line 65)May include surrounding context.

md
#### [Third Step](#/openapi?id=third-step)

To exchange the authorization code for an access token, make a POST request to https://dida365.com/oauth/token with the following parameters(Content-Type: application/x-www-form-urlencoded):

| Name | Description |
| --- | --- |

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/dida365-openapi.md (reported line 363)May include surrounding context.

Delete Task

text
DELETE /open/v1/project/{projectId}/task/{taskId}
Parameters

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/dida365-openapi.md (reported line 388)May include surrounding context.

Request
text
DELETE /open/v1/project/{{projectId}}/task/{{taskId}} HTTP/1.1
Host: api.dida365.com
Authorization: Bearer {{token}}

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/dida365-openapi.md (reported line 680)May include surrounding context.

Delete Project

text
DELETE /open/v1/project/{projectId}
Parameters

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/dida365-openapi.md (reported line 703)May include surrounding context.

Request
text
DELETE /open/v1/project/{{projectId}} HTTP/1.1
Host: api.dida365.com
Authorization: Bearer {{token}}

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill invokes scripts that perform local file reads/writes and networked OAuth/API operations, but it declares no explicit tool scope or permissions. That omission weakens reviewability and consent boundaries, making it easier for an agent or user to trigger sensitive actions like token persistence and outbound connections without clear upfront disclosure.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill description is written to be used in Chinese and explicitly targets Dida365, with no indication that users may choose another language or locale. The policy requires flagging language or locale constraints when they are imposed without user opt-in or clear justification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This markdown file describes a DELETE endpoint that removes a task, but it does not include any warning that the action affects user data or may be irreversible. Under the markdown-specific warning criteria, destructive behaviors should be clearly disclosed to users.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The Delete Project endpoint describes deletion of a user project but provides no accompanying warning about potential loss of project data and related contents. For markdown skill documentation, behaviors that can materially affect user data should be explicitly called out.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/ticktick_api_client.py (reported line 8)May include surrounding context.

python
import httpx
from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field

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


class ApiModel(BaseModel):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

This code file performs outbound HTTP requests and attaches the configured OAuth access token in the Authorization header, which is a sensitive credential and may transmit user or system data to an external service. While the code has developer-facing docstrings, it lacks any user-facing confirmation, warning, or visible logging that would disclose this behavior to an end user of the skill.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This code presents command help and runtime messages in Chinese for users, e.g. command descriptions and error guidance, while the skill does not offer an opt-in language selection. That creates a natural-language locale policy issue because it effectively forces one language for interaction.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

When token setup fails, the user receives Chinese-only recovery instructions. Because this is a core operational path and no locale choice is provided, the file imposes a specific language on users without opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

Multiple command descriptions and status messages throughout the CLI are Chinese-only, including project/task actions and doctor output guidance. Across the file, this constitutes a forced locale for user-facing interaction rather than an isolated string.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script persists the OAuth client secret and access token to predictable files under ~/.config/ticktick-official without setting restrictive permissions or clearly warning the user that long-lived credentials are being stored on disk. On multi-user systems, misconfigured home-directory permissions, backups, sync tools, or other local compromise could expose these secrets and allow unauthorized access to the user's Dida365 account or reuse of the client credentials.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The CLI help string and many user-facing messages are written only in Chinese, which imposes a specific language on all users. The file does not provide an opt-in language selection or indicate that the tool is intentionally limited to a Chinese-speaking audience.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.