agentleFS
fourteenwmfourteenwm/ppc-ai-skills/mcc-hack-audit/SKILL.md

mcc-hack-audit

Skill72 starsChanged 4 months agoMITView on GitHub

Portfolio-wide scan of every manager (MCC) that has access to accounts in your Google Ads tree. Built after an MCC link-fraud incident where hostile external MCCs were found linked to client accounts via compromised admin credentials. Classifies each manager link as INTERNAL (in your tree), HOSTILE (known threat), or EXTERNAL (potential exposure — no judgment). Outputs CSV by default with optional Google Sheets upload. Auto-invoke when user says 'mcc hack audit', 'audit mcc links', 'scan for external managers', 'check manager links', 'run mcc link scan', 'find external mccs', or asks who has access to their Google Ads accounts.

The file

We split this skill into the rules it gives an agent

8 rules. About 0 are common, 3 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every account under your login_customer_id (walks customer_client) — including your root MCC itself

    line 24
  2. Very specific:

    Every manager-link relationship on each account (customer_manager_link) — all link statuses, not just active ones: a REFUSED or CANCELED …

    line 26
  3. Distinctive:

    All account statuses (ENABLED, CANCELED, SUSPENDED, CLOSED) — security-relevant data lives in canceled accounts too

    line 30
  4. Very specific:

    Output: every (account, manager) pair, plus a suspicious-only subset (HOSTILE + EXTERNAL).

    line 32
  5. Distinctive:

    Every non-internal manager is EXTERNAL — even an agency you've worked with for 10 years, even an MCC that's been linked since the account …

    line 48
  6. Very specific:

    If you'd rather not see long-standing trusted MCCs in the suspicious list, --trusted-cids classifies them TRUSTED instead — but that …

    line 52
  7. Very specific:

    Start empty. Populate as incidents occur — the escalation path in rules.md tells you when a CID earns an entry, and its "Sharing …

    line 116
  8. Distinctive:

    Check every surprise against the false-alarm table before escalating.

    line 127

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

MCC Hack Audit

Owns one workflow: walk your entire Google Ads tree and map every manager (MCC) with a link into it — then hand the operator a triage-ready answer to "which MCCs currently have access to which of my accounts, and which are outside my own tree?" Built after a real-world MCC link-fraud incident where hostile external MCCs gained access to client accounts via compromised client-side admin credentials.

The triage judgment — which links matter, when a finding becomes an incident, what "trusted" costs — is rules.md. The exact walk/classification/output mechanics and the hard-won API limitations are references/scan-contract.md.

What It Scans

Included:

  • Every account under your login_customer_id (walks customer_client) — including your root MCC itself
  • Every manager-link relationship on each account (customer_manager_link) — all link statuses, not just active ones: a REFUSED or CANCELED link is an attempt record, and attempt records are security data
  • All account statuses (ENABLED, CANCELED, SUSPENDED, CLOSED) — security-relevant data lives in canceled accounts too

Output: every (account, manager) pair, plus a suspicious-only subset (HOSTILE + EXTERNAL). Full column layout, sort orders, and the Sheets-tab contract: references/scan-contract.md.

Classification Model — No Judgment

Three classifications, defined by objective ownership only (precedence mechanics in the contract):

ClassDefinition
INTERNALmanager_cid is inside your own MCC tree (auto-detected by walking customer_client)
HOSTILEmanager_cid matches an entry in your hostile-MCC list (you populate this)
EXTERNALAnything else. Potential exposure point regardless of name/age/footprint. Never auto-classified as legitimate.

Critical: There is no "allowlist" or "trusted partner" concept by design. Every non-internal manager is EXTERNAL — even an agency you've worked with for 10 years, even an MCC that's been linked since the account was created. The point is identification, not pre-clearance. You judge.

If you'd rather not see long-standing trusted MCCs in the suspicious list, --trusted-cids classifies them TRUSTED instead — but that suppresses visibility, not attack surface (rules invariant: re-audit trusted CIDs on the same cadence).

