Back to skill

Security audit

Google Analytics Insights

Security checks for vulnerabilities and agentic risk

Overview

This GA4 assistant has a real privacy and trust-boundary concern because setup asks users to grant ongoing read access to a fixed external service account while under-disclosing what that means.

Review carefully before installing. Do not connect sensitive or client GA4 properties unless you trust the publisher with ongoing read access. If already connected, consider removing ga-insights@plucky-engine-488015-d4.iam.gserviceaccount.com from GA4 Property Access Management and deleting local ~/.openclaw/ga-insights config/cache files. A safer design would use OAuth or a service account created and controlled by the user, with clear privacy, retention, and revocation documentation.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
ga_insights.py:37
Finding
Setup Grants GA4 Viewer Access to a Shared Third-Party Service Account<![CDATA[ ## Vulnerability Details **File Location**: `ga_insights.py:37-38, 107-110, 126-146`; `SKILL.md:42-50`; `QUICKSTART.md:3-17`; `README.md:5-12` **Vulnerability Type**: External account authorization and misleading authentication guidance **Risk Level**: High ### Complete Vulnerable Code and Instructions `ga_insights.py:37-38`: ```python DEFAULT_CREDENTIALS = str(Path.home() / ".openclaw" / "ga-insights-key.json") SERVICE_ACCOUNT_EMAIL = "ga-insights@plucky-engine-488015-d4.iam.gserviceaccount.com" ``` `ga_insights.py:107-110`: ```python creds = config.get("credentials_path") or DEFAULT_CREDENTIALS if os.path.exists(creds): os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = creds try: return BetaAnalyticsDataClient() ``` `ga_insights.py:126-146`: ```python return { "status": "setup_needed", "service_account_email": SERVICE_ACCOUNT_EMAIL, "steps": [ "1. Go to https://analytics.google.com/", "2. Admin (⚙) → Property Access Management", "3. Click + → Add users", f"4. Enter: {SERVICE_ACCOUNT_EMAIL}", "5. Role: Viewer → Save", "6. Admin → Property Settings → copy the numeric Property ID", "7. Tell me: 'ga connect <Property ID>'" ], "note": "The service account only has read access — your data stays private." } def complete_setup(property_id: str) -> dict: pid = property_id.strip().lstrip("properties/") config = load_config() config.update({"property_id": pid, "credentials_path": DEFAULT_CREDENTIALS, "connected": True}) ``` `SKILL.md:42-50`: ```markdown ## Quick Setup (2 Steps) 1. **Add our service account to your GA4:** - Go to Analytics → Admin → Property Access Management - Add: `ga-insights@plucky-engine-488015-d4.iam.gserviceaccount.com` - Role: Viewer 2. **Tell us your Property ID:** ``` `QUICKSTART.md:3-17`: ```markdown ## Step 1: Connect Your GA4 1. Go to [Google Analytics](https://analytics.google.com/) 2. Click **Admin** (gear icon, bot ...[truncated 3329 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the shared service-account address from all code and documentation. 2. Require each user to create a dedicated service account in a Google Cloud project that the user controls. 3. Prefer an OAuth 2.0 authorization flow with explicit read-only scopes, informed consent, and a documented revocation procedure. 4. If service-account authentication remains supported: - Explain how users create and download their own key. - Store the key with owner-only permissions. - Never request that users send the key through chat. - Recommend key rotation and deletion after compromise. 5. Clearly identify every external party capable of accessing analytics data. 6. Replace the statement that data “stays private” with an accurate explanation of the authorization boundary and data recipients. 7. Align documentation with implementation. Do not claim that no JSON credential is required while `get_client()` depends on one. 8. Advise existing users to remove the shared account from GA4 Property Access Management and review Google Cloud or GA4 access logs where available. 9. Validate connectivity before persisting `"connected": true`; roll back configuration when authentication or authorization fails. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
ga_insights.py:47
Finding
Sensitive Analytics Cache Is Written Without Explicitly Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `ga_insights.py:47-64` **Vulnerability Type**: Plaintext sensitive-data storage with inherited filesystem permissions **Risk Level**: Low ### Complete Vulnerable Code ```python def _load_cache() -> dict: try: if CACHE_FILE.exists(): with open(CACHE_FILE) as f: return json.load(f) except Exception: pass return {} def _save_cache(cache: dict): try: CONFIG_DIR.mkdir(parents=True, exist_ok=True) with open(CACHE_FILE, 'w') as f: json.dump(cache, f) except Exception: pass ``` The cache path is defined at `ga_insights.py:35-36`: ```python CONFIG_DIR = Path.home() / ".openclaw" / "ga-insights" CONFIG_FILE = CONFIG_DIR / "config.json" CACHE_FILE = CONFIG_DIR / "cache.json" ``` Realtime results written through this mechanism include country, device category, and active-user counts: ```python result = { "active_users": total, "breakdown": breakdown[:10], "status": "live", "insight": (f"🟢 {total} user{'s' if total != 1 else ''} on your site right now." if total else "No active users at this moment.") } cache_set("realtime", result) ``` ### Technical Analysis `_save_cache()` creates the cache directory and file without setting explicit security modes. The resulting permissions depend on the process umask and surrounding filesystem configuration. On a system with a permissive umask, other local users or processes may be able to read `cache.json`. The cache stores GA4-derived data in plaintext. Realtime results are JSON-serializable and can include current user counts, countries, and device categories. Other report functions also attempt to cache analytics results, although their inclusion of SDK response objects may cause JSON serialization to fail. Because every exception is silently suppressed, operators receive no warning when cache writes fail, are partial, or encounter permis ...[truncated 1308 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create `~/.openclaw/ga-insights` with mode `0700`. 2. Create both cache and configuration files with mode `0600`, independent of the current umask. 3. Write cache updates atomically: - Create a temporary file in the same protected directory. - Set mode `0600`. - Flush and synchronize the file where appropriate. - Replace the destination atomically with `os.replace()`. 4. Use file locking or another concurrency-safe cache mechanism when multiple processes can invoke the Skill. 5. Cache only the minimum fields needed and avoid persisting raw SDK response objects. 6. Consider disabling persistent caching for realtime, geographic, event, and conversion information. 7. Provide an option to disable disk caching entirely. 8. Replace broad silent exception handling with safe error reporting that does not disclose analytics data or credentials. 9. Validate cache structure, timestamps, and expected types before using cache entries. 10. Document the cache location, retained data, expiration behavior, and deletion command. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (12)

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The quickstart instructs users to grant an external service account direct Viewer access to their GA4 property, which exposes potentially sensitive analytics data to a third party without any disclosure of privacy, data handling, retention, or trust boundaries. Even read-only GA4 access can reveal business-sensitive traffic, campaign, conversion, audience, and behavioral data, so asking users to share it through a setup guide is a real security and privacy risk.

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill advertises behavior that likely relies on sensitive capabilities while omitting any explicit tool-scope or permissions declaration. That makes the execution boundary unclear to users and reviewers, increasing the risk of unintended environment access or file writes if the skill implementation or wrapper code uses those capabilities implicitly.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The skill invites broad natural-language requests such as general analytics questions, which can cause over-triggering in normal conversation and accidental invocation on unrelated user prompts. In a data-access skill tied to GA4, overly broad activation increases the chance of unintended disclosure or processing of analytics data without clear user intent.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The setup instructs users to grant an external service account Viewer access to their GA4 property but does not prominently warn that this gives a third party ongoing access to potentially sensitive business analytics. Users may not understand the trust, privacy, retention, and revocation implications, which is especially risky for agency or multi-client properties.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
## Pricing

- **Free tier**: 50 queries/month, 1 property
- **Pro ($9/mo)**: Unlimited queries, 5 properties, anomaly alerts
- **Business ($49/mo)**: Unlimited properties, API access

## Requirements
Confidence
80% confidence
Finding
Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The documentation states 'in-memory caching (5-10 min TTL)' but the implementation writes cached analytics data to a JSON file on disk. This mismatch is security-relevant because users may make trust decisions based on a false assumption that no local persistence occurs beyond process memory.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Google Analytics query results are serialized and written to a local cache file without user disclosure or consent. GA4 data can contain sensitive business intelligence such as traffic patterns, campaign performance, geography, and conversion behavior, so undisclosed persistence broadens the attack surface even in a local-only design.

External Transmission

Medium
Category
Data Exfiltration
Content
"status": "setup_needed",
        "service_account_email": SERVICE_ACCOUNT_EMAIL,
        "steps": [
            "1. Go to https://analytics.google.com/",
            "2. Admin (⚙) → Property Access Management",
            "3. Click + → Add users",
            f"4. Enter: {SERVICE_ACCOUNT_EMAIL}",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
"status": "setup_needed",
        "service_account_email": SERVICE_ACCOUNT_EMAIL,
        "steps": [
            "1. Go to https://analytics.google.com/",
            "2. Admin (⚙) → Property Access Management",
            "3. Click + → Add users",
            f"4. Enter: {SERVICE_ACCOUNT_EMAIL}",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The skill enables a catch-all natural-language entry point that forwards arbitrary user input directly to `python ga_insights.py query "{input}"` without any visible scope restriction, intent allowlist, or confirmation boundary. In an analytics-focused skill, broad NL activation increases the chance of unintended invocation, prompt/command bridging in downstream logic, or abuse of privileged data access through ambiguous queries.

Description-Behavior Mismatch

Low
Confidence
91% confidence
Finding
The skill handles potentially sensitive GA4 analytics data but persists query results to a local disk cache in ~/.openclaw/ga-insights/cache.json, which contradicts the user-facing expectation of a transient analytics assistant. Even if the data is not transmitted to a third party, local persistence increases exposure to other local users, backups, forensic recovery, or unrelated processes that can read the file.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The skill stores connection settings, including property ID and credentials path, in a local config file without disclosure. While this is less sensitive than storing raw credentials, it still exposes metadata about the user's analytics environment and may reveal file locations or linked properties to local attackers or other software.