Back to skill

Security audit

Windows 日历同步

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it claims for Outlook calendar sync, but it stores long-lived Microsoft calendar tokens in plaintext and can delete calendar events without a confirmation step.

Review before installing. Use it only if you are comfortable granting Calendars.ReadWrite and offline_access to a Microsoft app and having calendar tokens stored as a plaintext token_store.json file in the skill directory. Avoid shared machines, verify the Azure tenant/client ID before authenticating, confirm the timezone, and revoke the Microsoft app grant plus delete the token file when you stop using it.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/setup_and_auth.py:136
Finding
OAuth Access and Refresh Tokens Stored in Plaintext by Setup Workflow<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup_and_auth.py`, lines 136-138 **Vulnerability Type**: Plaintext storage of sensitive OAuth credentials **Risk Level**: Medium ### Vulnerable Code ```python def save_token(token_data: dict): with open(TOKEN_FILE, "w", encoding="utf-8") as f: json.dump(token_data, f, indent=2) ``` ### Technical Analysis The setup workflow serializes the complete Microsoft OAuth token response directly into `scripts/token_store.json`. The response can contain an access token, refresh token, ID token, granted scopes, and expiration metadata. The file is not protected with Windows DPAPI, Windows Credential Manager, an encrypted credential store, or explicitly restrictive filesystem permissions. Because the Skill requests `offline_access`, the stored refresh token may remain useful after the short-lived access token expires. Storing the token inside the Skill directory also increases its exposure to other local tools, agents, backup processes, archive operations, or users that can read that directory. The tokens are legitimately transmitted over HTTPS to official Microsoft endpoints. The vulnerability is their unprotected persistence after authentication, not the OAuth network exchange itself. ### Attack Path 1. The user runs `setup_and_auth.py` and completes Microsoft device-code authentication. 2. Microsoft returns an OAuth response containing an access token and potentially a refresh token. 3. `save_token()` writes the complete response to `scripts/token_store.json` in plaintext. 4. A malicious local process, another agent, an exposed backup, or a user with read access to the Skill directory copies the file. 5. The attacker uses the access token directly or submits the refresh token with the associated client ID to Microsoft’s token endpoint. 6. The attacker accesses Microsoft Graph using the delegated calendar permissions granted to the application. This attack requires local file access or ...[truncated 804 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store OAuth credentials using Windows Credential Manager or protect them with Windows DPAPI. 2. Prefer a supported MSAL token cache configured with platform-specific encryption. 3. Store credential material outside the Skill installation directory. 4. If file storage is unavoidable, create the file with user-only access and explicitly configure a restrictive Windows ACL. 5. Persist only fields required for token renewal instead of the complete token response. 6. Ensure backups, diagnostics, and package archives exclude the token file. 7. Provide documented token-revocation and credential-cleanup procedures. 8. Avoid printing token contents or including the token file in error reports. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/authenticate.py:105
Finding
OAuth Access and Refresh Tokens Stored in Plaintext by Authentication Workflow<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authenticate.py`, lines 105-108 **Vulnerability Type**: Plaintext storage of sensitive OAuth credentials **Risk Level**: Medium ### Vulnerable Code ```python def _save(token_data: dict): token_data["_expires_at"] = time.time() + token_data.get("expires_in", 3600) with open(TOKEN_FILE, "w", encoding="utf-8") as f: json.dump(token_data, f, indent=2, ensure_ascii=False) ``` ### Technical Analysis The reusable authentication module writes the entire OAuth token response to `token_store.json` without encryption or explicit access-control hardening. This function is invoked after device-code authentication and after refresh-token exchange, so sensitive credentials are repeatedly persisted in plaintext. The requested `offline_access` scope is operationally useful for unattended token renewal, but it increases the consequences of file disclosure. A refresh token may be exchanged for replacement access tokens even after the original access token expires. This behavior exceeds secure minimum handling requirements because the application needs access to the token at runtime but does not need to expose it as an ordinary plaintext file within the Skill directory. ### Attack Path 1. `get_access_token()` completes device-code authentication or refreshes an expired access token. 2. `_save()` writes the complete token response into `scripts/token_store.json`. 3. An attacker obtains read access through another local process, an agent with filesystem access, a shared account, an improperly protected archive, or a backup containing the Skill directory. 4. The attacker extracts the access token or refresh token. 5. The attacker presents the token to Microsoft Graph or exchanges the refresh token at the configured Microsoft token endpoint. 6. Microsoft Graph accepts the delegated token while it remains valid and permits operations covered by `Calendars.ReadWrite`. ### Impact Assessment Successf ...[truncated 561 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace plaintext JSON storage with Windows Credential Manager, DPAPI, or an encrypted MSAL token cache. 2. Apply a user-only ACL before writing any credential data if a file-backed cache must be retained. 3. Move the cache outside the project and Skill directories. 4. Persist only the minimum fields necessary for authentication and refresh. 5. Prevent token files from being included in source control, diagnostic bundles, backups, or Skill distribution archives. 6. Handle cache replacement atomically and securely delete obsolete token material where feasible. 7. Document how users can revoke the application grant and delete locally cached credentials. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/calendar_sync.py:122
Finding
OData Filter Injection Through Unescaped Calendar Search Input<![CDATA[ ## Vulnerability Details **File Location**: `scripts/calendar_sync.py`, lines 122-130 **Vulnerability Type**: OData query injection **Risk Level**: Low ### Vulnerable Code ```python def search_events(query: str, days: int = 30) -> list: """Search events by title keyword.""" now = datetime.now(_tz()) end = now + timedelta(days=days) q = urllib.parse.urlencode({ "$filter": f"contains(subject,'{query}') and " f"start/dateTime ge '{now.isoformat()}'", "$orderby": "start/dateTime", "$top": "20", }) token = get_access_token() result = _req(f"me/calendarView?{q}", token) ``` ### Technical Analysis The `query` value is inserted directly into a quoted OData expression. `urllib.parse.urlencode()` encodes the query for HTTP transport, but it does not escape OData string-literal delimiters or operators before Microsoft Graph parses the filter. An input containing a single quote and additional OData syntax can terminate the intended string literal and alter the logical expression. Depending on Microsoft Graph’s validation and supported filter behavior, crafted input may broaden the result set or generate malformed requests. The function also calculates `end` but never includes it in the filter, so the documented `days` search boundary is not enforced. This is not the injection primitive itself, but it can increase the amount of calendar data considered by an altered filter. ### Attack Path 1. An untrusted caller supplies a crafted value through `list --search`. 2. The value contains an OData quote or expression intended to terminate or modify `contains(subject, '...')`. 3. The script URL-encodes the resulting filter without escaping the OData string literal. 4. Microsoft Graph decodes and evaluates the modified filter. 5. If Graph accepts the expression, calendar events outside the intended title match may be returned. 6. The script prints subjects and start times from the ...[truncated 799 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Escape OData single quotes in user-controlled literals by replacing each single quote with two single quotes before interpolation. 2. Validate the search term against a narrowly defined character and length policy where compatible with expected titles. 3. Prefer a Microsoft Graph search mechanism that avoids manually constructing an OData expression when available. 4. Add the calculated end time to the filter so the `days` parameter enforces both lower and upper boundaries. 5. Reject control characters and malformed Unicode sequences before constructing the request. 6. Add tests covering quotes, parentheses, logical operators, empty input, long input, and encoded input. 7. Avoid printing more event metadata than the calling workflow requires. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared purpose says the skill syncs reminders to Outlook, but the documented behavior includes Azure app registration guidance, persistent storage of access/refresh tokens, and auto-refresh of those credentials. That hidden expansion of capability is dangerous because users may authorize long-lived account access and local secret storage without understanding the real trust boundary or operational behavior.

Vague Triggers

High
Confidence
96% confidence
Finding
The trigger phrases are broad enough to match ordinary conversation about reminders or calendars, which can cause the skill to activate unexpectedly. In this skill's context, accidental activation is more dangerous because activation can lead to OAuth login, network calls, local token storage, and calendar modifications or reads affecting a real user account.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill performs sensitive actions—network access to Microsoft Graph and local credential persistence—without declaring an explicit tool scope or permission boundary. This increases the chance that an agent invokes it without clear user awareness of file and network side effects, especially because it stores authentication material locally.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The invocation rules are ambiguous about when the skill should handle calendar-related requests versus when the agent should merely discuss or draft a reminder. Ambiguity is risky here because this skill has side effects in an external account, so unclear routing can result in unintended calendar reads/writes or deletion requests being executed.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
Hard-coding Asia/Shanghai and assuming a Chinese-user locale without opt-in can cause events to be scheduled at the wrong time, leading to missed meetings or reminders. In a calendar-writing skill, incorrect timezone handling directly affects integrity of user data and can silently propagate to all synced devices.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The code silently creates config.txt with a hardcoded Azure Tenant ID when the file is absent, contradicting the documented behavior that it only reads configuration. This can bind authentication to an unexpected Microsoft tenant, causing users to authenticate against the wrong organization and potentially expose calendar access to an unintended Azure app registration context.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The script stores access and refresh tokens in token_store.json on disk in plaintext without any warning, encryption, or permission hardening. Anyone or any local process with filesystem access to that file can reuse the tokens to access and modify the user's Outlook calendar, and the refresh token can prolong that access beyond the current session.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The skill metadata says it syncs reminders to Outlook calendar, but this file also exposes listing, searching, and deletion capabilities over the user's calendar. That expands the granted behavior beyond the described purpose, which can mislead users or higher-level agents into authorizing broader calendar access than expected.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The CLI explicitly advertises a delete command even though the stated purpose is reminder/calendar sync. Undisclosed destructive functionality is dangerous because an orchestrating agent or user may invoke the skill assuming it only creates reminders, while it can also remove existing calendar data.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
Deletion is available as a direct command path and executes immediately without any confirmation, dry-run, or warning. In an agent setting, ambiguous parsing, prompt injection, or accidental invocation could permanently remove calendar events with no user verification step.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The module docstring and all user-facing setup instructions are written exclusively in Chinese, and the script later continues with Chinese-only prompts and status messages. This creates a language-policy concern because the skill imposes a specific language on users without any opt-in, fallback, or explanation that the skill is region-specific.

Tainted flow: 'req' from input (line 105, user input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Content
"scope": " ".join(SCOPES),
    }).encode()
    req = urllib.request.Request(AUTH_URL, data=data)
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read().decode())
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.

