gcp-project-audit

Generated source view for the actual executable engineering/gcp-project-audit skill. The durable routing article is Cloud, Data, and Service Skills. Source: skills/engineering/gcp-project-audit/SKILL.md

Runtime Source

Field Value
Category engineering
Origin personal
Slug gcp-project-audit
Source slug gcp-project-audit
Family Cloud, Data, and Service Skills
Source skills/engineering/gcp-project-audit/SKILL.md

Bundled Resources

These files are part of the executable skill folder and must be preserved with the skill source.

File Role
references/hygiene-commands.md Progressive reference
scripts/inventory.sh Executable helper
scripts/summarize.py Executable helper

Description

Audit every Google Cloud project Kevin's personal Google account can see, read-only: billing, APIs, IAM, keys, buckets, Firebase and logs, with cleanup candidates. Use when Kevin says "audit my GCP projects" or before changing IAM, keys or billing.

Skill Source

---
name: gcp-project-audit
description: >-
  Audit every Google Cloud project Kevin's personal Google account can see, read-only: billing,
  APIs, IAM, keys, buckets, Firebase and logs, with cleanup candidates. Use when Kevin says "audit
  my GCP projects" or before changing IAM, keys or billing.
origin: personal
source_slug: gcp-project-audit
metadata:
  openclaw:
    category: "engineering"
    requires:
      bins:
        - gcloud
        - python3
        - curl
---

# GCP Project Audit

Thin router over `scripts/`. The inventory is read-only; the decisions it informs live in
`wiki/tools/gcp-projects.md`. Do not turn the audit rules into prompt text: change
`scripts/summarize.py` and re-run.

## Commands

```bash
skill_dir="${CLAUDE_SKILL_DIR:-${CODEX_SKILL_DIR:-skills/engineering/gcp-project-audit}}"
bash "$skill_dir/scripts/inventory.sh"                 # all visible projects, parallel, ~2 min
bash "$skill_dir/scripts/inventory.sh proj-a proj-b   # subset
```

Output lands in `~/Library/Logs/gcp-audit/<UTC stamp>/`: one directory of raw JSON per project,
`summary.json`, and `report.md`. The report path is printed last. Read `report.md`, then update the
inventory table and the dated decisions in `wiki/tools/gcp-projects.md`.

## Procedure

1. Confirm `gcloud auth list` shows Kevin's personal Google account active. The script uses gcloud's user
   credential and adds `x-goog-user-project` for Firebase REST calls; nothing needs a service account.
2. Run the inventory. Compute, Cloud Run, Functions, SQL and GKE calls fail on projects where those
   APIs are off; that is normal and recorded in `_errors.txt`, not a finding.
3. Read `report.md`. Flags mean:
   - `archive-candidate`: only default APIs, no resources, no log entry in 60 days. The log signal is
     admin-activity audit logs only (config changes, OAuth client edits, API enablement); Firestore
     reads, Hosting traffic and Auth sign-ins are not logged by default, so a serving Firebase app can
     look dormant. Resource Manager writes are excluded so this skill's own label updates do not count.
   - `dormant`: Firebase or data resources exist but no log entry in 60 days.
   - `stale-sa-key`: user-managed service-account key older than 90 days.
   - `unrestricted-api-key`: API key without API or application restrictions.
   - `bucket-pap-not-enforced`: bucket where public access prevention is inherited, not enforced.
   - `owner-not-kevin`: an IAM owner other than Kevin's personal account (`KEVIN` in `scripts/summarize.py`).
   - `oauth-unchecked`: the project has no `oauth-app` label, so nobody has checked Google Auth
     Platform for it; it is never an archive candidate until someone does (step 4).
4. OAuth clients are invisible to gcloud, and a project with nothing but default APIs can still be
   the Sign in with Google backend of a live site (four of Kevin's were on 2026-09-25). For every
   candidate open the Console pages `/auth/clients?project=<id>` and `/auth/overview?project=<id>`
   and read the Project Checkup "Client usage" line: "All of your OAuth clients are actively being
   used" means live within Google's inactivity window; "Unused clients" means Google will soon
   delete them. Record the answer as the label `oauth-app` = `live-signin` | `cli-clients` |
   `unused-client` | `none`, then re-run the summarizer. The Audience page's "N users / 100 cap"
   counts only sensitive-scope grants, so 0 users does not mean no sign-ins.
5. Apply only reversible hygiene from this skill: project labels (`purpose`, `status`, `owner`,
   `audited`) through the Resource Manager v3 API, and public access prevention on buckets. Both
   commands are in `references/hygiene-commands.md`.
6. Anything that deletes (projects, keys, API keys, Firestore data) needs Kevin's explicit go-ahead
   in the conversation and is done with plain gcloud, then recorded in the wiki page. Project
   shutdown is recoverable for 30 days; key deletion is not.

## Hard rules

- Never delete or disable anything from this skill's scripts.
- Never create service-account keys or API keys.
- Never enable billing, link billing accounts, or accept terms on Kevin's behalf.
- `gcloud projects update --update-labels` is not available in the installed SDK; use the REST call.

Timeline