Skip to content

feat(google-analytics): add allowedPropertyIds to restrict property access#481

Open
igoramf wants to merge 5 commits into
mainfrom
feat/google-analytics-allowed-property-ids
Open

feat(google-analytics): add allowedPropertyIds to restrict property access#481
igoramf wants to merge 5 commits into
mainfrom
feat/google-analytics-allowed-property-ids

Conversation

@igoramf

@igoramf igoramf commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds allowedPropertyIds field to the Google Analytics MCP StateSchema
  • When configured, restricts all tools to only operate on the listed GA4 properties
  • get-account-summaries filters accounts/properties not in the allowlist
  • resolveProperty enforces the restriction on every other tool call (run-report, run-funnel-report, run-realtime-report, get-property-details, etc.)
  • Backward compatible: leaving allowedPropertyIds empty preserves current behavior (all properties accessible)

How it works

Configure allowedPropertyIds in the Mesh UI for a GA installation:

["properties/258313733", "properties/123456"]

Any tool call targeting a property outside this list will return an error. get-account-summaries will only show accounts that have at least one allowed property.

Test plan

  • Install GA MCP without allowedPropertyIds — all accounts visible, all properties accessible (no regression)
  • Set allowedPropertyIds: ["properties/258313733"]get-account-summaries returns only FARM GA4 account
  • Call run-report with an unlisted property — receives error Property "..." is not allowed
  • Call run-report with an allowed property — works normally

🤖 Generated with Claude Code


Summary by cubic

Add an optional GA4 property allowlist to the Google Analytics integration. When allowedPropertyIds is set, tools only discover and operate on those properties; leaving it empty keeps current behavior.

  • New Features

    • Added allowedPropertyIds to StateSchema; accepts 1234567 or properties/1234567 and normalizes IDs.
    • get-account-summaries shows only allowed properties and hides accounts without any.
    • resolveProperty normalizes IDs and rejects any property not in the allowlist (sync).
  • Migration

    • Optional: set allowedPropertyIds in the Mesh UI, e.g. ["properties/258313733", "123456"].
    • No changes needed if you want to allow all accessible properties.

Written for commit a44901c. Summary will update on new commits.

Review in cubic

…property access

Adds a new `allowedPropertyIds` field to the StateSchema that, when
configured, restricts all tools to only operate on the listed GA4
properties. get-account-summaries filters out accounts/properties not
in the allowlist, and resolveProperty enforces the restriction on every
other tool call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread google-analytics/server/lib/env.ts Outdated
…countIds

Account-level restriction is simpler to configure (one ID per customer)
and automatically includes all properties under the account, including
new ones added in the future.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread google-analytics/server/lib/env.ts
igoramf and others added 3 commits July 1, 2026 13:55
resolveProperty is now async and, when allowedAccountIds is configured,
fetches the property's parent account from the GA4 Admin API to verify
it belongs to an allowed account. All tools updated to await the result.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…filtering

The extra getProperty() call in resolveProperty was unnecessary — the LLM
only discovers property IDs via get-account-summaries, which already filters
by allowedAccountIds. Reverts resolveProperty to a simple sync function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…yIds

Restrict access at the property level instead of account level.
- StateSchema: allowedAccountIds → allowedPropertyIds (numeric IDs accepted)
- env.ts: getAllowedPropertyIds normalizes "123" → "properties/123"; resolveProperty validates against allowlist synchronously
- accounts.ts: filters properties within each account summary, hides accounts with no allowed properties

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="google-analytics/server/lib/env.ts">

<violation number="1" location="google-analytics/server/lib/env.ts:14">
P2: `normalizePropertyId` lacks validation for blank or malformed entries, which makes allowlist enforcement brittle and can cause accidental total lockout. The codebase already has a strict `normalizeProperty` function in `ga-client.ts` that validates property IDs and throws on malformed input; `normalizePropertyId` should align with that behavior.

For example, an allowlist entry of `"   "` normalizes to `""`, and `"abc"` passes through unchanged. Because any non-empty allowlist array activates enforcement, a misconfigured list with only invalid entries silently denies all valid properties. Defensive validation (e.g., filtering out or rejecting invalid/blank entries during normalization) would prevent this operational failure mode.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

};

// Normalizes "1234567" → "properties/1234567"; already-prefixed IDs pass through.
function normalizePropertyId(id: string): string {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: normalizePropertyId lacks validation for blank or malformed entries, which makes allowlist enforcement brittle and can cause accidental total lockout. The codebase already has a strict normalizeProperty function in ga-client.ts that validates property IDs and throws on malformed input; normalizePropertyId should align with that behavior.

For example, an allowlist entry of " " normalizes to "", and "abc" passes through unchanged. Because any non-empty allowlist array activates enforcement, a misconfigured list with only invalid entries silently denies all valid properties. Defensive validation (e.g., filtering out or rejecting invalid/blank entries during normalization) would prevent this operational failure mode.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At google-analytics/server/lib/env.ts, line 14:

<comment>`normalizePropertyId` lacks validation for blank or malformed entries, which makes allowlist enforcement brittle and can cause accidental total lockout. The codebase already has a strict `normalizeProperty` function in `ga-client.ts` that validates property IDs and throws on malformed input; `normalizePropertyId` should align with that behavior.

For example, an allowlist entry of `"   "` normalizes to `""`, and `"abc"` passes through unchanged. Because any non-empty allowlist array activates enforcement, a misconfigured list with only invalid entries silently denies all valid properties. Defensive validation (e.g., filtering out or rejecting invalid/blank entries during normalization) would prevent this operational failure mode.</comment>

<file context>
@@ -10,20 +10,20 @@ export const getGoogleAccessToken = (env: Env): string => {
-// Normalizes "1234567" → "accounts/1234567"; already-prefixed IDs pass through.
-function normalizeAccountId(id: string): string {
+// Normalizes "1234567" → "properties/1234567"; already-prefixed IDs pass through.
+function normalizePropertyId(id: string): string {
   const clean = String(id).trim();
-  return /^\d+$/.test(clean) ? `accounts/${clean}` : clean;
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant