Back to skill

Security audit

Google Calendar

Security checks for vulnerabilities and agentic risk

Overview

This Google Calendar skill is mostly purpose-aligned, but it handles live OAuth tokens in an under-disclosed and insecure way that users should review before installing.

Review this skill before installing. Use it only with the minimum Google Calendar OAuth scope needed, avoid running the refresh_token.py helper unless you accept plaintext token storage, and prefer OpenClaw secrets or an OS credential store over ~/.config/google-calendar/secrets.env. Be careful with add, update, and delete commands because they can change calendar data immediately.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/refresh_token.py:28
Finding
OAuth Access Token Exposed Through Standard Output and Insecure Plaintext Storage## Vulnerability Details **File Location**: `scripts/refresh_token.py`, lines 28–49 **Vulnerability Type**: Plaintext sensitive-data exposure **Risk Level**: Medium **Vulnerable Code**: ```python # Update the secrets.env file env_path = os.path.expanduser('~/.config/google-calendar/secrets.env') # Read existing lines, replace or add GOOGLE_ACCESS_TOKEN lines = [] if os.path.exists(env_path): with open(env_path, 'r') as f: lines = f.readlines() new_lines = [] token_set = False for line in lines: if line.startswith('export GOOGLE_ACCESS_TOKEN='): new_lines.append(f'export GOOGLE_ACCESS_TOKEN={access_token}\n') token_set = True else: new_lines.append(line) if not token_set: new_lines.append(f'export GOOGLE_ACCESS_TOKEN={access_token}\n') with open(env_path, 'w') as f: f.writelines(new_lines) print(json.dumps(resp_data, indent=2)) ``` ### Technical Analysis The OAuth access token returned by Google is written in plaintext to `~/.config/google-calendar/secrets.env`. The script does not create the parent directory with restrictive permissions, explicitly set the file mode to `0600`, verify existing file ownership, or perform an atomic replacement. The entire OAuth response is also printed to standard output. Because the response contains `access_token`, the credential may be captured by terminal history tooling, agent transcripts, automation logs, CI logs, process wrappers, or redirected output files. This behavior exceeds the minimum disclosure necessary for token refresh. Persisting an access token may be operationally useful, but printing it is unnecessary, and storage should use a protected secret-management mechanism. The separate transmission of the client ID, client secret, and refresh token to `https://oauth2.googleapis.com/token` is expected OAuth behavior and is necessary for refresh-token exchange. No transmission to an unknown or attacker- ...[truncated 1220 chars]
Remediation
## Remediation Suggestions - Do not print the complete OAuth response. Emit only non-sensitive status information, such as successful refresh and token expiry. - Store the token using the documented OpenClaw secret-management facility instead of a plaintext environment file. - If file storage is unavoidable, create the configuration directory with mode `0700` and the token file with mode `0600`. - Verify that an existing token file is a regular file owned by the current user and is not a symbolic link. - Write to a securely created temporary file in the same directory, apply restrictive permissions, flush it, and atomically replace the destination. - Avoid preserving access tokens longer than operationally necessary, and document revocation and rotation procedures. - Ensure logs, exception handlers, and diagnostic output redact `access_token`, `refresh_token`, `client_secret`, and authorization headers.

T08 · Insecure Dependencies

Note
Location
SKILL.md:49
Finding
Unpinned and Unnecessary Third-Party Dependency Installation## Vulnerability Details **File Location**: `SKILL.md`, lines 49–52 **Vulnerability Type**: Unpinned third-party dependencies and avoidable supply-chain exposure **Risk Level**: Low **Vulnerable Code**: ```markdown 5. Install the required Python packages (once): ```bash pip install --user google-auth google-auth-oauthlib google-api-python-client ``` ``` ### Technical Analysis The setup documentation instructs users to install three packages from the default Python package index without fixed versions, hashes, or a reviewed lockfile. Package resolution therefore depends on mutable registry state at installation time. The audited scripts use Python standard-library modules rather than these packages. Consequently, this installation instruction introduces supply-chain exposure that is not necessary for the supplied implementation. Installation with `--user` also places packages in the user environment, where they may affect other Python programs. The audit did not identify evidence that the named packages are malicious. The issue is the unnecessary and non-reproducible installation process rather than a confirmed malicious dependency. ### Attack Path 1. A user follows the setup instructions and runs the documented `pip install --user` command. 2. Pip resolves whichever package releases and transitive dependencies are current at that time. 3. If a package account, release artifact, dependency, package index, or network trust path is compromised, malicious installation or package code is delivered. 4. That code executes with the privileges of the user performing installation or later importing the package. 5. It may access files, environment variables, or credentials available to that user, including Google OAuth secrets if they are present in the environment. ### Impact Assessment Successful supply-chain compromise would provide code execution in the installing user's context. Potential exposure includes use ...[truncated 272 chars]
Remediation
## Remediation Suggestions - Remove the installation step if the implementation continues to rely exclusively on Python’s standard library. - If third-party packages become necessary, pin exact direct and transitive versions in a reviewed lockfile. - Require package hashes, such as through a hash-locked requirements file and `pip install --require-hashes`. - Install dependencies in an isolated virtual environment rather than the shared user package directory. - Review dependency provenance, release history, maintainers, and vulnerability advisories before updates. - Keep the documentation synchronized with the actual implementation so users are not asked to install unused software.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (10)

