Back to skill

Security audit

ticktick-calendar

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real TickTick integration, but its OAuth and endpoint controls could let unsafe configuration or callback input redirect or overwrite account credentials.

Review before installing. Use only with trusted .env and runtime configuration, keep the OAuth and API URLs on official TickTick endpoints, protect the token.json file, and avoid exposing dev/test servers. The publisher should add strong OAuth state validation or PKCE, gate custom endpoints for development only, and update the flagged dev dependencies.

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
scripts/ticktick-cli.mjs:152
Finding
OAuth State Is Predictable and Not Validated During Authorization-Code Exchange<![CDATA[ ## Vulnerability Details **File Location**: `skill-entry/token-manager.mjs:28-30, 230-236`; `scripts/ticktick-cli.mjs:152-181` **Vulnerability Type**: OAuth login CSRF and authorization-code substitution **Risk Level**: Medium ### Vulnerable Code `skill-entry/token-manager.mjs:28-30`: ```js export function createOAuthState(prefix = "oc") { return `${prefix}_${Date.now().toString(36)}`; } ``` `skill-entry/token-manager.mjs:230-236`: ```js export function parseCallbackUrl(callbackUrl) { const parsed = new URL(callbackUrl); const code = parsed.searchParams.get("code") ?? undefined; const state = parsed.searchParams.get("state") ?? undefined; const error = parsed.searchParams.get("error") ?? undefined; const errorDescription = parsed.searchParams.get("error_description") ?? undefined; return { code, state, error, errorDescription }; } ``` `scripts/ticktick-cli.mjs:152-181`: ```js if (parsed.command === "auth-url") { const state = readFlag(parsed, "state") ?? createOAuthState(); const authUrl = buildTickTickAuthUrl(env, state); console.log(JSON.stringify({ state, authUrl, redirectUri: env.redirectUri }, null, 2)); return; } if (parsed.command === "auth-exchange") { const callbackUrl = readFlag(parsed, "callbackUrl"); const codeFromFlag = readFlag(parsed, "code"); let code = codeFromFlag; let state; if (callbackUrl) { const parsedCallback = parseCallbackUrl(callbackUrl); if (parsedCallback.error) { throw new Error( `OAuth callback returned error='${parsedCallback.error}'${ parsedCallback.errorDescription ? ` description='${parsedCallback.errorDescription}'` : "" }` ); } code = parsedCallback.code; state = parsedCallback.state; } if (!code) { throw new Error("auth-exchange requires --callbackUrl <url> or --code <code>"); } const token = await exchangeCodeAndPersistToken({ code, tokenPath, env }); ``` ### Technical Analysis The OAuth `state` p ...[truncated 2212 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate state with a cryptographically secure random source: ```js import { randomBytes } from "node:crypto"; export function createOAuthState() { return randomBytes(32).toString("base64url"); } ``` 2. Persist the issued state in a file created with mode `0600`, together with its creation time and intended redirect URI. 3. Require the callback to contain a state value and reject missing, expired, malformed, or mismatched values. 4. Compare state values using `crypto.timingSafeEqual` after validating equal lengths. 5. Delete or invalidate stored state immediately after one successful exchange to prevent replay. 6. Disable bare `--code` exchange by default. If it is necessary for advanced workflows, require an explicit unsafe/manual-flow option and a separately supplied expected state. 7. Add PKCE using a securely generated verifier and `S256` challenge, persist the verifier with state, and include it during token exchange. 8. Add unit tests covering missing state, mismatched state, replayed state, expired state, and successful one-time validation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/config/ticktick-env.ts:52
Finding
Configurable OAuth and API Destinations Can Receive TickTick Credentials<![CDATA[ ## Vulnerability Details **File Location**: `src/config/ticktick-env.ts:52-65, 164-180`; `src/auth/ticktick-oauth2-client.ts:86-95`; `src/api/ticktick-api-client.ts:186-207` **Vulnerability Type**: Credential disclosure through unrestricted configurable network destinations **Risk Level**: Medium ### Vulnerable Code `src/config/ticktick-env.ts:52-65`: ```ts const oauthTokenUrl = normalizeBaseUrl( validateUrl(readWithDefault(source, ENV_KEYS.oauthTokenUrl, DEFAULTS.oauthTokenUrl), ENV_KEYS.oauthTokenUrl, { requireHttps: true, }) ); const apiBaseUrl = normalizeBaseUrl( validateUrl(readWithDefault(source, ENV_KEYS.apiBaseUrl, DEFAULTS.apiBaseUrl), ENV_KEYS.apiBaseUrl, { requireHttps: true, }) ); ``` `src/config/ticktick-env.ts:164-180`: ```ts function validateUrl(value: string, key: string, options: { requireHttps: boolean }): string { let parsed: URL; try { parsed = new URL(value); } catch { throw new ContractValidationError(CONTRACT, `Invalid URL in '${key}'.`); } if (!["http:", "https:"].includes(parsed.protocol)) { throw new ContractValidationError(CONTRACT, `Invalid protocol in '${key}'.`); } if (options.requireHttps && parsed.protocol !== "https:") { throw new ContractValidationError(CONTRACT, `Expected '${key}' to use https.`); } return parsed.toString(); } ``` `src/auth/ticktick-oauth2-client.ts:86-95`: ```ts const form = toTokenRequestFormData(request); const response = await runWithTimeout( () => this.config.fetchImplementation(this.config.tokenUrl, { method: "POST", headers: buildTokenHeaders(this.config.userAgent), body: form.toString(), }), this.config.timeoutMs ); ``` `src/api/ticktick-api-client.ts:186-207`: ```ts const headers = mergeHeaders(this.config.defaultHeaders, request.headers, { accept: "application/json", authorization: `Bearer ${token}`, }); let body: string | undefined; if (request.body !== undefined) { headers["content-type"] ...[truncated 3266 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Allowlist official production destinations by hostname and protocol: - OAuth authorization and token endpoints under the expected TickTick domain. - API requests under `https://api.ticktick.com/open/v1`. 2. Reject URLs containing usernames or passwords, fragments, unexpected ports, or nonstandard hostnames. 3. Resolve and validate hostnames carefully if private-network destinations are prohibited; do not rely solely on an HTTPS protocol check. 4. Permit custom endpoints only behind an explicit development/test option such as `TICKTICK_ALLOW_CUSTOM_ENDPOINTS=true`. 5. Emit a prominent warning and require deliberate confirmation when custom endpoints are enabled outside unit tests. 6. Keep injected `fetchImplementation` support limited to programmatic testing and avoid exposing it to untrusted action input. 7. Separate production and test configuration types so production constructors cannot silently accept arbitrary credential destinations. 8. Add tests proving that attacker-controlled hosts, URL userinfo, unexpected ports, and deceptive subdomains such as `api.ticktick.com.attacker.example` are rejected. 9. Document that `.env` and deployment configuration are security-sensitive and must not be writable by untrusted users. ]]>
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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (61)

Known Vulnerable Dependency: vitest==3.2.4 — 2 advisory(ies): CVE-2026-47429 (When Vitest UI server is listening, arbitrary file can be read and executed); CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock)

Critical
Category
Supply Chain
Confidence
96% confidence
Finding
vitest 3.2.4 is flagged for arbitrary file read and possible execution when the Vitest UI server is listening, plus the redirect-mock traversal issue. This is a true dependency risk, but it is scoped to testing infrastructure rather than the TickTick OAuth/task runtime; that lowers real-world exposure unless test/UI servers are run in reachable environments.

Known Vulnerable Dependency: vitest==3.2.4 — 2 advisory(ies): CVE-2026-47429 (When Vitest UI server is listening, arbitrary file can be read and executed); CVE-2026-84373 (Vitest: Path Traversal / Arbitrary File Read via @vitest/mocker Redirect Mock)

Critical
Category
Supply Chain
Confidence
97% confidence
Finding
The manifest resolves vitest to a version with reported critical advisories involving arbitrary file read and possible code execution/path traversal in Vitest components. Even though vitest is a devDependency, it can still be dangerous in developer workstations, CI environments, or any context where the Vitest UI or mocker features are exposed or invoked, potentially compromising source code, secrets, or the build environment.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description describes a substantial TickTick integration toolkit with authentication and operational capabilities. The provided code chunk is only an index file that re-exports another module named 'contract-validation.js'. Based on the supplied code alone, the actual behavior is limited to module export wiring and appears unrelated to TickTick-specific auth or CRUD workflows. This is a material mismatch between the declared purpose and the observed code behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
Yes, this is a mismatch based on the supplied code chunk. The declared description describes a full TickTick integration toolkit with authentication, token management, and task/project operations. However, the actual code shown is only a barrel export file that re-exports from an error-categories module. That behavior is materially narrower and unrelated to the declared primary purpose. While this file could be a tiny supporting part of a larger codebase, the evaluation is against the supplied chunk, and this chunk does not implement or indicate the declared capabilities.

Ae1

High
Category
analysis-evasion
Content
| Need OpenClaw action integration | Use `skill-entry/ticktick-skill.mjs` and expose 5 MVP actions |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| Need OpenClaw action integration | Use `skill-entry/ticktick-skill.mjs` and expose 5 MVP actions |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| Need OpenClaw action integration | Use `skill-entry/ticktick-skill.mjs` and expose 5 MVP actions |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| Need OpenClaw action integration | Use `skill-entry/ticktick-skill.mjs` and expose 5 MVP actions |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `skill-entry/token-manager.mjs`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `skill-entry/token-manager.mjs`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
1. Parse env via `parseTickTickEnvFromRuntime`.
2. Resolve token path (`options.tokenPath` -> `TICKTICK_TOKEN_PATH` -> default path).
3. Load access token with auto refresh (`getAccessTokenWithAutoReauth`).
4. On missing/expired token without valid refresh token:
   - raise `ReauthRequiredError`
   - optionally notify via webhook
Confidence
86% confidence
Finding
The skill workflow explicitly loads and refreshes access tokens and may notify via webhook during reauthentication. Handling bearer tokens is inherently sensitive; without strict controls around storage, logging, notification content, and path handling, tokens or authorization state could be exposed and lead to account takeover or persistent API access.

Credential Access

High
Category
Privilege Escalation
Content
| Category | Typical Source |
|----------|----------------|
| `auth_401` | Invalid/expired access token |
| `auth_403` | Scope/permission denied |
| `not_found_404` | Missing task/project resource |
| `rate_limit_429` | TickTick rate-limited request |
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Ae1

High
Category
analysis-evasion
Content
- `src/api/ticktick-api-client.ts`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `src/api/ticktick-gateway.ts`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
Skill에 주입되는 `getAccessToken()`은 아래 정책으로 운영하는 것을 권장합니다.

1. 메모리/스토리지에서 현재 access token 조회
2. 만료 임박이면 refresh token으로 갱신
3. 갱신 성공 시 저장소 업데이트
4. 실패 시 인증 재진행 요청
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Known Vulnerable Dependency: nanoid==3.3.11 — 3 advisory(ies): CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-67213 (nanoid: custom generators can loop indefinitely when size is zero); CVE-2026-73086 (nanoid: Integer Overflow or Wraparound)

High
Category
Supply Chain
Confidence
80% confidence
Finding
nanoid 3.3.11 is flagged for multiple issues involving infinite loops or integer wraparound in generator size handling. In this dependency tree nanoid is transitive via PostCSS, so actual exploitability depends on whether attacker-controlled size values reach vulnerable generator code; the package presence is real, but practical impact in this skill context is less direct than a runtime auth bug.

Known Vulnerable Dependency: picomatch==4.0.3 — 2 advisory(ies): CVE-2026-33672 (Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Mat); CVE-2026-33671 (Picomatch has a ReDoS vulnerability via extglob quantifiers)

High
Category
Supply Chain
Confidence
86% confidence
Finding
picomatch 4.0.3 is flagged for method injection and ReDoS issues in glob parsing. Since it is used by dev tooling for file matching, risk is mainly present if untrusted glob patterns are accepted during tooling execution, which is plausible in CI/plugins but not obviously exposed by the lockfile alone.

Known Vulnerable Dependency: postcss==8.5.6 — 4 advisory(ies): CVE-2026-45623 (PostCSS: Arbitrary file read and information disclosure via attacker-controlled ); CVE-2026-69153 (PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappi); CVE-2026-41305 (PostCSS has XSS via Unescaped </style> in its CSS Stringify Output) +1 more

High
Category
Supply Chain
Confidence
88% confidence
Finding
postcss 8.5.6 is associated with several advisories including arbitrary file read and XSS-related issues. In this repository it appears as a transitive dev-tool dependency under Vite, so the vulnerability is real but most relevant when processing attacker-controlled CSS/config/source map inputs during development or build workflows.

Known Vulnerable Dependency: rollup==4.57.1 — 1 advisory(ies): CVE-2026-27606 (Rollup 4 has Arbitrary File Write via Path Traversal)

High
Category
Supply Chain
Confidence
87% confidence
Finding
rollup 4.57.1 is flagged for arbitrary file write via path traversal. As a bundler dependency this is principally dangerous during build/dev operations if an attacker can influence archive/module/output paths, which is less severe than a production-facing auth flaw but still meaningful in CI and developer environments.

Known Vulnerable Dependency: vite==7.3.1 — 5 advisory(ies): CVE-2026-39365 (Vite Vulnerable to Path Traversal in Optimized Deps `.map` Handling); CVE-2026-53571 (vite: `server.fs.deny` bypass on Windows alternate paths); CVE-2026-39363 (Vite Vulnerable to Arbitrary File Read via Vite Dev Server WebSocket) +2 more

High
Category
Supply Chain
Confidence
95% confidence
Finding
vite 7.3.1 is reported with multiple high-severity path traversal and arbitrary file read issues affecting the dev server and filesystem protections. Although Vite is a dev dependency, these classes of bugs become serious if a dev server is exposed on a workstation, preview environment, or CI runner, allowing unintended file disclosure from the host.

Credential Access

High
Category
Privilege Escalation
Content
parseCallbackUrl,
} from "../skill-entry/token-manager.mjs";

const DEFAULT_ENV_PATH = path.resolve(process.cwd(), ".env");

function printUsage() {
  console.log(`TickTick CLI
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
complete-task --taskId <id> [--completedAt <iso>]

Global options:
  --env <path>         Path to .env (default: ./ .env)
  --tokenPath <path>   Token JSON path (default: ~/.config/ticktick/token.json)
  --help               Show help
`);
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
complete-task --taskId <id> [--completedAt <iso>]

Global options:
  --env <path>         Path to .env (default: ./ .env)
  --tokenPath <path>   Token JSON path (default: ~/.config/ticktick/token.json)
  --help               Show help
`);
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
if (reason === "token_file_missing") {
    return "Token file is missing. OAuth reauthorization is required.";
  }
  return "Access token expired or refresh failed. OAuth reauthorization is required.";
}

export function createWebhookReauthNotifierFromEnv(options = {}) {
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 (reason === "token_file_missing") {
    return "Token file is missing. OAuth reauthorization is required.";
  }
  return "Access token expired or refresh failed. OAuth reauthorization is required.";
}

export function createWebhookReauthNotifierFromEnv(options = {}) {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.