Back to skill

Security audit

OpenCloutlook

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Outlook mail and calendar purpose, but it needs review because it handles persistent Microsoft tokens, can print an access token, and includes an LLM example that can perform mail or calendar changes without explicit user confirmation.

Review before installing. Use this only on a trusted machine, protect ~/.openclaw/msgraph-tokens.json, avoid running the token-printing command, and do not use the LLM integration example for automatic moves or event creation unless you add explicit review and approval before every action. Consider removing unused User.Read and using narrower read-only scopes if you do not need mail or calendar mutation.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

other

Warning
Location
examples/README.md:58
Finding
Mailbox Metadata May Be Disclosed to Third-Party LLM Providers<![CDATA[ ## Vulnerability Details **File Location**: `examples/README.md:58-69` **Vulnerability Type**: Sensitive data disclosure to an external service **Risk Level**: Medium ### Vulnerable Code ```python from llm_integration import EmailCalendarAssistant, format_inbox_for_context # Get formatted context context = format_inbox_for_context(10) # Send to LLM response = llm_client.chat.completions.create( messages=[ {"role": "user", "content": f"Here's my email:\n{context}\nSummarize."} ] ) ``` The data produced by `format_inbox_for_context()` includes mailbox-derived sender names, email subjects, dates, read status, and partial message identifiers. The documented workflow sends this information to an unspecified external LLM provider. ### Technical Analysis Transmitting mailbox metadata to Microsoft Graph is necessary for the declared email-management functionality. Transmitting that data onward to OpenAI, Claude, or another third-party LLM is a separate disclosure boundary and is not required for basic Graph integration. The example does not require explicit user consent immediately before transmission, redact sensitive fields, restrict approved providers, or warn about provider-side logging and retention. Email subjects and correspondent information can reveal confidential business activities, personal relationships, medical information, financial activity, or authentication-related messages. ### Attack Path 1. The user authenticates the Skill with Microsoft Graph. 2. `format_inbox_for_context(10)` retrieves metadata from recent messages. 3. Sender names, subjects, dates, status, and partial IDs are inserted into `context`. 4. The application sends `context` to the configured external LLM API. 5. The external provider can process, log, or retain the mailbox metadata according to its own policies. ### Impact Assessment The issue does not directly expose the OAuth bearer token. It can, however, disclose private mailbox metadata for ...[truncated 179 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require explicit, informed opt-in before transmitting mailbox or calendar information to an external LLM. - Display the exact fields and number of records that will be sent. - Redact sender addresses, names, subjects, message IDs, attendee addresses, and event details by default. - Permit only explicitly configured and trusted providers. - Document provider retention, training, and data-processing implications. - Offer local-model processing or a mode that sends only user-approved excerpts. - Apply data minimization so only information necessary for the current request is transmitted. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
examples/llm_integration.py:164
Finding
Untrusted Mailbox Content Can Influence Unconfirmed Graph Mutations<![CDATA[ ## Vulnerability Details **File Location**: `examples/llm_integration.py:52-62, 164-197` **Vulnerability Type**: Indirect prompt injection and unsafe execution of model-generated actions **Risk Level**: High ### Vulnerable Code Mailbox-controlled fields are placed into the model context: ```python formatted = "Recent emails:\n" for i, msg in enumerate(emails, 1): sender = msg.get("from", {}).get("emailAddress", {}).get("name", "Unknown") subject = msg.get("subject", "(no subject)") date = msg.get("receivedDateTime", "") is_read = msg.get("isRead", False) msg_id = msg.get("id", "") indicator = "[read]" if is_read else "[UNREAD]" formatted += f"{i}. {indicator} {sender}: {subject}\n" formatted += f" Date: {date_formatted} | ID: {msg_id[:8]}...\n" ``` The resulting model actions are executed without user confirmation: ```python data = json.loads(response) actions = data.get("actions", []) results = [] for action in actions: action_type = action.get("type") if action_type == "move": msg_id = action.get("id") folder = action.get("folder") folder_id = mail.resolve_folder_id(folder) result = graph_api.graph_post( f"/me/messages/{msg_id}/move", {"destinationId": folder_id} ) results.append(f"✓ Moved message to {folder}") elif action_type == "create_event": payload = action.get("payload") result = graph_api.graph_post("/me/events", payload) results.append(f"✓ Created event: {payload.get('subject')}") elif action_type == "search": query = action.get("query") params = {"$search": f'"{query}"', "$top": "10"} result = graph_api.graph_get("/me/messages", params) results.append(f"✓ Found {len(result.get('value', []))} messages") ``` ### Technical Analysis Email sender names and subjects are attacker-controlled content. The example incorporates those fields into an LLM prompt wi ...[truncated 1839 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Treat all mailbox and calendar content as untrusted data, never as instructions. - Place external content in a clearly delimited structured-data section and instruct the model that it must not follow instructions found there. - Use a strict typed schema for model output and reject unknown fields or action types. - Build Graph payloads locally from an allowlist instead of forwarding model-generated payloads. - Validate message IDs, folder destinations, dates, attendee addresses, and calendar identifiers. - Present a human-readable action preview and require explicit user confirmation before every message move, event creation, event deletion, or invitation. - Bind action execution to the user’s current request rather than accepting any syntactically valid model action. - Add adversarial prompt-injection tests involving malicious subjects, sender names, event titles, and message bodies. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auth.py:278
Finding
Privileged Microsoft Graph Access Token Can Be Printed to Standard Output<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth.py:278-279` **Vulnerability Type**: Plaintext bearer-token exposure **Risk Level**: Medium ### Vulnerable Code ```python def cmd_token(): print(get_access_token()) ``` The command is explicitly documented in `skill.md:24`: ```bash python scripts/auth.py token # Print current access token ``` ### Technical Analysis An OAuth access token is a bearer credential: possession is sufficient to exercise its delegated permissions. Printing it to standard output exposes it to terminal scrollback, shell capture, agent transcripts, CI logs, screen-sharing sessions, process wrappers, and logging infrastructure. The default token scopes include `Mail.ReadWrite`, `Calendars.ReadWrite`, `offline_access`, and `User.Read`. Although only the short-lived access token is printed, it remains usable until expiration and carries substantial read/write authority. ### Attack Path 1. A user, agent, diagnostic script, or support procedure invokes `python scripts/auth.py token`. 2. `get_access_token()` obtains or refreshes a valid bearer token. 3. `cmd_token()` prints the complete credential to standard output. 4. Terminal output or an agent transcript records the token. 5. An observer or log reader extracts the token. 6. The attacker replays it in an `Authorization: Bearer` header against Microsoft Graph until expiration. ### Impact Assessment A stolen token can permit reading and modifying the victim’s email, moving messages, reading and modifying calendar data, and accessing profile data within the granted scopes. The printed access token does not itself provide the long-term refresh token, so exploitation is normally limited by the access token’s lifetime. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the `token` command from the public CLI and documentation. - Do not expose bearer credentials through agent-visible output, logs, or terminal streams. - Let internal Graph helpers retrieve and apply tokens directly. - If credential export is indispensable for development, disable it by default and require an explicit unsafe-debug option and interactive warning. - Prefer protected inter-process communication or a restricted file descriptor over standard output. - Add automated tests ensuring that status, refresh, login, and error paths never print access or refresh token values. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auth.py:86
Finding
OAuth Tokens Are Written Before Restrictive File Permissions Are Applied<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth.py:86-89` **Vulnerability Type**: Non-atomic sensitive-file creation and symlink-following risk **Risk Level**: Medium ### Vulnerable Code ```python def save_tokens(tokens): TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True) TOKEN_FILE.write_text(json.dumps(tokens, indent=2)) TOKEN_FILE.chmod(0o600) ``` ### Technical Analysis `Path.write_text()` creates the token file using permissions derived from the process umask. The code applies mode `0600` only after the complete access and refresh tokens have already been written. Under a permissive umask, there is a window in which another local user may be able to read the file. The parent directory is also created without an explicit `0700` mode. In addition, `write_text()` follows an existing symbolic link and does not provide an exclusive-create or no-follow guarantee. A malicious or compromised local environment could therefore redirect the sensitive write if it can control the destination entry. Because the stored file contains a long-lived refresh token in addition to the access token, compromise can outlast a single access-token lifetime. ### Attack Path 1. A local attacker monitors the predictable path `~/.openclaw/msgraph-tokens.json`, or prepares a malicious filesystem entry where permissions permit. 2. The user completes login or triggers token refresh. 3. `write_text()` creates or truncates the destination and writes the OAuth token set using umask-derived permissions. 4. Before `chmod(0o600)` completes, the attacker reads the file; alternatively, an existing symlink redirects the write. 5. The attacker obtains the refresh token. 6. The attacker exchanges the refresh token with Microsoft’s token endpoint and receives new access tokens. ### Impact Assessment Successful exploitation can disclose both access and refresh tokens. This may provide continuing delegated access to the victim’s mailbox, calendar, and profile ...[truncated 158 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create `~/.openclaw` with mode `0700` and verify that it is owned by the current user. - Reject symbolic links for both the directory and token destination. - Create a temporary file atomically with mode `0600`, using flags such as `O_CREAT`, `O_EXCL`, and `O_NOFOLLOW` where supported. - Write the serialized token data, flush it, call `fsync`, and atomically replace the destination. - Verify the final file is a regular file owned by the current user. - Avoid writing unnecessary token-response fields; retain only fields required for authentication. - Consider an operating-system credential store instead of a plaintext JSON refresh-token file. ]]>

T05 · Unauthorized Access and Privilege Escalation

Note
Location
config.example.ini:4
Finding
Default OAuth Configuration Requests an Unused User Profile Scope<![CDATA[ ## Vulnerability Details **File Location**: `config.example.ini:4` **Vulnerability Type**: Excess OAuth privilege **Risk Level**: Low ### Vulnerable Code ```ini scopes = Mail.ReadWrite Calendars.ReadWrite offline_access User.Read ``` The same scope set is used as the fallback in `scripts/auth.py:44`: ```python SCOPES = cfg.get("scopes", "Mail.ReadWrite Calendars.ReadWrite offline_access User.Read") ``` ### Technical Analysis The reviewed implementation requires mail write access for message moves and read-status changes, calendar write access for event creation and deletion, and offline access for automatic token refresh. No reviewed executable code invokes a profile endpoint that requires `User.Read`. Requesting `User.Read` therefore grants access beyond the functionality implemented by the Skill. This violates least-privilege principles and unnecessarily increases the impact of token disclosure. ### Attack Path 1. The user authenticates using the default scope configuration. 2. Microsoft grants a token containing the unused `User.Read` delegated permission. 3. An attacker obtains the access token through another weakness or environmental compromise. 4. The attacker uses the token to retrieve profile information that the Skill itself does not need. ### Impact Assessment This permission can expose Microsoft account profile information in addition to the required mail and calendar capabilities. It does not grant administrative privileges, but it unnecessarily broadens the data available to anyone who compromises the token. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `User.Read` from the default and fallback scope lists unless a documented profile feature is implemented. - Maintain a scope-to-feature mapping and request only permissions used by enabled features. - Consider separate read-only and read/write configurations so users who only need summaries can grant `Mail.Read` and `Calendars.Read`. - Clearly explain why each delegated permission is needed before login. - Add tests that detect unauthorized expansion of the default scope set. - Instruct existing users to revoke and reauthorize the application after the scope reduction if they want the narrower grant reflected in issued tokens. ]]>
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
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (64)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code aligns strongly with the mail portion of the description: it accesses Outlook mail folders and messages through Microsoft Graph, supports reading inbox/folder contents, moving messages, and listing folders. However, the declared purpose also states calendar capabilities such as listing events and creating events, and no calendar-related behavior appears in this code chunk. That is a material description-to-code gap for this specific chunk. Additionally, the code supports email search and marks messages as read when opened; these are extra mail behaviors not explicitly mentioned, though they are adjacent to the declared mail-management purpose. Overall, this chunk is narrower than the declared skill description, so it should be flagged as a mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
Yes, this is a mismatch based on the provided code chunk. The declared description promises substantive Outlook inbox and calendar management via Microsoft Graph, but the actual code shown is only an __init__.py file for tests containing a docstring. There is no functional behavior to support the declared capabilities. While this may be only a partial repository fragment, evaluating the supplied chunk alone, its actual behavior does not match the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The description says the skill reads/manages Outlook email and calendar via Microsoft Graph API. However, this code chunk is focused on authentication infrastructure tests, not mailbox or calendar functionality. While Microsoft login/token handling could support a Graph-based Outlook skill, the actual code shown does not perform the declared primary functions such as reading messages, moving emails, listing events, or creating events. Therefore the supplied code materially differs from the declared purpose.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
The code shown is specifically tests/test_cal.py and focuses on calendar functionality: cmd_list, cmd_get, cmd_create, cmd_delete, and cmd_calendars. These imply actual capabilities to list events, get event details, create events, delete events, and list calendars. The declared description includes calendar listing and creation, so those parts are aligned. However, deletion of calendar events is a substantive capability not mentioned in the description. Listing calendars is also an additional calendar-management capability not explicitly described, though it is related. More importantly, the declared description strongly covers Outlook email management (read/manage inbox, folders, move messages), but this supplied code chunk contains no evidence of any email-related behavior. Because the evaluation is against the supplied chunk, the description overstates email functionality relative to the observed implementation and omits event deletion. Therefore this is a description-behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The code shown is only tests, but those tests clearly indicate the implemented behavior of the related mail module. That behavior aligns strongly with the email portion of the description: inbox access, message reading, marking as read, moving messages, and folder listing through Microsoft Graph. However, the declared purpose also claims Outlook calendar support (listing and creating calendar events), and nothing in this code chunk relates to calendar resources or calendar commands. Additionally, the code tests message search, which is an undeclared mail capability, though it is adjacent to the stated purpose and less material than the missing calendar functionality. Because a significant declared capability—calendar management—is not represented by the supplied code chunk, this is a description/behavior mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This is a clear description-behavior mismatch. The declared purpose describes a fully functional Outlook/Microsoft 365 skill that interacts with user email and calendar through Microsoft Graph API. However, the provided code chunk is a test module (`tests/test_utils.py`) that validates helper utilities such as `format_datetime`, `parse_local_datetime`, and `strip_html`. While some tests mention Graph API-compatible datetime structure and email HTML cleanup, these are merely support utilities and not the claimed primary behavior. There is no evidence here of message retrieval, folder access, moving emails, listing events, creating events, authentication, Graph API requests, or access to Outlook resources. Therefore the supplied code does not accurately represent the declared skill behavior.

Credential Access

High
Category
Privilege Escalation
Content
### Q: How often do tokens refresh?

**A:** Access tokens last ~1 hour. When expired, the script automatically exchanges the refresh token for a new one. The refresh token lasts ~90 days.

### Q: What happens after 90 days?
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
```bash
# Remove local token
rm ~/.openclaw/msgraph-tokens.json

# (Optional) Revoke in Azure Portal:
# 1. Go to Account settings → Security & privacy → Apps & services
Confidence
85% 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).

Credential Access

High
Category
Privilege Escalation
Content
**A:**

- **Access token**: ~1 hour (auto-refreshes)
- **Refresh token**: ~90 days (auto-refreshes)
- **Skill remembers**: You don't need to log in again for 90 days
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
Stored at `~/.openclaw/msgraph-tokens.json` (mode 0600). Contains:
- `access_token` — Bearer token for API calls
- `refresh_token` — Long-lived token for renewal
- `expires_at` — Unix timestamp of access token expiry
- `expires_in` — Seconds until expiry (from last refresh)

## Key Endpoints
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
python auth.py login    - Initiate PKCE auth flow (opens browser)
  python auth.py status   - Show current auth status
  python auth.py refresh  - Force token refresh
  python auth.py token    - Print current access token (for scripting)
"""

import os
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
python auth.py login    - Initiate PKCE auth flow (opens browser)
  python auth.py status   - Show current auth status
  python auth.py refresh  - Force token refresh
  python auth.py token    - Print current access token (for scripting)
"""

import os
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
python auth.py login    - Initiate PKCE auth flow (opens browser)
  python auth.py status   - Show current auth status
  python auth.py refresh  - Force token refresh
  python auth.py token    - Print current access token (for scripting)
"""

import os
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
python auth.py login    - Initiate PKCE auth flow (opens browser)
  python auth.py status   - Show current auth status
  python auth.py refresh  - Force token refresh
  python auth.py token    - Print current access token (for scripting)
"""

import os
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
python auth.py login    - Initiate PKCE auth flow (opens browser)
  python auth.py status   - Show current auth status
  python auth.py refresh  - Force token refresh
  python auth.py token    - Print current access token (for scripting)
"""

import os
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
python auth.py login    - Initiate PKCE auth flow (opens browser)
  python auth.py status   - Show current auth status
  python auth.py refresh  - Force token refresh
  python auth.py token    - Print current access token (for scripting)
"""

import os
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
python auth.py login    - Initiate PKCE auth flow (opens browser)
  python auth.py status   - Show current auth status
  python auth.py refresh  - Force token refresh
  python auth.py token    - Print current access token (for scripting)
"""

import os
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
remaining = expires_at - time.time()
    if remaining > 0:
        print(f"Status: authenticated")
        print(f"Access token expires in: {int(remaining)}s")
    else:
        print("Status: access token expired (will auto-refresh on next use)")
    has_refresh = bool(tokens.get("refresh_token"))
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

High
Confidence
96% confidence
Finding
The documentation explicitly includes a command to print the current access token without warning that it is a bearer credential. Exposing an access token in terminal output, logs, transcripts, or screenshots can let anyone with access to that output reuse the token to access the user's Microsoft data until expiry, making this a real credential exposure risk.

Credential Access

High
Category
Privilege Escalation
Content
python scripts/auth.py status    # Check if authenticated
python scripts/auth.py login     # Full PKCE login (opens browser — interactive, needs user)
python scripts/auth.py refresh   # Force token refresh
python scripts/auth.py token     # Print current access token
```

**First-time setup:** Run `auth.py login`. A browser window opens for Microsoft login. After login, tokens are stored and auto-refreshed on future calls.
Confidence
97% confidence
Finding
Providing a command that prints the current access token is a direct credential-access mechanism. Because Microsoft Graph access tokens are bearer tokens, anyone who obtains the printed value can potentially read or modify the user's mail and calendar data for the token's lifetime; the presence of persistent token storage in this skill increases the sensitivity of credential handling overall.

Session Persistence

Medium
Category
Rogue Agent
Content
## Making Changes

1. **Create a feature branch:**

   ```bash
   git checkout -b feature/my-feature
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.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The documentation states that OAuth tokens are stored under ~/.openclaw/ but gives no warning about the sensitivity of those credentials or expectations for filesystem protections. For a skill that accesses Outlook mail and calendar data, stolen refresh/access tokens could enable unauthorized access to highly sensitive user communications and scheduling information.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The manifest says the skill can list and create Outlook calendar events, but this documentation states the code supports event deletion via `cmd_delete()`. Deleting calendar events is a materially broader management action than the manifest advertises, creating a description-to-behavior mismatch.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README explicitly states that OAuth tokens are stored locally but does not warn that refresh tokens and access tokens are sensitive credentials that can grant ongoing access to a user's mailbox and calendar if the host is compromised or the file permissions are weak. In the context of an email/calendar skill, this omission matters because the stored token can enable persistent access to private communications and account data.

Session Persistence

Medium
Category
Rogue Agent
Content
[msgraph]
CLIENT_ID = your-client-id-here
tenant = consumers
scopes = Mail.ReadWrite Calendars.ReadWrite offline_access User.Read
redirect_port = 8765
```
Confidence
79% confidence
Finding
The documented use of the offline_access scope and local token persistence enables long-lived sessions through refresh tokens. In an Outlook/Calendar skill, that increases the risk that a stolen local token file can be used to maintain access to sensitive email and calendar data even after the original access token expires.

Static analysis

No suspicious patterns detected.