Back to skill

Security audit

Microsoft To Do

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed Microsoft To Do integration, but it stores OAuth tokens locally, so users should protect the token file.

Install only if you are comfortable granting delegated Microsoft Graph Tasks.ReadWrite access. Keep token.json private, avoid shared or synced directories for token storage, do not paste bearer tokens into logs or chats, and delete/revoke the token if you stop using the skill.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ms_todo_auth.py:99
Finding
OAuth Tokens Stored Without Explicit Owner-Only File Permissions## Vulnerability Details **File Location**: `scripts/ms_todo_auth.py`, lines 99–101 **Vulnerability Type**: Insecure storage of OAuth access and refresh tokens **Risk Level**: Medium ### Vulnerable Code ```python def write_json(path: Path, payload: dict) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") ``` This function is used to persist complete OAuth responses in the following flows: ```python write_and_print_json(TOKEN_FILE, payload, redact_tokens=True) ``` Although token values are redacted from normal console output, the complete payload—including access tokens, refresh tokens, and potentially ID tokens—is written to `token.json`. ### Technical Analysis The configuration directory and token file are created without explicit owner-only permissions. Their effective permissions therefore depend on the process umask and any pre-existing filesystem objects. Under a permissive umask or unsafe configuration-directory override, another local account may be able to read the token file. The destination can also be controlled through environment variables such as `MS_TODO_CONFIG_DIR` and `MS_TODO_TOKEN_FILE`. The implementation does not verify destination ownership, permissions, or whether the destination is a symbolic link. It also does not use an atomic, exclusive file-creation operation. These omissions increase the risk when the helper runs in a shared or adversarial local environment. The network transmission detected by the pre-scan is otherwise consistent with the declared functionality: device codes and refresh tokens are sent to Microsoft OAuth endpoints under `login.microsoftonline.com`. The requested `Tasks.ReadWrite` permission is necessary for the documented task creation, modification, completion, and deletion features, while `offline_access` supports the documented token-refresh feature. ### Attack Path 1. A victim runs `device-code` followed by `poll-tok ...[truncated 1520 chars]
Remediation
## Remediation Suggestions 1. Create the configuration directory with owner-only permissions (`0700` on POSIX systems), and verify that existing directories are owned by the current user and are not writable by untrusted users. 2. Create token and device-code files with owner-only permissions (`0600` on POSIX systems) rather than relying on the process umask. 3. Write secrets atomically: - Create a temporary file in the same trusted directory using exclusive creation. - Apply restrictive permissions before writing sensitive content. - Flush and securely replace the destination. 4. Reject symbolic links and other unexpected file types for secret destinations. Where supported, use no-follow semantics during file creation. 5. Validate environment-overridden paths before storing credentials. Warn or fail if the destination is in a shared, world-readable, or world-writable directory. 6. On Windows, apply an ACL that grants access only to the current user rather than relying solely on generic file-creation behavior. 7. Prefer an OS-native credential store or keychain for refresh tokens when practical. 8. Preserve the existing stdout redaction behavior and clearly warn users that `access-token` intentionally emits a bearer token that must not be logged or exposed.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description says the skill manages Microsoft To Do lists and tasks, but the actual behavior prominently includes OAuth device-code authentication, token extraction, refresh-token handling, and local credential storage. This mismatch is dangerous because users and orchestration systems may approve or invoke the skill for simple task CRUD while unintentionally granting it credential-handling and secret-persistence capabilities.

Credential Access

High
Category
Privilege Escalation
Content
- `scripts/.env.example` shows the supported variables for a portable setup
- copy it to `scripts/.env` only if you explicitly want local overrides

## Get an Access Token

Create the default config directory and store the app IDs:
Confidence
88% confidence
Finding
The skill includes steps to obtain and output access tokens and to store device-code and token artifacts locally. Credential material such as access and refresh tokens is highly sensitive; exposing token retrieval through the skill increases the risk of misuse, accidental disclosure, or unauthorized reuse if file permissions and outputs are not tightly controlled.

Credential Access

