Back to skill

Security audit

Ga4 Data Api

Security checks for vulnerabilities and agentic risk

Overview

This GA4 skill is purpose-aligned, but its installer has unsafe shell-startup modification and credential-storage risks users should review before installing.

Install only if you are comfortable reviewing or hardening the installer first. Use a trusted GA4 property ID, avoid running it with values supplied by someone else, consider manually storing GA4_PROPERTY_ID outside shell startup files, and restrict permissions on ~/.config/openclaw/ga4-client.json and ga4-token.json.

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/install_ga4_openclaw.sh:28
Finding
Persistent Shell Command Injection Through Unsanitized GA4 Property ID<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install_ga4_openclaw.sh:28-36` **Vulnerability Type**: Unsanitized input interpolation and shell startup-file injection **Risk Level**: High ### Vulnerable Code ```bash python3 - <<PY from pathlib import Path import re rc = Path("$SHELL_RC") text = rc.read_text() if rc.exists() else "" text = re.sub(r'^export GA4_PROPERTY_ID=.*\\n?', '', text, flags=re.M) if text and not text.endswith('\\n'): text += '\\n' text += 'export GA4_PROPERTY_ID="$PROPERTY_ID"\\n' rc.write_text(text) print(f"Updated {rc}") PY ``` ### Technical Analysis The installer directly interpolates the attacker-influenced `PROPERTY_ID` argument into an unquoted heredoc containing Python source. No validation restricts this argument to the numeric format expected for a GA4 property ID. This creates two related injection opportunities: 1. A value containing Python string delimiters and suitable syntax can alter the generated Python program and execute arbitrary Python code during installation. 2. A value containing shell syntax, such as command substitution, can be written literally into `.bashrc` or `.zshrc`. When a later interactive shell parses that startup file, the injected shell expression is executed. For example, a property ID containing a command substitution expression can result in a startup-file entry conceptually equivalent to: ```bash export GA4_PROPERTY_ID="$(attacker-controlled-command)" ``` Because the installer deliberately modifies a persistent shell initialization file, exploitation can survive the original installation process. ### Attack Path 1. An attacker supplies or recommends a crafted value as the GA4 property ID. 2. The user or an Agent invokes the documented installer with that value as its first argument. 3. The installer embeds the value into generated Python source without validation or safe argument passing. 4. The crafted value either executes through Python during installation or causes a ...[truncated 803 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Validate the property ID before any use with a strict numeric allowlist: ```bash if [[ ! "$PROPERTY_ID" =~ ^[0-9]+$ ]]; then echo "Invalid GA4 property ID: expected digits only" >&2 exit 1 fi ``` - Do not interpolate shell variables into Python source. Pass values as command-line arguments or environment variables: ```bash python3 - "$SHELL_RC" "$PROPERTY_ID" <<'PY' import sys from pathlib import Path rc = Path(sys.argv[1]) property_id = sys.argv[2] PY ``` - Avoid modifying `.bashrc` or `.zshrc` when possible. Store the property ID in a dedicated configuration file with restrictive permissions and load it explicitly. - If a shell assignment must be generated, use a robust shell-escaping mechanism rather than manual quoting. - Write startup-file changes atomically and preserve the original file so the operation can be safely rolled back. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ga4_query.py:42
Finding
OAuth Credentials Stored Without Explicit Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/ga4_query.py:42-52` **Vulnerability Type**: Insecure storage of OAuth credentials **Risk Level**: Medium ### Vulnerable Code ```python def load_creds(client_secret: str, token_file: str): CONFIG_DIR.mkdir(parents=True, exist_ok=True) creds = None token_path = Path(token_file) if token_path.exists(): creds = Credentials.from_authorized_user_file(str(token_path), SCOPES) if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) if not creds or not creds.valid: flow = InstalledAppFlow.from_client_secrets_file(client_secret, SCOPES) creds = flow.run_local_server(port=0) token_path.write_text(creds.to_json()) return creds ``` Related installation code at `scripts/install_ga4_openclaw.sh:10-19`: ```bash CONFIG_DIR="$HOME/.config/openclaw" mkdir -p "$CONFIG_DIR" if [[ ! -f "$CLIENT_JSON" ]]; then echo "Client secret JSON not found: $CLIENT_JSON" exit 1 fi python3 -m pip install --user google-analytics-data google-auth-oauthlib google-auth-httplib2 cp "$CLIENT_JSON" "$CONFIG_DIR/ga4-client.json" ``` ### Technical Analysis The OAuth token is written with `Path.write_text()`, while the client configuration is copied with `cp`. Neither operation explicitly enforces a `0600` file mode, and the configuration directory is not explicitly restricted to `0700`. Consequently, effective permissions depend on the user’s current `umask` and any pre-existing directory or destination-file permissions. In a permissively configured or multi-user environment, another local account may be able to read the OAuth token. The token JSON can contain a reusable refresh token associated with the `analytics.readonly` scope. The script also accepts an arbitrary `--token-file` path and does not reject symbolic links or verify ownership before reading or overwriting the target. ### Attack Path 1. The Skill is installed or first ...[truncated 1180 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create the configuration directory with mode `0700` and verify that it is owned by the current user. - Set `umask 077` before creating or copying credential files. - Store both `ga4-client.json` and `ga4-token.json` with mode `0600`. - Write the token atomically through a securely created temporary file in the same directory, set its permissions, and then replace the destination. - Reject symbolic links and non-regular destination files, and verify ownership before reading or overwriting an existing token. - Consider using an operating-system credential store or keyring instead of a plaintext token file. - Example hardening logic: ```python CONFIG_DIR.mkdir(parents=True, exist_ok=True, mode=0o700) os.chmod(CONFIG_DIR, 0o700) flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW fd = os.open(token_path, flags, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as token_handle: token_handle.write(creds.to_json()) ``` - In the installer, use `install -m 600` instead of an unrestricted `cp` operation. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/install_ga4_openclaw.sh:18
Finding
Unpinned Runtime Dependency Installation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/install_ga4_openclaw.sh:18` **Vulnerability Type**: Unpinned third-party dependencies and non-reproducible installation **Risk Level**: Medium ### Vulnerable Code ```bash python3 -m pip install --user google-analytics-data google-auth-oauthlib google-auth-httplib2 ``` ### Technical Analysis The installer retrieves the latest available versions of three Python packages and their transitive dependencies at installation time. It does not use exact version constraints, a reviewed lock file, package hashes, or an isolated virtual environment. Although the package names are consistent with the intended Google Analytics functionality and there is no evidence that these named packages are malicious, the effective dependency set can change after the Skill has been reviewed. A compromised upstream release, malicious transitive dependency, or unexpected incompatible update would therefore be introduced automatically. Python package installation may execute package build or installation logic. As a result, dependency compromise can become code execution under the account running the installer. ### Attack Path 1. An upstream package or one of its transitive dependencies publishes a compromised or otherwise unsafe release. 2. A user invokes `install_ga4_openclaw.sh`. 3. `pip` resolves the newest available package set because no reviewed versions or hashes are specified. 4. The compromised package is downloaded and installed. 5. Malicious build or runtime code executes with the privileges of the installing user. This path depends on a supply-chain compromise or unsafe upstream update; the audited project does not itself host or select an explicitly malicious package. ### Impact Assessment A compromised dependency could execute arbitrary code with the privileges of the user running the installer. Potential consequences include theft of local credentials, modification of user files, interception of OAuth t ...[truncated 210 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin every direct dependency to a reviewed exact version. - Generate a lock file that includes all transitive dependencies and cryptographic hashes. - Install with hash enforcement, for example: ```bash python3 -m pip install --require-hashes -r requirements.lock ``` - Use a dedicated virtual environment rather than `pip install --user`, preventing the Skill from modifying the user’s shared Python package environment. - Periodically review and update the lock file through a controlled dependency-update process. - Perform vulnerability and provenance checks on locked packages before publishing updated Skill releases. - Where practical, use trusted package-index configuration explicitly and disable unexpected extra indexes to reduce dependency-confusion exposure. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding
The skill instructs the agent to run local scripts, copy OAuth client secrets into a user config directory, write environment configuration, and perform networked API access, but it declares no explicit tool scope or permissions. This creates an authorization gap: a user or orchestrator cannot reliably constrain file, environment, or network actions, increasing the risk of unintended secret handling, local file modification, or outbound access beyond what was expected.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script persists OAuth credentials to a token file on disk via token_path.write_text(creds.to_json()) without any warning, consent prompt, or permission hardening. These tokens can grant ongoing read access to GA4 data if another local user, process, backup system, or misconfigured filesystem can access the file, which is especially relevant because the skill is explicitly designed to access potentially sensitive internal or enterprise analytics data.

Session Persistence

Medium
Category
Rogue Agent
Content
PROPERTY_ID="$1"
CLIENT_JSON="$2"
CONFIG_DIR="$HOME/.config/openclaw"
mkdir -p "$CONFIG_DIR"

if [[ ! -f "$CLIENT_JSON" ]]; then
  echo "Client secret JSON not found: $CLIENT_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.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script copies an OAuth client secret JSON into a persistent config directory without warning the user or setting restrictive permissions. Even if this file is not as sensitive as an access token, it still contains credential material and may facilitate misuse, accidental sharing, or insecure backup/sync exposure.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The installer persistently modifies the user's shell startup file to set GA4_PROPERTY_ID, which changes future shell behavior outside the immediate install session. While the variable itself is not highly sensitive, silently writing to .bashrc/.zshrc expands the script's scope and creates a persistence mechanism that users may not expect from a GA4 setup helper.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The script injects configuration into unrelated shell initialization files rather than keeping settings scoped to the application. This is risky because shell RC files are privileged persistence points for user sessions, and normalizing this behavior in an installer makes future abuse easier and reduces user visibility into what changed.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The installer modifies the user's shell startup file without prior warning or consent, creating persistent environment changes that apply to future sessions. Although the exported value is only a property ID, the unsafe pattern is the unannounced persistence into a high-trust file, which can surprise users and be abused in similar scripts for more harmful values.

Static analysis

No suspicious patterns detected.