Skill flagged — suspicious patterns detected

ClawHub Security flagged this skill as suspicious. Review the scan results before using.

Google Drive Skill

Use when the user wants to interact with a public Google Drive — listing, reading, creating, updating, or deleting files and folders. Handles both read-only...

MIT-0 · Free to use, modify, and redistribute. No attribution required.
0 · 79 · 0 current installs · 0 all-time installs
MIT-0
Security Scan
VirusTotalVirusTotal
Suspicious
View report →
OpenClawOpenClaw
Suspicious
high confidence
Purpose & Capability
Name/description align with included Python scripts (list, download, upload, update, delete, permissions). The scripts legitimately require either a GOOGLE_API_KEY (read-only) or a GOOGLE_SERVICE_ACCOUNT_JSON (read+write). However, the registry metadata declares no required environment variables or primary credential, which is inconsistent with the actual code.
Instruction Scope
SKILL.md and the scripts stay within Drive and local filesystem operations (CRUD, permissions, download/upload). The SKILL.md includes a SKILL_SCRIPTS resolution shell snippet that attempts to locate the scripts by inspecting the git workspace and running find across $HOME and /opt; this is not malicious but it will probe the user's filesystem and may be surprising. Scripts rely on environment variables and read/write local files the user points them at (expected), but a user should be aware these scripts can permanently delete Drive content and make files public.
Install Mechanism
There is no install spec or remote download; the package is instruction + included Python scripts. Dependencies are standard Python Google API libraries and are installed via pip per the docs. No external URLs, archive extraction, or remote code execution were found.
!
Credentials
The code requires GOOGLE_API_KEY (read-only public access) and/or GOOGLE_SERVICE_ACCOUNT_JSON (path to a service account JSON) and uses the full https://www.googleapis.com/auth/drive scope for write operations. Those credentials are appropriate for the claimed functionality, but the skill registry metadata lists no required env vars or primary credential — an important discrepancy. Because a service account file grants broad Drive access, users must ensure least-privilege keys and avoid storing private keys in the repository.
Persistence & Privilege
The skill is not marked always:true and does not attempt to modify other skills or system-wide agent settings. It runs as user-invoked scripts; autonomous invocation is allowed by default but is not combined here with other privilege escalation signals.
What to consider before installing
This skill's code matches its description, but the registry metadata failed to declare the credentials the scripts actually need — exercise caution. Before installing or running: (1) verify you trust the skill owner and review the service_account_template.json and each script (they will accept a service account file granting full Drive access); (2) never commit service-account JSON into your repo and keep it outside the skill folder; (3) if you only need read-only access, prefer using an API key and the read-only flows; (4) be aware the SKILL.md's SKILL_SCRIPTS resolution runs a find over $HOME and /opt (may scan your filesystem) — run it manually on your terms or adjust it; (5) restrict service-account scopes and rotate keys, and test in a non-production account before granting access to important drives. Also ask the publisher to update the registry metadata to list GOOGLE_API_KEY and GOOGLE_SERVICE_ACCOUNT_JSON as required env vars/primary credential so the permissions are transparent.

Like a lobster shell, security has layers — review code before you run it.

Current versionv0.1.0
Download zip
latestvk973pcn489k8t2hjbnedjab9pn837hpm

License

MIT-0
Free to use, modify, and redistribute. No attribution required.

SKILL.md

Google Drive Skill

Resolve SKILL_SCRIPTS

IMPORTANT: All commands below use SKILL_SCRIPTS as shorthand for the absolute path to the scripts/ directory of this skill. Resolve it before running any script:

SKILL_SCRIPTS="$(
  _ws_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
  _ws_path="$_ws_root/.github/skills/google-drive-skill/scripts"
  _global_path="$HOME/.openclaw/workspace/skills/google-drive-skill/scripts"
  _global_path2="$HOME/.openclaw/skills/google-drive-skill/scripts"

  if [ -d "$_ws_path" ]; then
    echo "$_ws_path"
  elif [ -d "$_global_path" ]; then
    echo "$_global_path"
  elif [ -d "$_global_path2" ]; then
    echo "$_global_path2"
  else
    find "$HOME" /opt -type d -name google-drive-skill -path '*/skills/*' 2>/dev/null \
      | head -1 | sed 's|$|/scripts|'
  fi
)"
echo "SKILL_SCRIPTS=$SKILL_SCRIPTS"

Overview

This skill provides patterns and ready-to-run scripts for CRUD operations on Google Drive using the Drive API v3.

ScriptOperationAuth Required
list_files.pyList files/folders in a folderAPI key (public) or service account
download_file.pyDownload a file to diskAPI key (public) or service account
create_folder.pyCreate a new folderService account
upload_file.pyUpload a local fileService account
update_file.pyRename, move, or replace contentService account
delete_file.pyTrash or permanently deleteService account
set_permissions.pyMake public / share / revokeService account

Supporting files:

  • scripts/drive_client.py — shared auth factory (imported by all scripts)
  • assets/service_account_template.json — template for your service account JSON
  • references/mime_types.md — MIME type quick reference
  • references/error_codes.md — HTTP error codes + retry patterns

