Everything needed to take 60 (use60.com) through Google OAuth brand verification, restricted-scope review, and the CASA Tier 2 security assessment — and to repeat it for any future application. Prepared 11 June 2026.
| Term | What it means |
|---|---|
| Brand verification | The lightweight first gate: Google confirms your app name, logo, homepage, and privacy policy genuinely represent you. ~2–3 business days. Required before anything else. |
| Sensitive scopes | Scopes that need verification but no security audit — e.g. gmail.send, calendar, tasks. Review takes up to ~10 days. |
| Restricted scopes | The big ones — gmail.readonly, gmail.compose, gmail.modify, drive.readonly. These trigger the full review plus the annual security assessment. Budget ~6 weeks end-to-end (often much faster). |
| CASA | Cloud Application Security Assessment — the App Defense Alliance framework Google uses for that security assessment. Based on OWASP ASVS, mapped to CWEs. |
| Tier 2 | The CASA level virtually all SaaS apps land in: you scan your own app and fill in a questionnaire; an authorized lab validates the evidence. No lab access to your code or infra. |
| TAC Security | Google's preferred/recommended CASA lab. Basic plan $540/app/year, LOV typically inside 1–3 weeks. |
| SAQ | Self-Assessment Questionnaire — ~54 security requirements you answer with evidence (encryption, access control, incident response, data deletion…). |
| LOV | Letter of Validation. The lab issues it when you pass and submits it to Google. Google then approves the OAuth verification within ~5–6 business days. |
| Limited Use | The policy that governs what you may do with Google user data: user-facing features only, no ads, no selling, no humans reading it, no training generalized AI models. Your privacy policy must state compliance verbatim-ish. |
| 100-user cap | Unverified apps requesting sensitive/restricted scopes are capped at 100 new users, then Google sign-in gets disabled. This is the wall we're removing. |
Google's official guidance says "up to 6 weeks" for restricted scopes. Real-world reports (app already secure, evidence ready) run as fast as 8 working days end-to-end. Our remediation work (section 3) is what buys the fast path.
This is why we expect to pass with flying colours. Everything below is merged and live in production (PR #1660 → promotion PR #1676, deployed 11 June 2026). Each row doubles as SAQ evidence in Phase 4.
| Control | What we shipped | CASA/ASVS area |
|---|---|---|
| OAuth tokens encrypted at rest | pgsodium XChaCha20-Poly1305 application-layer encryption for google_integrations and microsoft_integrations (migration 20260610140000), same framework as Slack/LinkedIn/HubSpot tokens. Plus Supabase disk-level AES-256 under everything. | V6 Cryptography, V8 Data Protection |
| Privacy Policy + Terms | Live at app.use60.com/privacy and /terms with the verbatim Google Limited Use disclosure, retention & deletion terms, GDPR rights, subprocessors. | Verification requirement |
| Data deletion on request | admin_hard_delete_user RPC cascades through tokens, emails, calendar events, recordings; Google OAuth grants are revoked at Google during deletion. Users can also disconnect Google any time in Settings → Integrations. | CASA's headline requirement: "delete user data upon a user's request" |
| Rate limiting on auth paths | oauth-initiate, google-oauth-callback, password reset — per-user/IP/email limits via shared rate_limit table, fail-closed. | V2 Authentication |
| Security headers + HSTS | HSTS (2yr, includeSubDomains), X-Frame-Options DENY, nosniff, Referrer-Policy, Permissions-Policy served by production nginx. Verified live. | V14 Configuration |
| No secrets in logs/code | Token-fragment logging removed; no hardcoded keys; service-role key never reaches the frontend; CI guards (check:no-clerk, egress allowlist, Deno permission tiers). | V7 Error/Logging, V14 |
| Dependency scanning | npm audit --audit-level=high blocks every PR; existing high/critical advisories cleared (27 → 11 moderate, 0 high). | V14 Dependencies |
| Access control | Row-Level Security on every table holding Google user data, user-scoped policies; all edge-function auth derives identity from validated JWTs, never request bodies; webhooks verify HMAC signatures. | V4 Access Control |
| TLS everywhere | All traffic HTTPS; Railway edge TLS; Supabase TLS; outbound egress allowlist enforced in CI. | V9 Communications |
| Security documentation | docs/security/: hardening guide, OAuth token rotation runbook, RLS audits, sandbox posture, admin audit — pre-written answers for most of the 54 SAQ questions. | V1 Architecture |
Do not click "Submit for Verification" until every box is ticked. Google reviewers check these mechanically and a miss restarts the cycle.
https://app.use60.com/privacy ✓ deployed 11 Junehttps://app.use60.com/terms ✓app.use60.com/privacy, and ideally this sentence on the page: "60's use of information received from Google APIs will adhere to Google's Limited Use Requirements."use60.com, with the Cloud Console project owner having access. (Check: Search Console → Settings → Users.)[email protected]) is correct and monitored.All of this lives in Google Cloud Console → APIs & Services → OAuth consent screen (rebranded "Google Auth Platform" in newer consoles). Use the production project — the one whose client ID the live app uses. Never request unverified scopes from this project while the review runs.
| Field | Value for 60 | Gotcha |
|---|---|---|
| App name | 60 (must match what users see in-app) | No Google trademarks without ™; name mismatches are a top brand-rejection cause |
| User support email | monitored team address | — |
| App logo | 60 logo, 120×120 | Uploading/changing a logo triggers (re-)brand review |
| App home page | https://use60.com | Must publicly describe the product and link the privacy policy |
| Privacy policy | https://app.use60.com/privacy | Must be on the same (verified) domain family and identical to the homepage's link |
| Terms of service | https://app.use60.com/terms | Optional but we have it — include it |
| Authorized domains | use60.com | Every redirect URI / origin must live under an authorized domain |
User type External, publishing status In production. This is also where the 100-user cap is displayed while unverified.
Click Add or remove scopes and declare the exact list in section 6. The console will bucket them into non-sensitive / sensitive / restricted automatically — that bucketing drives the requirements. Paste a per-scope justification (section 6 gives you copy) into the justification fields when prompted at submission.
The principle is narrowest scopes: every scope needs (a) an implemented user-facing feature, (b) a justification, (c) an explanation of why a narrower scope won't work. Here is 60's full declaration with submission-ready copy:
| Scope | Justification (paste-ready) |
|---|---|
gmail.readonly | "60 is a sales productivity CRM. With the user's consent, 60 reads the user's email messages to (1) show relevant correspondence with a meeting attendee on the pre-meeting preparation screen, (2) detect unanswered prospect emails so the user can follow up, and (3) learn the user's own writing tone from their sent messages so drafts match their voice. Narrower scopes are insufficient: gmail.metadata omits message bodies, which are required to summarize conversation context and analyse writing style." |
gmail.compose | "After a sales meeting, 60 creates a follow-up email draft in the user's Gmail drafts folder for the user to review, edit, and send themselves. 60 creates drafts only at the user's direction and never auto-sends from this scope. gmail.send alone is insufficient because the product's core workflow is creating reviewable drafts inside the user's own Gmail, not sending directly." |
| Scope | Feature |
|---|---|
gmail.send | Sending the follow-up email the user has approved, from their own address. |
gmail.labels | Organizing 60-related email threads with labels the user configures. |
calendar + calendar.events | Two-way calendar sync: showing the user's schedule, creating/updating/cancelling meetings the user books through 60, detecting conflicts, and triggering meeting prep. |
tasks | Syncing follow-up tasks 60 generates into the user's Google Tasks. |
drive.file + documents | Creating proposal/meeting documents in the user's Drive. drive.file only reaches files 60 itself creates — deliberately narrow. |
drive.metadata.readonly | Listing file names to let the user attach existing documents to deals. |
userinfo.email / userinfo.profile | Account identification. |
gmail.modify (overlaps gmail.readonly+compose+labels — overlap invites downscoping), https://mail.google.com/ (full access, hardest tier), drive.readonly (restricted; drive.file covers our need). If reviewers suggest narrower scopes anyway, reply "Confirm Downscoping" only if the feature truly survives, else "Unable to use narrower scopes" with specifics.One video — 60 has a single web OAuth client. (If we ever submit multiple client IDs — e.g. a Chrome extension — the video must show the grant + data access on each client.) Keep a copy of the recording; the annual re-verification reuses or refreshes it.
Setup: clean Chrome profile · test Google account with realistic data (a few meetings, some email threads with a "prospect") · production app.use60.com · screen recorder with mic (Loom/OBS/QuickTime) at 1080p · consent screen language set to English · do a dry run first.
Tier 2 = you scan your own app + answer the SAQ; the lab validates and issues the LOV. The lab never touches our code or infrastructure. To pass: the scan must show no findings mapped to CWEs with high likelihood of exploit (mediums also count on revalidation years — fix them anyway).
# 1. Get the CASA ZAP config from the ADA AST guide
# https://appdefensealliance.dev/casa/tier-2/ast-guide/dynamic-scan
# 2. Run the full scan via Docker against production:
docker run -v $(pwd):/zap/wrk/:rw -t zaproxy/zap-stable \
zap-full-scan.py -t https://app.use60.com \
-c zap-casa-config.conf -r zap-report.html
# Keep BOTH: the config file used and the plain-text results —
# they're uploaded as evidence ("AST configuration file(s)" +
# "AST scan result(s) in .txt format").
Note: ADA's pre-configured static scanner (FluidAttacks) doesn't support TypeScript/JavaScript — for 60 the DAST ZAP scan is the primary artifact; a custom CWE-compatible SAST (e.g. Semgrep with CWE output) is the fallback if asked.
casa.tacsecurity.com.~54 requirements across the ASVS categories. Map straight to section 3 of this doc plus docs/security/:
| SAQ theme | 60's evidence |
|---|---|
| How is user data encrypted at rest? | pgsodium XChaCha20-Poly1305 for OAuth tokens (screenshot the _enc bytea columns); AES-256 disk encryption (Supabase) for all data. Keep the screenshot + 3-sentence explanation ready — labs ask for exactly this. |
| Encryption in transit | TLS 1.2+ everywhere; HSTS 2 years (curl -I screenshot) |
| Access control | RLS policies (cite migrations), JWT-derived identity in edge functions, admin role gates |
| Data deletion on user request | admin_hard_delete_user + cascade FKs + Google token revocation; user-facing disconnect |
| Authentication controls | Supabase Auth, rate-limited reset/OAuth endpoints, session management |
| Logging & monitoring | Audit logs with retention (30/90d), Sentry, no secrets in logs |
| Dependency management | npm audit gate in CI, lockfiles |
| Incident response | docs/security/hardening-guide.md incident-response section |
TAC validates → may request 1–2 evidence items → issues the LOV and submits it to Google directly. Google sends the approval email within ~5–6 business days. Done — restricted scopes unlocked, user cap removed.
caerqjzvuerejfrdtygb + a dev project for this).| Rejection | Root cause | Our mitigation |
|---|---|---|
| "Demo video does not show the OAuth consent workflow" | Consent screen skipped, cropped, wrong language, or scope list ≠ submission | Script step 1:05 — slow pan, English, address bar visible |
| "Video does not sufficiently demonstrate functionality" | A requested scope never shown in use | Script demonstrates all 11 scopes individually; pre-upload checklist |
| "Homepage requirements not met" | Privacy link missing on homepage, or homepage is a login wall | Marketing-site footer task in Phase 0 — do before submitting |
| "Scope not justified / narrower scope available" | Overlapping or speculative scopes | We dropped gmail.modify + drive.readonly; justifications explain why narrower fails |
| "Scopes for unimplemented features" | Requesting ahead of the roadmap | Everything requested is live in prod today |
| "Privacy policy insufficient" | Generic policy that doesn't address Google user data specifically | Our policy has a dedicated Google/Limited Use section, separate from general data |
| CASA scan failures | High-likelihood CWEs in the DAST results | Remediation already shipped (headers, rate limits, etc.) + we run ZAP ourselves first |
| Silence / stalled review | Replies going to new threads or unmonitored inboxes | Reply-to-thread rule; both contact emails monitored |
docs/security/ · privacy page source src/pages/legal/PrivacyPolicy.tsx · remediation PRs #1660, #1676Prepared by Claude Code for the 60 team, 11 June 2026. Reusable for future apps via the google-verification skill in .claude/skills/.