Tainted flow: 'req' from input (line 105, user input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Content
"scope": " ".join(SCOPES),
    }).encode()
    req = urllib.request.Request(AUTH_URL, data=data)
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read().decode())
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.

Tainted flow: 'req' from input (line 105, user input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Content
"scope": " ".join(SCOPES),
    }).encode()
    req = urllib.request.Request(AUTH_URL, data=data)
    with urllib.request.urlopen(req) as resp:
        return json.loads(resp.read().decode())
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.

Tainted flow: 'req' from input (line 201, user input) → urllib.request.urlopen (network output)

Medium
Category
Data Flow
Content
}).encode()
                    req = urllib.request.Request(TOKEN_URL, data=data)
                    try:
                        with urllib.request.urlopen(req) as resp:
                            new_token = json.loads(resp.read().decode())
                            new_token["_expires_at"] = time.time() + new_token.get("expires_in", 3600)
                            save_token(new_token)
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.

Description-Behavior Mismatch

Low
Confidence
94% confidence
Finding
The manifest description says the skill is used to sync reminders or add items to the calendar, emphasizing direct writing to the user's Outlook calendar. However, the file later documents additional capabilities to query weekly schedules and delete existing events, which go beyond a narrow 'sync reminders' description.

Intent-Code Divergence

Low
Confidence
88% confidence
Finding
The workflow section describes a single operational flow ending with calling `calendar_sync.py add` and telling the user the event was created. Elsewhere, the document explicitly states the skill also supports querying calendars and deleting events, so this workflow description contradicts the broader documented behavior rather than merely omitting details.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
Natural-language strings in the module docstring and runtime output are written only in Chinese, with no option for the user to choose another language. The policy explicitly calls out language or locale constraints as violations when they are forced without opt-in or justification.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
User-facing docstrings, CLI descriptions, help text, and printed output are all hard-coded in Chinese throughout the file. This forces a specific language/locale experience without indicating user choice or documenting a justified region-specific constraint.

Static analysis

No suspicious patterns detected.