Back to skill

Security audit

Task Sync

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real task-sync skill, but it can automatically delete real Google Tasks and TickTick data with insufficient safeguards.

Review carefully before installing. Use only dedicated test accounts or empty test lists until safeguards are added, do not enable cron on important task data, restrict token file permissions, pin the TickTick API endpoint to the official host, and add dry-run or confirmation controls before any delete propagation.

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

T09 · Insecure Skill Coding Practices

Error
Location
utils/ticktick_api.py:11
Finding
Configurable TickTick API Base Can Exfiltrate Bearer Tokens and Task Data<![CDATA[ ## Vulnerability Details **File Location**: `sync.py:92`; `utils/ticktick_api.py:11-12, 28-38` **Vulnerability Type**: Unvalidated credential-bearing network destination **Risk Level**: High ### Vulnerable Code ```python # sync.py:92 self.ticktick = TickTickAPI( self.cfg["ticktick_token"], self.cfg["ticktick_api_base"], ) ``` ```python # utils/ticktick_api.py:11-12, 28-38 def __init__(self, token_path, api_base): self.api_base = api_base self.token = self._load_token(token_path) def _request(self, endpoint, method="GET", data=None): if not self.token: return None headers = { "Authorization": f"Bearer {self.token}", "Content-Type": "application/json", } url = f"{self.api_base}{endpoint}" try: resp = requests.request( method, url, headers=headers, json=data if data else None, timeout=30, ) ``` ### Technical Analysis The API base URL is read directly from `config.json` and used as the destination for requests carrying the TickTick bearer token. The code does not enforce HTTPS, validate the hostname, or restrict the destination to the official TickTick API. Although configurability may help testing, allowing arbitrary credential-bearing destinations exceeds what is necessary for the declared synchronization functionality. If the configuration is modified accidentally or maliciously, the application sends the bearer token and synchronized task data to the configured server. ### Attack Path 1. An attacker, compromised installer, or another process with access to the project configuration modifies `ticktick_api_base`. 2. The value is changed to an attacker-controlled HTTP or HTTPS endpoint. 3. The user runs `python sync.py` manually or through the documented cron configuration. 4. `TickTickAPI._request()` adds the TickTick bearer token to the `Authorization` header. 5. The request is delivered to the attacker-controlled server. 6. The attacker reuses t ...[truncated 497 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin the production API base to `https://api.ticktick.com/open/v1`. - If configurability is required, parse the URL and enforce: - The `https` scheme. - An exact hostname allowlist such as `api.ticktick.com`. - The expected API path prefix. - No embedded user information, fragments, or unexpected ports. - Reject redirects for requests containing authorization headers, or verify that redirects remain on the approved origin. - Separate test and production clients so test endpoints cannot receive production credentials. - Fail before constructing a credential-bearing request when destination validation fails. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
sync.py:274
Finding
Ambiguous API Errors Are Interpreted as Remote Task Deletions<![CDATA[ ## Vulnerability Details **File Location**: `utils/ticktick_api.py:28-46, 65-68`; `sync.py:274-288, 327-337` **Vulnerability Type**: Destructive reconciliation based on fail-open error handling **Risk Level**: High ### Vulnerable Code ```python # utils/ticktick_api.py:28-46 def _request(self, endpoint, method="GET", data=None): if not self.token: return None headers = { "Authorization": f"Bearer {self.token}", "Content-Type": "application/json", } url = f"{self.api_base}{endpoint}" try: resp = requests.request( method, url, headers=headers, json=data if data else None, timeout=30, ) if resp.ok: return resp.json() if resp.content else {} log.error( "TickTick %s %s -> %d: %s", method, endpoint, resp.status_code, resp.text[:200] ) return None except requests.RequestException as e: log.error("TickTick request error: %s", e) return None # utils/ticktick_api.py:65-68 def get_task(self, project_id, task_id): """Get a single task by ID. Returns task dict or None if deleted/not found.""" return self._request(f"/project/{project_id}/task/{task_id}") ``` ```python # sync.py:274-288 tt_task = self.ticktick.get_task(t_proj["id"], tid) if tt_task: if not g_done: log.info("Completed in TickTick: %s", g["title"]) self.google.update_task( g_list["id"], gid, status="completed" ) self.stats["completed"] += 1 else: if not g_done: log.info( "Deleted in TickTick, deleting from Google: %s", g["title"], ) self.google.delete_task(g_list["id"], gid) del task_db[gid] self.stats["completed"] += 1 ``` ```python # sync.py:327-337 known_gid = rev_idx.get(tid) if known_gid: log.info("Google partner gone, deleting: %s", t["title"]) self.ticktick.delete_task(t_proj["id"], tid) task_db. ...[truncated 1857 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Return structured results that distinguish successful responses, confirmed `404` responses, authentication errors, rate limits, server errors, and transport failures. - Permit deletion only after an explicit, authoritative `404 Not Found`. - Abort the relevant synchronization phase when list or task retrieval is incomplete. - Add retries with bounded exponential backoff for transient failures. - Introduce deletion tombstones and a grace period rather than deleting on the first missing observation. - Require the same object to be confirmed missing in multiple successful synchronization runs before propagating deletion. - Record destructive operations in an audit log and provide a dry-run mode. - Ensure local mappings are retained when remote state cannot be determined reliably. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
sync.py:132
Finding
TickTick Project Enumeration Failure Can Delete All Mapped Google Lists<![CDATA[ ## Vulnerability Details **File Location**: `utils/ticktick_api.py:49-51`; `sync.py:132-155` **Vulnerability Type**: Bulk destructive action caused by treating retrieval failure as an empty authoritative result **Risk Level**: High ### Vulnerable Code ```python # utils/ticktick_api.py:49-51 def get_projects(self): return self._request("/project") or [] ``` ```python # sync.py:132-155 g_lists = self.google.get_lists() t_projects = self.ticktick.get_projects() if not any(p["id"] == "inbox" for p in t_projects): t_projects.append({"id": "inbox", "name": "Inbox"}) g_idx = {l["id"]: l for l in g_lists} t_idx = {p["id"]: p for p in t_projects} pairs = [] used_g, used_t = set(), set() for gid in list(self.db["lists"]): tid = self.db["lists"][gid] if ( gid not in g_idx or tid not in t_idx or g_idx[gid]["title"] in SMART_LIST_NAMES ): if gid in g_idx and tid not in t_idx: log.info( "TickTick project deleted, removing Google list: %s", g_idx[gid]["title"], ) for gt in self.google.get_tasks(gid, show_completed=True): self.db["tasks"].pop(gt["id"], None) self.google.delete_list(gid) used_g.add(gid) del self.db["lists"][gid] ``` ### Technical Analysis `get_projects()` transforms any failed API request into an empty list. The synchronization code then builds an index from that list and concludes that every previously mapped TickTick project is missing. For each missing project, it deletes the associated Google task list. Deleting a task list also removes the tasks contained within it. The code does not verify that project enumeration completed successfully and does not require a confirmed project deletion event. This is more severe than a single-task reconciliation error because one failed request can affect every mapped list. ### Attack Path 1. The user has multiple mapped TickTick projects ...[truncated 696 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Make `get_projects()` return a distinct error result rather than `[]` when the request fails. - Continue reconciliation only after a complete and successful project enumeration. - Never infer project deletion from a failed or empty response without additional confirmation. - Require repeated successful observations of absence before deleting a corresponding list. - Prefer soft deletion, archival, or an operator-confirmed deletion queue. - Add a bulk-deletion safety threshold that aborts the run if more than a small number or percentage of lists would be deleted. - Back up mappings before destructive reconciliation and preserve mappings when deletion calls fail. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
sync.py:408
Finding
Smart-List Cleanup Deletes Unrelated Tasks from Existing Google Lists<![CDATA[ ## Vulnerability Details **File Location**: `sync.py:408-414, 470-483` **Vulnerability Type**: Unsafe ownership assumptions during destructive cleanup **Risk Level**: High ### Vulnerable Code ```python # sync.py:408-414 target = next((l for l in g_lists if l["title"] == name), None) if not target: target = self.google.create_list(name) if not target: log.error("Cannot access smart list: %s", name) return ``` ```python # sync.py:470-483 mapped_gids = set(mapping.values()) for gid, g in g_idx.items(): if gid not in mapped_gids and g["status"] != "completed": log.info("Removing orphan from %s: %s", name, g["title"]) self.google.delete_task(target["id"], gid) self.stats["completed"] += 1 for gid, g in g_idx.items(): if g["status"] == "completed": log.info("Removing completed from %s: %s", name, g["title"]) self.google.delete_task(target["id"], gid) tid_to_remove = next( (t for t, gi in mapping.items() if gi == gid), None, ) if tid_to_remove: del mapping[tid_to_remove] ``` ### Technical Analysis The Skill identifies managed smart lists only by common titles: `Today`, `Next 7 Days`, and `All`. If a user already has a Google Tasks list with one of those names, the Skill reuses it without verifying ownership. It then deletes: - Every active task that is not present in the Skill's mapping. - Every completed task, regardless of whether the Skill created it. Consequently, manual tasks and tasks created by other applications are classified as orphans and removed. Name equality is not a sufficient ownership boundary for destructive cleanup. ### Attack Path 1. A user already has a Google Tasks list named `Today`, `Next 7 Days`, or `All`. 2. The list contains manually created tasks or tasks maintained by another integration. 3. The user runs the synchronization Skill. 4. `_push_smart()` selects the existing list based solely on its titl ...[truncated 456 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not claim ownership of a list based solely on its title. - Create uniquely named managed lists or require the user to select dedicated list IDs explicitly. - Persist and validate the IDs of lists created by the Skill. - Never delete tasks that lack a verified Skill-created mapping. - Restrict cleanup to objects carrying a reliable ownership marker. - Require explicit user consent before cleaning an existing list. - Add a non-destructive migration path for users who already have lists with colliding names. - Implement a dry-run report showing proposed deletions before enabling destructive cleanup. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/setup_google_tasks.py:87
Finding
OAuth Tokens and Client Secrets Are Stored Without Enforced Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup_google_tasks.py:87-99`; `scripts/setup_ticktick.py:53-56` **Vulnerability Type**: Insecure plaintext credential storage **Risk Level**: Medium ### Vulnerable Code ```python # scripts/setup_google_tasks.py:87-99 token_file.parent.mkdir(parents=True, exist_ok=True) token_data = { "token": creds.token, "refresh_token": creds.refresh_token, "token_uri": creds.token_uri, "client_id": creds.client_id, "client_secret": creds.client_secret, "scopes": creds.scopes, "expiry": creds.expiry.isoformat() if creds.expiry else None, } with open(token_file, "w") as f: json.dump(token_data, f, indent=2) ``` ```python # scripts/setup_ticktick.py:53-56 def save_token(token_data, token_file): token_file.parent.mkdir(parents=True, exist_ok=True) with open(token_file, "w") as f: json.dump(token_data, f, indent=2) ``` ### Technical Analysis Both OAuth setup scripts write sensitive credentials to plaintext JSON files using ordinary file creation. The resulting permissions depend on the process umask and are not explicitly restricted to the current user. The Google token file includes the access token, refresh token, and client secret. The TickTick token response may contain an access token and related OAuth metadata. On systems with permissive umasks, shared groups, backups, or broadly readable workspaces, other local users or processes may be able to copy these credentials. The OAuth exchanges themselves are necessary and use fixed official HTTPS endpoints. The vulnerability concerns local storage after the exchange, not the network transmission. ### Attack Path 1. A user runs an OAuth setup script in an environment with a permissive umask or shared project directory. 2. The token file is created with group-readable or world-readable permissions. 3. Another local account, process, backup job, or compromised tool reads the JSON file. 4. The attacker extracts the ...[truncated 491 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create credential files atomically with mode `0600`. - Apply `chmod(0o600)` after creation and verify the resulting permissions. - Create credential directories with mode `0700`. - Refuse to use token files that are group-readable or world-readable. - Use the operating system credential store or a dedicated secrets manager where available. - Avoid persisting credential fields that are not required at runtime. - Ensure token and credential paths are excluded from source control and general-purpose backups. - Document token revocation and rotation procedures. ]]>

T08 · Insecure Dependencies

Note
Location
README.md:78
Finding
Unpinned Third-Party Dependencies Create a Mutable Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:19`; `README.md:78` **Vulnerability Type**: Unpinned dependency installation **Risk Level**: Low ### Vulnerable Code ```bash pip install google-auth google-auth-oauthlib google-api-python-client requests ``` ### Technical Analysis The documented installation command installs packages without version constraints, hashes, or a lock file. The package names are consistent with the intended official libraries, and no direct evidence of typosquatting or a currently malicious dependency was found. However, every installation resolves mutable package versions from the configured Python package index. This prevents reproducible builds and means future package releases, dependency changes, or package-index compromise can alter the code installed and executed by the Skill. ### Attack Path 1. A user follows the documented installation command. 2. `pip` resolves the newest available versions and transitive dependencies from its configured index. 3. A compromised release, malicious index response, or unexpectedly incompatible update is selected. 4. The package is installed into the environment. 5. Package code executes during installation or when imported by the OAuth and synchronization scripts. ### Impact Assessment A compromised dependency would execute with the privileges of the user running the Skill. It could access locally stored Google and TickTick credentials, task data, project files, and any other resources available to that user. No currently malicious package was identified, so this is a supply-chain hardening weakness rather than evidence of active malicious behavior. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Provide a version-pinned `requirements.txt`, `pyproject.toml`, or lock file. - Pin both direct and transitive dependencies to reviewed versions. - Use package hashes and install with hash verification where practical. - Generate dependency inventories and run automated vulnerability scanning. - Review and update pinned versions through a controlled maintenance process. - Configure installation to use a trusted package index. - Prefer isolated virtual environments and avoid installing with elevated system privileges. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (18)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description covers a broad synchronization skill between TickTick and Google Tasks, with sync logic, mapping, exports, and troubleshooting. The supplied code chunk does only one narrow setup function: TickTick OAuth authorization and token storage. While OAuth setup is mentioned in the description as one possible use case, the code does not implement the primary declared behavior of synchronization or any Google-side functionality. This is therefore a material description-behavior mismatch rather than a supporting partial implementation of the full declared skill.

Missing User Warnings

High
Confidence
95% confidence
Finding
This test suite performs live creation, completion, and deletion-like lifecycle changes against real Google Tasks and TickTick data, with no guardrails to ensure it is operating on a dedicated test account or sandbox. In the context of an agent skill, that is dangerous because accidental execution can modify or destroy a user's real task data at scale.

Missing User Warnings

High
Confidence
96% confidence
Finding
The script deletes a Google task list programmatically without confirmation or robust ownership checks. If the wrong list ID is used or the test runs against a real account, this can permanently remove user-organized data and cause broader data loss than individual task completion.

Credential Access

High
Category
Privilege Escalation
Content
SCOPES = ["https://www.googleapis.com/auth/tasks"]
BASE_DIR = Path(__file__).resolve().parent.parent
DEFAULT_CONFIG_FILE = BASE_DIR / "config.json"
DEFAULT_CREDENTIALS_FILE = BASE_DIR / "config" / "google_credentials.json"
DEFAULT_TOKEN_FILE = BASE_DIR / "data" / "google_token.json"
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
SCOPES = ["https://www.googleapis.com/auth/tasks"]
BASE_DIR = Path(__file__).resolve().parent.parent
DEFAULT_CONFIG_FILE = BASE_DIR / "config.json"
DEFAULT_CREDENTIALS_FILE = BASE_DIR / "config" / "google_credentials.json"
DEFAULT_TOKEN_FILE = BASE_DIR / "data" / "google_token.json"
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
"scope": "tasks:read tasks:write",
    }

    print("\nRequesting access token...")
    response = requests.post(token_url, data=data, timeout=30)

    if response.status_code == 200:
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
if response.status_code == 200:
        token_data = response.json()
        print("Access token received.")
        return token_data

    print(f"Failed to get token ({response.status_code}): {response.text}")
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

External Transmission

Medium
Category
Data Exfiltration
Content
"ticktick_token": "/path/to/ticktick/token.json",
  "sync_db": "/path/to/sync_db.json",
  "sync_log": "/path/to/sync_log.json",
  "ticktick_api_base": "https://api.ticktick.com/open/v1"
}
```
Confidence
50% 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 README instructs users to run an end-to-end test suite against live Google Tasks and TickTick APIs but does not explicitly warn that the tests can create, modify, complete, or delete real user task data. In a bidirectional sync skill, this omission is risky because users may run tests against production accounts and unintentionally alter or lose task state across both services.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill invokes Python scripts, performs OAuth setup, and references cron, logs, config files, and external APIs, yet it declares no explicit tool scope or permissions boundaries. In an agent environment, this can cause over-broad access to shell, network, environment variables, and file writes without clear user-visible constraints, increasing the chance of unsafe execution or secret exposure.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The setup instructions direct users to perform OAuth flows, configure token paths, and run synchronization with third-party services, but they do not explicitly warn that access tokens will be stored locally and that task titles, notes, dates, and completion data will be transmitted externally. This weakens informed consent and can lead to accidental exposure of sensitive task content or insecure handling of credential files.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This Python file reads Google and TickTick tokens from configuration and immediately initializes API clients that will act on remote task data. While the module docstring says it is an E2E sync test, there is no explicit warning in the file that credentials are being used and that task data will be sent to third-party services.

Missing User Warnings

Medium
Confidence
81% confidence
Finding
The helper runs sync.py via subprocess, and that sync operation can modify tasks across Google Tasks and TickTick. Although there is a console message saying "Running sync...", it does not clearly warn that the subprocess may change remote user data as part of the test.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_sync():
    """Run sync.py and return output."""
    result = subprocess.run(
        [PYTHON_BIN, SYNC_SCRIPT],
        capture_output=True, text=True, cwd=BASE_DIR, timeout=120,
    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'PYTHON_BIN' from os.environ.get (line 17, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def run_sync():
    """Run sync.py and return output."""
    result = subprocess.run(
        [PYTHON_BIN, SYNC_SCRIPT],
        capture_output=True, text=True, cwd=BASE_DIR, timeout=120,
    )
Confidence
93% confidence
Finding
The executable used by subprocess.run is taken from the PYTHON_BIN environment variable without validation. An attacker who can influence the environment could cause the test to execute an arbitrary binary instead of Python, leading to unintended code execution with access to the same local files and API tokens used by the test.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
This code removes an existing token file before proceeding and later writes a replacement token file, which is a safety-relevant file modification. Although there are print statements describing the paths, the script does not ask the user to confirm the deletion or overwrite before performing it.

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

Medium
Category
Data Flow
Content
}

    print("\nRequesting access token...")
    response = requests.post(token_url, data=data, timeout=30)

    if response.status_code == 200:
        token_data = response.json()
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
91% confidence
Finding
The sync logic automatically deletes Google lists, Google tasks, and TickTick tasks when it infers the counterpart was deleted or is missing, with no dry-run, confirmation gate, conflict review, or safety threshold. In a bidirectional sync skill that manages user task data, mistaken mappings, transient API errors, stale caches, or inconsistent remote state can cause irreversible data loss across both services.

Static analysis

No suspicious patterns detected.