High
Category
Privilege Escalation
Content
ACCESS_TOKEN=$(python3 scripts/ms_todo_auth.py access-token)
```

Do not hardcode access tokens in the skill. They expire.

Example with env-var overrides:
Confidence
90% confidence
Finding
The documented `access-token` helper explicitly retrieves a bearer token into a shell variable, which can be exposed through shell history, process inspection in some environments, logs, or accidental echoing. In a skill context, surfacing raw credentials is more dangerous than performing authenticated requests internally because it expands the number of places secrets may leak.

Credential Access

High
Category
Privilege Escalation
Content
- To Do is user-scoped. Use delegated auth, not app-only auth.
- Hardcoding `tenant_id` and `client_id` is acceptable for a personal setup, but store them in the platform config directory unless you intentionally choose `scripts/.env` for a local portable copy.
- A bearer token comes from the token endpoint after device-code sign-in. It is not shown in the Azure dashboard.
- If you want automatic renewal, save the refresh token from `token.json` and exchange it for a new access token later.
- `poll-token` and `refresh-token` persist the full token payload to `token.json`; stdout is summarized so raw token JSON secrets are not printed.
- For personal Outlook/Hotmail/Live accounts, set `tenant_id` to `consumers` for device-code and token requests.
Confidence
93% confidence
Finding
The notes explicitly instruct saving refresh tokens in token.json for automatic renewal and confirm that full token payloads are persisted locally. Refresh tokens provide durable delegated access and are more sensitive than short-lived access tokens, so insecure local storage or reuse by other tools could lead to account compromise over time.

Credential Access

High
Category
Privilege Escalation
Content
SCRIPT_DIR = Path(__file__).resolve().parent
load_dotenv(SCRIPT_DIR / ".env")

CONFIG_DIR = default_config_dir()
TENANT_FILE = Path(os.environ.get("MS_TODO_TENANT_FILE", CONFIG_DIR / "tenant_id"))
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
SCRIPT_DIR = Path(__file__).resolve().parent
load_dotenv(SCRIPT_DIR / ".env")

CONFIG_DIR = default_config_dir()
TENANT_FILE = Path(os.environ.get("MS_TODO_TENANT_FILE", CONFIG_DIR / "tenant_id"))
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill documents capabilities to read environment variables, read/write local files, and make authenticated network requests, but it does not declare any tool scope such as permissions or allowed-tools. That creates an authorization transparency gap: a caller may trust the high-level description without realizing the skill can access credentials, persist tokens, and transmit data externally.

Session Persistence

Medium
Category
Rogue Agent
Content
---
name: microsoft-todo
description: "Microsoft To Do via Microsoft Graph. List task lists, read tasks, create tasks, update tasks, and mark tasks complete."
metadata:
  openclaw:
    category: "productivity"
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
LIST_ID="your-list-id"

curl -s -X POST "https://graph.microsoft.com/v1.0/me/todo/lists/$LIST_ID/tasks" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
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
LIST_ID="your-list-id"
TASK_ID="your-task-id"

curl -s -X PATCH "https://graph.microsoft.com/v1.0/me/todo/lists/$LIST_ID/tasks/$TASK_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
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
LIST_ID="your-list-id"
TASK_ID="your-task-id"

curl -s -X PATCH "https://graph.microsoft.com/v1.0/me/todo/lists/$LIST_ID/tasks/$TASK_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
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
94% confidence
Finding
The script persists OAuth token responses, including refresh tokens, to disk in token.json without setting restrictive file permissions or warning the user that long-lived credentials are being stored locally. If the config directory is readable by other local users, backed up insecurely, or exposed through logs/support bundles, an attacker could reuse the refresh token to obtain access to the user's Microsoft To Do data.

Missing User Warnings

Low
Confidence
88% confidence
Finding
This markdown file includes a task deletion example using `DELETE` against Microsoft Graph, but the surrounding documentation does not warn that the action removes user task data. For markdown files, destructive behaviors that could affect user data should include a clear warning so users understand the impact before using the example.

Static analysis

No suspicious patterns detected.