Quick Start

1. Install dependencies

pip install google-api-python-client google-auth google-auth-httplib2

2. Set credentials

# Read-only (public files):
export GOOGLE_API_KEY="your_api_key_here"

# Read + write (service account):
export GOOGLE_SERVICE_ACCOUNT_JSON="/path/to/service_account.json"

See assets/service_account_template.json for the expected JSON structure.

3. Resolve SKILL_SCRIPTS (see top of this file), then run scripts


Script Commands

List files in a folder

# Table output (default):
python "$SKILL_SCRIPTS/list_files.py" --folder-id FOLDER_ID

# JSON output:
python "$SKILL_SCRIPTS/list_files.py" --folder-id FOLDER_ID --json

Download a file

# Save to specific path:
python "$SKILL_SCRIPTS/download_file.py" --file-id FILE_ID --dest ./downloads/report.pdf

# Save to directory (auto filename from Drive):
python "$SKILL_SCRIPTS/download_file.py" --file-id FILE_ID --dest ./downloads/

Create a folder

# In Drive root:
python "$SKILL_SCRIPTS/create_folder.py" --name "My New Folder"

# Inside a specific parent folder:
python "$SKILL_SCRIPTS/create_folder.py" --name "Subfolder" --parent-id PARENT_FOLDER_ID

Upload a file

# Basic upload (MIME type auto-detected):
python "$SKILL_SCRIPTS/upload_file.py" --src ./report.pdf --parent-id FOLDER_ID

# Custom name on Drive:
python "$SKILL_SCRIPTS/upload_file.py" --src ./report.pdf --name "Q1-Report.pdf" --parent-id FOLDER_ID

# Upload and make publicly readable immediately:
python "$SKILL_SCRIPTS/upload_file.py" --src ./photo.png --parent-id FOLDER_ID --make-public

Update a file

# Rename:
python "$SKILL_SCRIPTS/update_file.py" --file-id FILE_ID --name "New Name.pdf"

# Move to a different folder:
python "$SKILL_SCRIPTS/update_file.py" --file-id FILE_ID --move-to NEW_PARENT_ID --old-parent OLD_PARENT_ID

# Replace file content:
python "$SKILL_SCRIPTS/update_file.py" --file-id FILE_ID --src ./updated_report.pdf

# Rename + replace content:
python "$SKILL_SCRIPTS/update_file.py" --file-id FILE_ID --name "v2.pdf" --src ./v2.pdf

Delete or trash a file

# Move to trash (safe, recoverable):
python "$SKILL_SCRIPTS/delete_file.py" --file-id FILE_ID

# Permanently delete (prompts for confirmation):
python "$SKILL_SCRIPTS/delete_file.py" --file-id FILE_ID --permanent

# Permanently delete (non-interactive):
python "$SKILL_SCRIPTS/delete_file.py" --file-id FILE_ID --permanent --yes

Manage permissions

# Make file publicly readable (anyone with link):
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --public

# Share with a user as writer:
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --email user@example.com --role writer

# Share with a user as reader:
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --email user@example.com --role reader

# List current permissions (returns JSON):
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --list

# Remove a specific permission:
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --remove PERMISSION_ID

Auth Setup

Option A — API Key (read-only public files)

Use when the file/folder is shared as "Anyone with the link" and you only need to read.

from googleapiclient.discovery import build

API_KEY = os.environ["GOOGLE_API_KEY"]
drive = build("drive", "v3", developerKey=API_KEY)

Option B — Service Account (read + write on shared/public drives)

Use when you need to create, modify, or delete files.

from google.oauth2 import service_account
from googleapiclient.discovery import build
import os

SCOPES = ["https://www.googleapis.com/auth/drive"]
creds = service_account.Credentials.from_service_account_file(
    os.environ["GOOGLE_SERVICE_ACCOUNT_JSON"], scopes=SCOPES
)
drive = build("drive", "v3", credentials=creds)

Copy assets/service_account_template.json to see the expected structure.
Store the real file outside the repo and never commit it.
Add service_account.json to .gitignore.


File & Folder ID

Every Drive resource has a unique fileId. Extract it from the share URL:

https://drive.google.com/drive/folders/FOLDER_ID_HERE
https://drive.google.com/file/d/FILE_ID_HERE/view

CRUD Operations

List Files in a Public Folder

def list_files(drive, folder_id: str) -> list[dict]:
    results = []
    page_token = None
    while True:
        resp = drive.files().list(
            q=f"'{folder_id}' in parents and trashed=false",
            fields="nextPageToken, files(id, name, mimeType, size, modifiedTime)",
            pageToken=page_token,
            supportsAllDrives=True,
            includeItemsFromAllDrives=True,
        ).execute()
        results.extend(resp.get("files", []))
        page_token = resp.get("nextPageToken")
        if not page_token:
            break
    return results

Download a Public File

import io
from googleapiclient.http import MediaIoBaseDownload

def download_file(drive, file_id: str, dest_path: str) -> None:
    request = drive.files().get_media(fileId=file_id, supportsAllDrives=True)
    with open(dest_path, "wb") as fh:
        downloader = MediaIoBaseDownload(fh, request)
        done = False
        while not done:
            _, done = downloader.next_chunk()

