
How We Passed Google CASA Tier 2 in a Weekend (With Claude as Our Security Engineer)
A practical guide to getting your app verified for sensitive Google OAuth scopes - fast. Includes free templates for security documentation, SAQ, and DAST remediation.
A practical guide to getting your app verified for sensitive Google OAuth scopes - fast.
If your app requests sensitive Google OAuth scopes like gmail.modify or calendar.events, you'll eventually hit a wall: CASA Tier 2 verification. Google requires a third-party security assessment before your app can access user data in production.
We went through this process for Orbis, our professional relationship management platform. The assessment covers everything from DAST scanning to a 54-question security questionnaire to encryption documentation.
Here's the thing: we did it in a weekend. Not by cutting corners - by using Claude as a hands-on security engineering partner. This post walks through exactly how, with templates you can use for your own app.
You Probably Don't Need This
Let's start with the most useful thing: most apps only need Tier 1. If your app just does "Sign in with Google" - the openid, email, and profile scopes - you fill out a self-assessment in the OAuth consent screen and you're done. No third-party scan, no SAQ, no cost. The vast majority of apps shipping Google login never need to think about CASA.
You only hit Tier 2 when you request sensitive or restricted scopes - when your app wants to read a user's calendar, access their contacts, or sync their email. That's where this guide picks up.
For more details on scope classifications, see Google's OAuth 2.0 Scopes for Google APIs and the CASA tiering criteria.
Understanding Google OAuth Scopes (and Why They Matter)
Google classifies every OAuth scope into one of three sensitivity levels, and this directly determines your CASA tier:
| Classification | Examples | CASA Tier | What's Required |
|---|---|---|---|
| Default | openid, email, profile | Tier 1 | Self-assessment only |
| Sensitive | calendar.events, contacts.readonly, gmail.modify | Tier 2 | Third-party DAST scan + SAQ |
| Restricted | gmail.readonly, gmail.compose, mail.google.com | Tier 3 | Full penetration test |
The distinction matters more than you'd think. Here's a real example from our experience:
How We Avoided Tier 3
Orbis syncs Gmail data. Our first instinct was to request gmail.readonly - seems reasonable, we just need to read emails. But gmail.readonly is a restricted scope, which triggers Tier 3: a full penetration test that costs thousands of dollars and takes weeks.
Instead, we use gmail.modify. Yes, modify. It's counterintuitive, but gmail.modify is classified as sensitive (Tier 2), not restricted. It lets you read, draft, send, and modify labels - but critically, it does not allow permanent deletion of emails. Users can archive or trash emails, but they can't purge them from the trash. Google considers this a meaningful safety boundary.
The scope hierarchy for Gmail looks like this:
| Scope | Classification | Allows |
|---|---|---|
gmail.readonly | Restricted (Tier 3) | Read-only access to all mail |
gmail.modify | Sensitive (Tier 2) | Read, draft, send, label, archive, trash - but not permanent delete |
gmail.compose | Restricted (Tier 3) | Create and send emails |
mail.google.com | Restricted (Tier 3) | Full access including permanent delete |
By designing our app so users can archive and trash emails but never permanently delete them, we qualified for gmail.modify instead of a restricted scope - saving us from a Tier 3 assessment entirely.
The takeaway: read the scope documentation carefully before you build. A small design decision can be the difference between a $540 DAST scan and a $5,000+ penetration test.
What Is CASA Tier 2?
CASA (Cloud Application Security Assessment) is Google's verification program, run by the App Defense Alliance, for apps requesting sensitive or restricted OAuth scopes. For Tier 2, Google assigns you an approved security assessor who runs a DAST (Dynamic Application Security Testing) scan against your production app, generates a findings report, and gives you a Self-Assessment Questionnaire to complete. You remediate the findings, answer the questions, and they revalidate.
Google OAuth Console → Request Sensitive Scopes → Assigned Assessor
→ DAST Scan → Findings Report → Remediation → SAQ → Revalidation → Verified
Why We Chose TAC Security ($540)
Google gives you a list of approved CASA assessors. They vary significantly in price - some charge $3,000+ for a Tier 2 assessment. As a bootstrapped startup, that wasn't an option.
We went with TAC Security because:
- $540 total for the assessment (one of the most affordable approved assessors)
- Includes the initial DAST scan and a revalidation scan after you remediate
- Clear deliverables: PDF report with findings, SAQ template, and revalidation confirmation
- Fast turnaround - report delivered within days
If you're bootstrapping and need to get through CASA without burning your runway, TAC Security is a solid choice. The process was straightforward and the report was detailed enough to action.
What We Were Working With
Orbis is a React 19 + TypeScript SPA hosted on Vercel, with a Supabase backend (PostgreSQL, Edge Functions, Auth, Storage). We use Google OAuth exclusively - no passwords. The app syncs Gmail, Calendar, and Contacts data for CRM purposes.
Our stack for this assessment:
- Frontend: React 19 + Vite on Vercel
- Backend: Supabase Edge Functions (Deno)
- Database: PostgreSQL with Row Level Security
- Auth: Google OAuth via Supabase Auth
- Scopes:
gmail.modify,calendar.events,contacts.readonly,contacts.other.readonly(all Sensitive → Tier 2) - AI partner: Claude (Anthropic)
Step Zero: Scan Yourself Before the Assessor Does
This is the biggest time-saver we can recommend: don't wait for the assessor's report to find your vulnerabilities. We ran our own scans before even engaging TAC Security, which meant most issues were already fixed by the time their scan ran.
Here's what we used:
1. OWASP ZAP Baseline Scan (Free)
The standard open-source DAST scanner. Run it against your production URL to catch the same issues an assessor will find:
docker run --rm ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
-t https://your-app.com -J report.json
This catches: missing security headers, CORS misconfiguration, cookie issues, CSP problems, information disclosure. It's a passive scan - it won't modify anything on your site.
2. Fluid Attacks DAST (Free/Open Source)
Complements ZAP with SSL/TLS analysis and additional HTTP security checks:
# Create config file (fluid-attacks-config.yaml)
cat <<EOF > config.yaml
namespace: your-app
output:
file_path: /src/results.csv
format: CSV
dast:
urls:
- url: https://your-app.com
- url: https://api.your-app.com
EOF
# Run the scan
docker run --rm -v "$(pwd):/src" fluidattacks/dast /bin/dast /src/config.yaml
This catches: weak TLS cipher suites, missing CSP directives, SSL configuration issues.
3. Qualys SSL Labs (Free, Web-Based)
Test your TLS configuration. Aim for an A or A+ rating. This is what assessors use to verify your transport security.
4. Security Headers Scanner (Free, Web-Based)
Test your HTTP security headers. Aim for an A+ rating. It checks HSTS, CSP, X-Frame-Options, and all the headers the DAST scan will look for.
What We Fixed Before TAC Even Scanned Us
We also had Claude create a CASA Tier 2 checklist upfront - a structured document tracking every requirement across the entire process: documentation, security controls, scanning, SAQ, and submission. This gave us a clear map of what needed doing before we even started.
By running these tools ourselves first, we caught and fixed:
- SAST findings (Fluid Attacks static analysis): Docker running as root, hardcoded JWT in a migration, source maps enabled, unpinned Docker images, CORS wildcard in edge functions
- DAST findings (Fluid Attacks dynamic analysis + OWASP ZAP): Missing CSP directives, weak TLS cipher suites, missing
upgrade-insecure-requests, security header gaps - Security headers: Added HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
- TLS configuration: Verified A+ SSL Labs rating on both domains
This meant TAC Security's scan only found 4 Low severity issues - things like Vercel's default CORS behavior and external Google Fonts. Without pre-scanning, we'd have had dozens of findings to remediate under time pressure.
The lesson: spend an hour running free tools before you spend $540 on an assessor. Fix the obvious stuff first.
How We Used Claude
We didn't use Claude to write a report and hope for the best. We used it as an interactive security engineering partner - specifically Claude Code, Anthropic's CLI tool that can read files, run commands, and make code changes directly in your project. It reads your actual codebase, makes real changes, runs real scans, and verifies fixes against production.
Here's the workflow that worked:
Step 1: Feed Claude the findings report
We gave Claude the TAC Security PDF directly. It extracted the findings, mapped them to specific URLs and evidence, and identified the root cause of each issue.
Step 2: Fix each finding with Claude writing the code
Claude read our vercel.json, edge function CORS handlers, and index.html - then made targeted fixes:
Finding 1 & 2 (CORS): The Vercel defaults were returning Access-Control-Allow-Origin: * on static assets. Claude added explicit headers in vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "https://app.meetorbis.com" }
]
}
]
}
For edge functions, Claude migrated all 14 functions from a legacy wildcard corsHeaders export to a new getCorsHeaders() function that validates the request Origin against an allowlist:
export function getCorsHeaders(req: Request): Record<string, string> {
const origin = req.headers.get('Origin') ?? ''
const allowedOrigins = (Deno.env.get('ALLOWED_ORIGINS') ?? '')
.split(',')
.map(o => o.trim())
.filter(Boolean)
const isAllowed = allowedOrigins.includes(origin)
return {
'Access-Control-Allow-Origin': isAllowed ? origin : '',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'authorization, content-type, x-client-info, apikey',
}
}
Finding 3 (Proxy Disclosure): One line in vercel.json - set X-Powered-By to empty string to suppress Vercel fingerprinting.
Finding 4 (SRI / External Fonts): Instead of adding SRI attributes to Google Fonts (impractical - Google dynamically generates the CSS), Claude self-hosted the fonts. It downloaded the woff2 files, added @font-face declarations, removed the external <link> tags, and tightened the CSP font-src to 'self' only.
Step 3: Verify with automated tests
Claude wrote Playwright E2E tests that verify each finding against production:
test('denies preflight from malicious origin', async ({ request }) => {
const response = await request.fetch(edgeFunctionUrl, {
method: 'OPTIONS',
headers: {
Origin: 'https://evil.example.com',
'Access-Control-Request-Method': 'POST',
},
})
const acao = response.headers()['access-control-allow-origin']
expect(acao).not.toBe('*')
expect(acao).not.toBe('https://evil.example.com')
})
7 tests covering all 4 findings. All passing against production.
Step 4: Independent scan verification
We ran three independent scans to confirm the fixes:
| Scan Tool | Result |
|---|---|
| Playwright E2E | 7/7 pass |
| OWASP ZAP Baseline | 0 failures, 60 passes |
| Fluid Attacks DAST | 0 findings related to original issues |
Step 5: Answer the 54-question SAQ
This is where Claude really saved time. The Self-Assessment Questionnaire covers everything from trust boundaries to LDAP injection to certificate revocation. For each question, Claude:
- Searched the actual codebase for evidence
- Identified the specific files, functions, and patterns that satisfy the requirement
- Wrote detailed technical justifications referencing real code
We answered 44 as Compliant, 8 as Not Applicable (we use Google OAuth only - no passwords, no LDAP), and 1 as Partially Compliant (antivirus scanning on uploads).
Step 6: Generate all supporting documentation
Claude produced every document the assessor needed:
- Remediation report mapping each finding to the fix + evidence
- Encryption architecture document with code snapshots
- Revalidation summary for submission
The Security Documentation We Created
Here's every document we produced during this process, with what it covers:
| Document | Purpose | When You Need It |
|---|---|---|
| Security Architecture | Trust boundaries, auth flows, data protection model | Pre-assessment prep |
| PII Data Flow | Where personal data goes, who sees it, how it's protected | SAQ questions, privacy compliance |
| CASA Tier 2 Checklist | Step-by-step progress tracker for the entire process | Throughout assessment |
| OAuth Scope Justification | Why your app needs each sensitive scope | Google submission |
| Data Retention Policy | How long you keep data, deletion procedures | SAQ questions, GDPR |
| Vulnerability Remediation (SAST) | Static analysis findings and fixes | Pre-DAST scan prep |
| DAST Remediation Report | Dynamic scan findings with fix evidence | Revalidation submission |
| Self-Assessment Questionnaire | 54 security control answers | Assessor submission |
| Encryption Documentation | What's encrypted, how, with code snapshots | Assessor follow-up questions |
What Caught Us Off Guard
A few things we didn't expect:
1. Vercel defaults to Access-Control-Allow-Origin: * on static assets. If you're on Vercel, you probably have this right now. It's fine for most apps but will fail a CASA scan.
2. Google Fonts can't use SRI. The CSS is dynamically generated per-request, so the hash changes. The only real fix is self-hosting the fonts.
3. The assessor asks follow-up questions. After the scan and SAQ, TAC Security asked us to "share a database screenshot of encrypted data" and confirm whether we store Google user data. Have your encryption documentation and database screenshots ready.
4. Some findings are infrastructure-level. Fluid Attacks flagged CBC cipher suites on our Supabase domain. We can't change Supabase's TLS configuration - the answer is "managed by our infrastructure provider, TLS 1.3 is supported and preferred."
5. The revalidation date is real. Ours was set for the day we received the report. Don't sit on findings.
The Claude Workflow That Worked
Here's the specific workflow we'd recommend for anyone going through this:
1. Give Claude your DAST report (PDF or text)
→ It maps findings to your codebase
2. Let Claude read your actual config files
→ vercel.json, nginx.conf, CSP headers, CORS handlers
3. Have Claude fix each finding with minimal, targeted changes
→ No over-engineering, just fix what was flagged
4. Ask Claude to write verification tests
→ Playwright tests that prove each finding is resolved
5. Run independent scans ([OWASP ZAP](https://www.zaproxy.org/), [Fluid Attacks](https://github.com/fluidattacks/cli))
→ Claude can run these via Docker and interpret results
6. Have Claude answer the SAQ against your real codebase
→ It searches for evidence, not guesses
7. Generate all documentation
→ Remediation reports, encryption docs, revalidation summaries
The key insight: Claude works best when it can read your actual code. Don't describe your architecture in abstract - point it at the files. It will find the specific RLS policies, the exact encryption function, the precise CSP header configuration. The SAQ answers are better because they reference real file paths and real function names.
Templates: Do It Yourself
We've open-sourced our security documentation as templates. These aren't generic checklists - they're structured documents designed to work with Claude Code (or any AI assistant) to produce thorough, evidence-based security documentation for your own app.
How to use these templates
- grab the whole packDownload the templates below (or )
- Place them in your project's
docs/security/directory - Open each template with Claude and say: "Fill this out by searching my codebase for evidence"
- Claude will search your code, find the relevant implementations, and write accurate answers
Template Pack Contents
- Security Architecture TemplateDocument your app's trust boundaries, authentication flow, data protection layers, and external service integrations.
- PII Data Flow TemplateMap every piece of personal data: where it enters, where it's stored, who can access it, and how it's protected.
- CASA Tier 2 Checklist TemplateStep-by-step checklist for the entire CASA Tier 2 process, from pre-assessment prep to Google submission.
Timeline: How Long It Actually Took
| Phase | Time | What happened |
|---|---|---|
| Receive TAC Security report | Day 0 | PDF with 9 findings (4 Low, 5 Info) |
| Fix all 4 findings | Day 0 | CORS headers, X-Powered-By, self-hosted fonts |
| Write Playwright verification tests | Day 0 | 7 tests, all passing |
| Run ZAP + Fluid Attacks scans | Day 0 | Confirmed all findings resolved |
| Complete 54-question SAQ | Day 1 | Claude searched codebase for each answer |
| Generate all documentation | Day 1 | Remediation report, encryption docs, summaries |
| Respond to assessor follow-ups | Day 1 | Database screenshots, encryption snapshots |
| Total | ~2 days |
Final Thoughts
CASA Tier 2 isn't hard - it's tedious. The security requirements are reasonable, and most well-built apps are already 80% compliant. The pain is in the documentation, the evidence gathering, and the back-and-forth with the assessor.
That's exactly where Claude excels. It doesn't hallucinate security controls - it reads your actual vercel.json, your actual RLS policies, your actual encryption functions. It writes SAQ answers that reference real file paths. It generates Playwright tests that verify real production endpoints.
The templates below are our gift to the next team staring down a CASA assessment at 5pm on a Friday. Download them, point Claude at your codebase, and get it done.
Built by the Orbis team. Security templates available below.
Related Articles

Why We're Building Orbis: Your Best Deals Don't Close in a CRM
The deals that change your company's trajectory happen in WhatsApp, Telegram, and Slack - not a Salesforce pipeline. Here's why we're building an AI relationship workspace for the channels that actually matter.
7 min read

We're Entering the Age of Hyper-Personalization. Your Outreach Should Too.
The new era of AI-driven personalization is shifting away from digital noise toward curated, hyper-personalized interactions.
3 min read

Why ChatGPT Can't Replace Your CRM (And What Actually Will)
ChatGPT can draft emails in seconds, but it can't remember your relationship history. Here's why founder relationships need more than a general-purpose AI.
4 min read
Your network deserves better than a spreadsheet.
Join founders who are building stronger relationships with AI-powered insights. Get early access to Orbis.