Back to skill

Security audit

Google Workspace

Security checks for vulnerabilities and agentic risk

Overview

This skill appears intended to manage Google Workspace, but it handles credentials and high-impact Google actions with too little scoping and guardrail detail.

Review this skill before installing. Only use it if you trust MyBrandMetrics and the local gws CLI, set GWS_TOKEN_URL only to the official HTTPS token service, protect the API key file with restrictive permissions, and require explicit user confirmation before delete, trash, move, overwrite, or sharing-permission changes.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gws_wrapper.py:8
Finding

API Key Transmitted to an Unrestricted Runtime-Configured Endpoint

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/install_gws.sh:5
Finding

Unpinned and Integrity-Unverified Installation of an Executable Dependency

Content
View full analysis
/dev/null && ! [ -f "./scripts/gws_musl" ]; then echo "gws not found. Installing compatible musl binary..." if command -v go &> /dev/null; then go install github.com/googleworkspace/cli@latest else # Fallback: attempt to download latest release binary if go is missing echo "Go not found. Attempting to download compatible musl binary..." # We target the musl version specifically for better portability on older/different glibc environments VERSION=$(curl -s https://api.github.com/repos/googleworkspace/cli/releases/latest | grep "tag_name" | cut -d : -f 2 | tr -d \"\ ,) FILENAME="google-workspace-cli-x86_64-unknown-linux-musl.tar.gz" URL="https://github.com/googleworkspace/cli/releases/download/${VERSION}/${FILENAME}" curl -L -O "$URL" tar -xzf "$FILENAME" mv gws gws_musl chmod +x gws_musl rm "$FILENAME" fi ``` ### Technical Analysis Both installation branches select a mutable `latest` version. The Go branch invokes `go install ...@latest`, while the fallback obtains the latest release identifier from the GitHub API and downloads the corresponding archive. The script does not pin a reviewed release or Go module revision. It also does not verify a cryptographic checksum or trusted signature before extracting and marking the downloaded binary executable. HTTPS protects transport in ordinary conditions but does not protect against a compromised upstream account, malicious upstream release, repository takeover, or release artifact replacement. The wrapper later executes `gws` and provides it with a Google OAuth access token through `GOOGLE_WORKSPACE_CLI_TOKEN`, increasing the consequence of supply-chain compromise. ### ...[truncated 1199 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:67
Finding

Plaintext API-Key File Created Without Enforced Restrictive Permissions