Deliberately does NOT do

  • No link removal — revocation is a manual UI action, by design.
  • No outreach — no client emails, no Slack, no notifications. All communication is your call.
  • No auto-judgment — nothing is ever classified "legitimate"; EXTERNAL stays EXTERNAL until you verdict it.
  • No watchdog mode — manual runs only; the cross-day CSV diff is the manual watchdog pattern (rules.md, cadence).
  • No name resolution for external CIDs — the API refuses it (contract, limitation 1); naming happens in the UI's Account Access page.
  • Single parent MCC per run — the script reads one login_customer_id from your YAML; multiple parent MCCs = one run each.

Prerequisites

  • Google Ads API credentials YAML with MCC access (google-ads.yaml) — see the google-ads-api-setup skill if you don't have one
  • Your login_customer_id set in the YAML — the script auto-detects your tree from this
  • Python packages: google-ads, pyyaml
  • Optional: gspread if you want Sheets upload — auth is gspread.service_account(): service-account JSON at gspread's default location (~/.config/gspread/service_account.json), target sheet shared with the service account's email; or adapt maybe_upload_to_sheets() to reuse the google-ads.yaml OAuth credentials like this repo's other Sheets-writing skills

How to Run

# Default: walks your full tree, writes CSVs to output/
python scripts/mcc_hack_audit.py

# Custom output directory
python scripts/mcc_hack_audit.py --output-dir my-audit-output

# Include Google Sheets upload (requires gspread auth)
python scripts/mcc_hack_audit.py --sheet-id YOUR_SHEET_ID

# Add known-hostile CIDs from a JSON file
python scripts/mcc_hack_audit.py --hostile-list hostile.json

# Mark specific external CIDs as trusted (they'll be classified TRUSTED instead of EXTERNAL)
python scripts/mcc_hack_audit.py --trusted-cids "1234567890,0987654321"

# Tune parallelism (default 20, lower if you hit RESOURCE_EXHAUSTED)
python scripts/mcc_hack_audit.py --workers 10

# Custom credentials path
python scripts/mcc_hack_audit.py --config /path/to/google-ads.yaml

Runtime: ~2-5 min for a 1,000-account tree at 20 workers. Plan ~10 min for a 10,000-account tree.

Hostile list format (hostile.json) — CID → one-line context (the authoritative shape lives in the contract; this is the run surface):

{
  "1234567890": "label or context for this hostile MCC",
  "0987654321": "another known-bad CID with a one-line note"
}

Start empty. Populate as incidents occur — the escalation path in rules.md tells you when a CID earns an entry, and its "Sharing hostile-MCC intelligence" section covers when (and when not) to publish entries beyond your own list.

After a Run (status leg)

Open output/mcc_link_scan_YYYYMMDD_SUSPICIOUS.csv and walk the triage order in rules.md: HOSTILE rows, then PENDING-to-unknown, then EXTERNAL newest-first, then the attempt records. Datestamped CSVs accumulate across days — diff the newest two to see link churn (the manual watchdog). Check every surprise against the false-alarm table before escalating; the error count vs your canceled-account inventory is the standing coverage check.

Files in this skill

FileRole
SKILL.mdThis file — workflow + routing
rules.mdJudgment: triage order, escalation default, cadence, threat-intel sharing, false alarms
examples.mdThree worked reads (clean baseline, the PENDING invite incident, the absent account)
references/scan-contract.mdExact walk/classification/output mechanics + API limitations
scripts/mcc_hack_audit.pyThe parallel tree-scan engine

Runtime artifacts (never committed): output/*.csv scans, your hostile.json, your google-ads.yaml.

When to load sibling skills

LoadWhen
change-history-checkerA link needs the activity question answered — "what changed after this manager appeared?" This skill maps who has access; that one reads what changed (and inside 30 days, who changed it)
mutation-safetyAny change you make in response to findings — remediation actions go through its approval discipline
google-ads-api-setupFirst run, or any credentials error
gaql-query-patternsWriting follow-up queries beyond what the scan and the history checker ship

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.