Tainted flow: 'req' from os.getenv (line 17, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = urllib.request.Request('https://oauth2.googleapis.com/token', data=data, method='POST')
    req.add_header('Content-Type', 'application/x-www-form-urlencoded')
    try:
        with urllib.request.urlopen(req) as resp:
            resp_data = json.load(resp)
    except urllib.error.HTTPError as e:
        sys.stderr.write(f'HTTP error {e.code}: {e.read().decode()}\n')
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
```

## How it works (brief)
The script loads the credentials from the environment, refreshes the access token using the refresh token, builds a `service = build('calendar', 'v3', credentials=creds)`, and then calls the appropriate API method.

## References
- Google Calendar API reference: https://developers.google.com/calendar/api/v3/reference
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 not access_token:
        sys.stderr.write('No access_token in response\n')
        sys.exit(1)
    # Update the secrets.env file
    env_path = os.path.expanduser('~/.config/google-calendar/secrets.env')
    # Read existing lines, replace or add GOOGLE_ACCESS_TOKEN
    lines = []
Confidence
95% confidence
Finding
The code stores an access token in a plaintext secrets file under the user's home directory without setting secure permissions or using a protected secret store. This creates credential exposure risk through local file disclosure, backups, shell tooling, or permissive default umasks; in a skill context, persistent local storage of live tokens increases the blast radius beyond a single execution.

Credential Access

High
Category
Privilege Escalation
Content
sys.stderr.write('No access_token in response\n')
        sys.exit(1)
    # Update the secrets.env file
    env_path = os.path.expanduser('~/.config/google-calendar/secrets.env')
    # Read existing lines, replace or add GOOGLE_ACCESS_TOKEN
    lines = []
    if os.path.exists(env_path):
Confidence
95% confidence
Finding
Using a fixed path for a plaintext secrets file in the home directory encourages persistent credential storage in a location that may be accessible to other local users, processes, or backup systems. The danger is amplified because the script writes refreshed live access tokens there automatically, making compromise of that file immediately useful for unauthorized calendar API access.

Lp3

Medium
Category
MCP Least Privilege
Confidence
97% confidence
Finding
The skill requests or implies access to environment secrets, local files, and outbound network access, but the manifest does not declare any explicit tool scope or allowed-tools restrictions. In an agent environment, this increases the blast radius because the skill can potentially read sensitive credentials and make external API calls without clear least-privilege boundaries or operator visibility.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill performs state-changing calendar actions (add, update, delete) immediately based on command-line input, with no built-in confirmation, dry-run mode, or guardrails against accidental invocation. In an agent context, this increases the risk of unintended modification or deletion of calendar data if the model misunderstands user intent, is prompt-injected, or is given ambiguous instructions.

Context-Inappropriate Capability

Medium
Confidence
86% confidence
Finding
The manifest describes interacting with Google Calendar to list, create, update, and delete events. This script additionally pulls client credentials and a refresh token from process environment variables, which is credential-management behavior rather than calendar interaction itself; that capability is not stated in the skill purpose.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The request body includes client_secret and refresh_token and sends them to Google's token endpoint. While this is functionally expected for token refresh, the file itself provides no user-facing disclosure that sensitive environment credentials will be transmitted over the network.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The manifest says the skill interacts with Google Calendar to list, create, update, and delete calendar events. This file instead calls Google's OAuth token endpoint to mint a new access token and updates a local secrets file, which is distinct authentication and credential-storage behavior not reflected in the description.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script silently writes a refreshed access token into a local secrets file, which changes persisted credentials without any confirmation, permission hardening, or integrity checks. In an agent-skill context, silent secret mutation is more dangerous because users may not expect background credential storage and the file may be readable by other local processes or users depending on filesystem permissions.

Static analysis

No suspicious patterns detected.