Create a Folder

def create_folder(drive, name: str, parent_id: str | None = None) -> str:
    metadata = {
        "name": name,
        "mimeType": "application/vnd.google-apps.folder",
    }
    if parent_id:
        metadata["parents"] = [parent_id]
    folder = drive.files().create(
        body=metadata,
        fields="id",
        supportsAllDrives=True,
    ).execute()
    return folder["id"]

Upload / Create a File

from googleapiclient.http import MediaFileUpload

def upload_file(
    drive,
    local_path: str,
    name: str,
    parent_id: str | None = None,
    mime_type: str = "application/octet-stream",
) -> str:
    metadata = {"name": name}
    if parent_id:
        metadata["parents"] = [parent_id]
    media = MediaFileUpload(local_path, mimetype=mime_type, resumable=True)
    file = drive.files().create(
        body=metadata,
        media_body=media,
        fields="id",
        supportsAllDrives=True,
    ).execute()
    return file["id"]

Upload from In-Memory Bytes

from googleapiclient.http import MediaIoBaseUpload

def upload_bytes(
    drive,
    data: bytes,
    name: str,
    parent_id: str | None = None,
    mime_type: str = "application/octet-stream",
) -> str:
    metadata = {"name": name}
    if parent_id:
        metadata["parents"] = [parent_id]
    media = MediaIoBaseUpload(io.BytesIO(data), mimetype=mime_type, resumable=True)
    file = drive.files().create(
        body=metadata,
        media_body=media,
        fields="id",
        supportsAllDrives=True,
    ).execute()
    return file["id"]

Update File Metadata (rename, move)

def rename_file(drive, file_id: str, new_name: str) -> None:
    drive.files().update(
        fileId=file_id,
        body={"name": new_name},
        supportsAllDrives=True,
    ).execute()

def move_file(drive, file_id: str, new_parent_id: str, old_parent_id: str) -> None:
    drive.files().update(
        fileId=file_id,
        addParents=new_parent_id,
        removeParents=old_parent_id,
        fields="id, parents",
        supportsAllDrives=True,
    ).execute()

Update File Content

def update_file_content(
    drive,
    file_id: str,
    local_path: str,
    mime_type: str = "application/octet-stream",
) -> None:
    media = MediaFileUpload(local_path, mimetype=mime_type, resumable=True)
    drive.files().update(
        fileId=file_id,
        media_body=media,
        supportsAllDrives=True,
    ).execute()

Delete a File or Folder

def delete_file(drive, file_id: str) -> None:
    drive.files().delete(fileId=file_id, supportsAllDrives=True).execute()

Deleting a folder also deletes all its children permanently. Prefer trashing:

drive.files().update(fileId=file_id, body={"trashed": True}).execute()

Set Permissions (Make Public)

To make a file publicly readable (anyone with the link):

def make_public(drive, file_id: str) -> None:
    drive.permissions().create(
        fileId=file_id,
        body={"role": "reader", "type": "anyone"},
        supportsAllDrives=True,
    ).execute()

To grant write access to a specific user:

def share_with_user(drive, file_id: str, email: str, role: str = "writer") -> None:
    # role: "reader", "commenter", "writer", "fileOrganizer", "organizer", "owner"
    drive.permissions().create(
        fileId=file_id,
        body={"role": role, "type": "user", "emailAddress": email},
        sendNotificationEmail=False,
        supportsAllDrives=True,
    ).execute()

Common MIME Types

ContentMIME Type
Folderapplication/vnd.google-apps.folder
Google Docapplication/vnd.google-apps.document
Google Sheetapplication/vnd.google-apps.spreadsheet
PDFapplication/pdf
Plain texttext/plain
JSONapplication/json
PNGimage/png
ZIPapplication/zip

Error Handling

from googleapiclient.errors import HttpError

def safe_delete(drive, file_id: str) -> bool:
    try:
        drive.files().delete(fileId=file_id, supportsAllDrives=True).execute()
        return True
    except HttpError as e:
        if e.resp.status == 404:
            return False  # already gone
        raise

Common HTTP status codes:

  • 400 — bad request (check field names, MIME types)
  • 403 — permission denied (check auth scope or file sharing settings)
  • 404 — file not found (check fileId, supportsAllDrives)
  • 429 — rate limited (back off and retry)

Required Python Packages

google-api-python-client>=2.100.0
google-auth>=2.23.0
google-auth-httplib2>=0.1.1

Install:

pip install google-api-python-client google-auth google-auth-httplib2

Security Notes

  • Never hardcode API keys or service account credentials in source code. Use environment variables or secret managers.
  • Restrict API key to the Drive API scope in the Google Cloud Console.
  • Service account credentials (service_account.json) must be in .gitignore.
  • When sharing files, prefer time-limited access tokens over permanent public links where possible.
  • "type": "anyone" with "role": "writer" on a production folder is dangerous — audit permissions regularly.

Reference

Local references (this skill):

Official docs:

Files

13 total
Select a file
Select a file to preview.

Comments

Loading comments…