Content
View full analysis
~/.google_workspace_api_key ``` ```python # scripts/gws_wrapper.py api_key_path = os.path.expanduser("~/.google_workspace_api_key") api_key = os.environ.get("GWS_SKILL_API_KEY") if not api_key and os.path.exists(api_key_path): with open(api_key_path, "r") as f: api_key = f.read().strip() ``` ### Technical Analysis The documented setup command writes a long-lived API key to a plaintext file using shell redirection. The resulting permissions depend on the user's current umask. On a system with a permissive umask, the file may be readable by the user's group or other local accounts. The wrapper reads the credential without checking that the file is owned by the current user, is a regular file rather than a symbolic link, or has restrictive permissions. It also does not warn about or reject an insecure mode. Local plaintext storage may be necessary for unattended use, but allowing uncontrolled file permissions is not necessary for the declared functionality. ### Attack Path 1. The user follows the setup command while operating under a permissive umask. 2. `~/.google_workspace_api_key` is created with group-readable or world-readable permissions. 3. Another local user or a process running under a different account reads the file. 4. The attacker copies the MyBrandMetrics API key. 5. The attacker reuses the key against the legitimate token service, subject to its authentication and authorization controls. 6. If the key can obtain tokens for connected data sources, the attacker accesses the corresponding Google Workspace resources. An additional local attack is possible if an attacker can pre-create or replace the expected path with a symbolic link or otherwise manipulate the user ...[truncated 568 chars]
Remediation
View remediation
~/.google_workspace_api_key chmod 600 ~/.google_workspace_api_key ``` Alternatively, use: ```bash install -m 600 /dev/null ~/.google_workspace_api_key ``` and populate the file through a non-logging credential prompt. 2. Before reading the file, use `os.lstat()` to reject symbolic links and non-regular files. 3. Verify that the file is owned by the effective user. 4. Reject or repair files with group or other permission bits set. 5. Avoid placing the key directly in shell command history. Provide an interactive prompt using hidden input or integrate with an operating-system credential store. 6. Document credential rotation procedures for cases where insecure permissions are detected. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (10)

Tainted flow: 'TOKEN_URL' from os.environ.get (line 8, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The code sends the API key in a custom header to a URL taken directly from the GWS_TOKEN_URL environment variable without any allowlisting or validation. If that environment variable is altered, secrets can be exfiltrated to an attacker-controlled endpoint and a malicious token can be returned for later use.

Content

Scanner excerpt · scripts/gws_wrapper.py (reported line 24)May include surrounding context.

python
data = {"source_key": source_key}
    
    try:
        response = requests.post(TOKEN_URL, headers=headers, json=data)
        response.raise_for_status()
        return response.json().get("access" + "_token")
    except Exception as e:

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 34)May include surrounding context.

md
| --- | --- |
| Sign in to MyBrandMetrics with Google. | Create or configure a Google Cloud project. |
| Connect Google Calendar, Google Drive, and Google Sheets as data sources. | Configure OAuth clients, scopes, and local MCP server auth. |
| Use a MyBrandMetrics API key to fetch managed access tokens. | Manage credentials and token storage yourself. |
| Run with a local wrapper around `gws`; no MCP server required. | Requires running and maintaining an MCP server. |

Use this when you want Google Workspace actions without setting up your own

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · SKILL.md (reported line 49)May include surrounding context.

md
| --- | --- |
| Sign in to MyBrandMetrics with Google. | Create or configure a Google Cloud project. |
| Connect Google Calendar, Google Drive, and Google Sheets as data sources. | Configure OAuth clients, scopes, and local MCP server auth. |
| Use a MyBrandMetrics API key to fetch managed access tokens. | Manage credentials and token storage yourself. |
| Run with a local wrapper around `gws`; no MCP server required. | Requires running and maintaining an MCP server. |

Use this when you want Google Workspace actions without setting up your own

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
74% confidence
Finding

Copying the full parent environment into a child process is not inherently malicious, but it increases the blast radius of any compromised or trojaned gws binary by exposing all inherited environment secrets in addition to the access token. In this skill, that risk is amplified because the subprocess is an external CLI whose path may be influenced by the local environment.

Content

Scanner excerpt · scripts/gws_wrapper.py (reported line 65)May include surrounding context.

python
token = get_token(source_key, api_key)

    # Prepare environment
    env = os.environ.copy()
    env["GOOGLE_WORKSPACE_CLI_TOKEN"] = token

    # Execute gws

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill advertises capabilities that imply shell, network, environment, and file access, but it does not declare any explicit tool scope or permissions boundaries. This creates an over-privileged integration pattern where an agent may invoke sensitive local or networked operations without clear least-privilege constraints, increasing the risk of unintended command execution, local file access, or exfiltration.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The skill supports destructive actions such as deleting calendar events, deleting or trashing Drive files, moving files, and modifying sharing permissions, but it does not prominently warn users or require confirmation before those operations. In an agent-driven workflow, missing guardrails around destructive actions can lead to accidental data loss or unauthorized sharing from ambiguous natural-language prompts.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
88% confidence
Finding

This file transmits sensitive material off-host: the MyBrandMetrics API key is sent over the network to obtain an access token. External transmission is part of the intended design, but it remains security-sensitive because compromise of the destination or misconfiguration of the URL exposes credentials.

Content

Scanner excerpt · scripts/gws_wrapper.py (reported line 24)May include surrounding context.

python
data = {"source_key": source_key}
    
    try:
        response = requests.post(TOKEN_URL, headers=headers, json=data)
        response.raise_for_status()
        return response.json().get("access" + "_token")
    except Exception as e:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
77% confidence
Finding

The wrapper resolves gws by calling which via PATH and, if found, later executes gws by name. In a compromised or attacker-controlled environment, PATH hijacking could cause a malicious which or gws binary to be executed, inheriting the freshly obtained access token via environment variables.

Content

Scanner excerpt · scripts/gws_wrapper.py (reported line 75)May include surrounding context.

python
try:
        # Check if gws is in PATH, if not try the local scripts dir
        if subprocess.call(["which", "gws"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) != 0:
            local_gws = os.path.join(os.path.dirname(__file__), "gws_musl")
            if os.path.exists(local_gws):
                cmd[0] = local_gws

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/gws_wrapper.py (reported line 80)May include surrounding context.

python
if os.path.exists(local_gws):
                cmd[0] = local_gws

        result = subprocess.run(cmd, env=env, capture_output=False)
        sys.exit(result.returncode)
    except Exception as e:
        print(f"Error executing gws: {e}", file=sys.stderr)

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/install_gws.sh (reported line 14)May include surrounding context.

sh
# Fallback: attempt to download latest release binary if go is missing
        echo "Go not found. Attempting to download compatible musl binary..."
        # We target the musl version specifically for better portability on older/different glibc environments
        VERSION=$(curl -s https://api.github.com/repos/googleworkspace/cli/releases/latest | grep "tag_name" | cut -d : -f 2 | tr -d \"\ ,)
        FILENAME="google-workspace-cli-x86_64-unknown-linux-musl.tar.gz"
        URL="https://github.com/googleworkspace/cli/releases/download/${VERSION}/${FILENAME}"

Static analysis

No suspicious patterns detected.