Back to skill

Security audit

HML Google Slides

Security checks for vulnerabilities and agentic risk

Overview

This Google Slides helper is mostly purpose-focused, but it needs review because it handles Google OAuth tokens unsafely and asks for broader Google account access than Slides work requires.

Review before installing. Use only with an account you explicitly choose, avoid the documented broad re-auth command, and revoke/reissue any token if /tmp/gog_slides_token.json may already exist. The batch command can make broad or destructive changes to a presentation, and comment resolution writes to Drive comment threads.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/slides.py:21
Finding
OAuth Refresh Token Exported to a Predictable Persistent Temporary File<![CDATA[ ## Vulnerability Details **File Location**: `scripts/slides.py`, lines 21–39 **Vulnerability Type**: Unsafe temporary-file handling and plaintext credential exposure **Risk Level**: High ### Vulnerable Code ```python GOG_CREDS = os.path.expanduser("~/.config/gogcli/credentials.json") TOKEN_TMP = "/tmp/gog_slides_token.json" ACCOUNT = os.environ.get("GOG_ACCOUNT", "david@hml.tech") def get_creds(): from google.oauth2.credentials import Credentials from google.auth.transport.requests import Request result = subprocess.run( ["gog", "auth", "tokens", "export", ACCOUNT, "--out", TOKEN_TMP, "--overwrite"], capture_output=True, text=True ) if result.returncode != 0: print(f"Error exporting token: {result.stderr}", file=sys.stderr) sys.exit(1) with open(TOKEN_TMP) as f: token_data = json.load(f) ``` ### Technical Analysis The script exports an OAuth refresh token to the fixed path `/tmp/gog_slides_token.json`. This filename is predictable, located in a commonly shared temporary directory, and reused with `--overwrite`. The script does not create the file itself with an explicitly restrictive mode, verify that the destination is a regular file owned by the current user, reject symbolic links, or remove the token file after loading it. The actual file permissions and overwrite protections may depend on the behavior of the external `gog` command, but this script does not independently enforce them. A refresh token is long-lived credential material. Leaving it on disk after the process completes unnecessarily expands the time during which another local process, another user where permissions permit, malware, backup software, or diagnostic tooling could obtain it. Reusing one fixed path also creates race and cross-invocation hazards. Reading `~/.config/gogcli/credentials.json` is relevant to the current authentication implementation because the script needs the OAuth client ID and clien ...[truncated 2001 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer a supported `gog` API, standard Google credential provider, operating-system keychain, or secure token broker that returns credentials without exporting a refresh token to a filesystem path. 2. If a temporary file is unavoidable, create a unique file in a private directory using `tempfile.NamedTemporaryFile`, `tempfile.mkstemp`, or an equivalent secure primitive. 3. Create the file with permissions limited to the current user, such as mode `0600`, and ensure its parent directory is not accessible to other users. 4. Do not use a fixed filename in a shared directory. 5. Validate that the temporary object is a regular file owned by the expected user and do not follow symbolic links. 6. Read the token immediately and remove the temporary file in a `finally` block so cleanup occurs on success and failure. 7. Avoid printing token contents or exception objects that could include credentials. 8. Revoke and reissue any tokens that may already have been exposed through retained temporary files. 9. Reduce the token's OAuth scopes to the minimum needed for the requested operation, limiting the impact of any future disclosure. A safer implementation pattern is: ```python import os import tempfile token_path = None try: fd, token_path = tempfile.mkstemp(prefix="gog-slides-", suffix=".json") os.close(fd) os.chmod(token_path, 0o600) result = subprocess.run( [ "gog", "auth", "tokens", "export", ACCOUNT, "--out", token_path, "--overwrite" ], capture_output=True, text=True, check=False, ) if result.returncode != 0: raise RuntimeError("Token export failed") with open(token_path, encoding="utf-8") as token_file: token_data = json.load(token_file) finally: if token_path is not None: try: os.remove(token_path) except FileNotFoundError: pass ``` This pattern reduces, but does not elimina ...[truncated 64 chars]

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
SKILL.md:17
Finding
Authentication Instructions Request Unrelated Google Service Permissions<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, line 17 **Vulnerability Type**: Excessive OAuth permissions and violation of least privilege **Risk Level**: Medium ### Vulnerable Code ```bash gog auth add david@hml.tech --services gmail,calendar,drive,docs,sheets,contacts,tasks,people ``` ### Technical Analysis The Skill's declared functionality is creating, reading, editing, commenting on, and exporting Google Slides presentations. Slides operations and presentation comments may legitimately require narrowly selected Google Slides and Drive permissions. The documented re-authentication command additionally requests access associated with Gmail, Calendar, Docs, Sheets, Contacts, Tasks, and People. Most of those services are unrelated to the declared presentation-management functionality. Requesting all of them together violates the principle of least privilege and unnecessarily increases the value and impact of the resulting OAuth credential. The exact OAuth scopes granted depend on how `gog` maps each service name and on the user's consent. Nevertheless, the service list explicitly requests a significantly broader authorization set than presentation operations require. ### Attack Path 1. Authentication fails or has not yet been configured. 2. The user follows the re-authentication command in `SKILL.md`. 3. The user grants the permissions requested by `gog`, including access to unrelated Google services. 4. The resulting refresh token represents this broader authorization set. 5. The credential is subsequently exposed through the predictable temporary token file, compromised local storage, malware, logs, or another credential-theft mechanism. 6. The attacker exchanges the refresh token for access tokens. 7. Instead of being limited to presentation functionality, the attacker can attempt operations against every service and scope granted during authentication. This issue increases the consequences of credential compromise rather than i ...[truncated 626 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the broad re-authentication command with one that requests only the services and OAuth scopes required for Google Slides operations. 2. Use narrowly scoped Slides permissions for presentation creation and modification. 3. Add only the minimum Drive permission needed for export and comment functionality. Where supported, prefer per-file scopes such as `drive.file` over unrestricted Drive access. 4. Do not request Gmail, Calendar, Sheets, Contacts, Tasks, or People access for the core Slides workflow. 5. If optional functionality later requires another service, document it separately and request additional authorization only when the user invokes that feature. 6. Explain the purpose of each requested permission before asking the user to grant it. 7. Use separate credentials or incremental authorization for unrelated Google-service features. 8. Revoke existing broadly scoped authorizations and re-authenticate with the reduced scope set after correcting the documentation. 9. Verify the exact scopes to which each `gog --services` value maps before publishing a replacement command, because service aliases may grant broader permissions than expected. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared purpose focuses on creating, editing, reading, and exporting Slides, but the skill also includes comment listing and comment resolution/reply operations through Drive comment resources. This expands the effective access surface to collaboration metadata and communications that a user may not anticipate, increasing the risk of unauthorized data access or modification.

Credential Access

High
Category
Privilege Escalation
Content
import subprocess
import argparse

GOG_CREDS = os.path.expanduser("~/.config/gogcli/credentials.json")
TOKEN_TMP = "/tmp/gog_slides_token.json"
ACCOUNT = os.environ.get("GOG_ACCOUNT", "david@hml.tech")
Confidence
90% confidence
Finding
The script directly accesses stored OAuth client credentials and combines them with exported refresh-token material to obtain active Google API access. In a skill context, that means the code has credential-handling capability that could expose or misuse sensitive authentication artifacts if the host or surrounding workflow is compromised.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill exposes shell, environment, and network-capable behavior but does not declare any explicit tool scope or permission boundaries. That makes it easier for an agent or operator to invoke broader capabilities than users may reasonably expect, especially given the ability to authenticate, export files, and call local scripts.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The skill hard-codes a specific email account identity, which can cause actions to run against an unintended account without user selection. In a multi-account or shared environment, this could lead to unauthorized access, data modification, or exports from the wrong user's Google workspace.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The re-auth instruction requests broad Google service access across gmail, calendar, drive, docs, sheets, contacts, tasks, and people, even though the skill is for Slides. Requesting unnecessary scopes without a clear warning increases blast radius if tokens are misused and may grant access to unrelated private account data.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
Guidance to set GOG_ACCOUNT to a specific identity creates persistent default-account behavior without user opt-in. That makes accidental use of the wrong account more likely across future commands, especially for read/write actions and token-backed API access.

Session Persistence

Medium
Category
Rogue Agent
Content
Google Slides API helper - uses gog's stored OAuth credentials.

Usage:
  python3 slides.py create "My Presentation"
  python3 slides.py info <presentationId>
  python3 slides.py add-slide <presentationId> --title "Slide Title" --body "Bullet 1\nBullet 2"
  python3 slides.py batch <presentationId> requests.json
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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
from google.oauth2.credentials import Credentials
    from google.auth.transport.requests import Request
    
    result = subprocess.run(
        ["gog", "auth", "tokens", "export", ACCOUNT, "--out", TOKEN_TMP, "--overwrite"],
        capture_output=True, text=True
    )
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script exports OAuth token material to a predictable file in /tmp, which is an unsafe location for sensitive credentials on multi-user systems. Even if file permissions are usually restrictive, using a static world-discoverable path increases the chance of token exposure, race conditions, or accidental reuse by other processes.

Tainted flow: 'ACCOUNT' from os.environ.get (line 23, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
from google.oauth2.credentials import Credentials
    from google.auth.transport.requests import Request
    
    result = subprocess.run(
        ["gog", "auth", "tokens", "export", ACCOUNT, "--out", TOKEN_TMP, "--overwrite"],
        capture_output=True, text=True
    )
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

Medium
Confidence
91% confidence
Finding
The skill claims to focus on Google Slides creation, editing, reading, and export, but it also exposes Drive comment listing and comment resolution capabilities. That mismatch expands the authority surface beyond the advertised scope, which can surprise calling agents and enable unintended access to discussion metadata and workflow actions.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The generic batch endpoint accepts arbitrary JSON requests and forwards them directly to the Slides API, allowing many more mutations than the narrower advertised operations like add/update slides. In an agent setting, this is dangerous because it creates an unbounded write primitive that can restructure, delete, or alter presentations in unexpected ways.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This function performs arbitrary JSON-driven write operations against a presentation without any guardrails, confirmation, or user-facing disclosure of the breadth of possible changes. In an agent-executed context, that raises the risk of silent destructive or policy-violating edits when untrusted input is mapped into the request file.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def cmd_export(args):
    """Export presentation to PDF or PPTX using gog."""
    out = args.out or f"/tmp/{args.presentation_id}.{args.format}"
    result = subprocess.run(
        ["gog", "slides", "export", args.presentation_id,
         "--format", args.format, "--out", out],
        capture_output=True, text=True
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The markdown documents exporting presentations to `/tmp/deck.pdf` and `/tmp/deck.pptx`, which writes files to the local filesystem. The skill description does not include any warning that it will create or overwrite local output files or advise users to verify the destination path.

Static analysis

No suspicious patterns detected.