Back to Blog
Google CASA Tier 2 Verification with Claude
Security

How We Passed Google CASA Tier 2 in a Weekend (With Claude as Our Security Engineer)

SecurityAIEngineering

A practical guide to getting your app verified for sensitive Google OAuth scopes - fast. Includes free templates for security documentation, SAQ, and DAST remediation.

February 10, 2026·15 min read

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:

ClassificationExamplesCASA TierWhat's Required
Defaultopenid, email, profileTier 1Self-assessment only
Sensitivecalendar.events, contacts.readonly, gmail.modifyTier 2Third-party DAST scan + SAQ
Restrictedgmail.readonly, gmail.compose, mail.google.comTier 3Full 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:

ScopeClassificationAllows
gmail.readonlyRestricted (Tier 3)Read-only access to all mail
gmail.modifySensitive (Tier 2)Read, draft, send, label, archive, trash - but not permanent delete
gmail.composeRestricted (Tier 3)Create and send emails
mail.google.comRestricted (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 ToolResult
Playwright E2E7/7 pass
OWASP ZAP Baseline0 failures, 60 passes
Fluid Attacks DAST0 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:

  1. Searched the actual codebase for evidence
  2. Identified the specific files, functions, and patterns that satisfy the requirement
  3. 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:

DocumentPurposeWhen You Need It
Security ArchitectureTrust boundaries, auth flows, data protection modelPre-assessment prep
PII Data FlowWhere personal data goes, who sees it, how it's protectedSAQ questions, privacy compliance
CASA Tier 2 ChecklistStep-by-step progress tracker for the entire processThroughout assessment
OAuth Scope JustificationWhy your app needs each sensitive scopeGoogle submission
Data Retention PolicyHow long you keep data, deletion proceduresSAQ questions, GDPR
Vulnerability Remediation (SAST)Static analysis findings and fixesPre-DAST scan prep
DAST Remediation ReportDynamic scan findings with fix evidenceRevalidation submission
Self-Assessment Questionnaire54 security control answersAssessor submission
Encryption DocumentationWhat's encrypted, how, with code snapshotsAssessor 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

  1. grab the whole packDownload the templates below (or )
  2. Place them in your project's docs/security/ directory
  3. Open each template with Claude and say: "Fill this out by searching my codebase for evidence"
  4. Claude will search your code, find the relevant implementations, and write accurate answers

Template Pack Contents


Timeline: How Long It Actually Took

PhaseTimeWhat happened
Receive TAC Security reportDay 0PDF with 9 findings (4 Low, 5 Info)
Fix all 4 findingsDay 0CORS headers, X-Powered-By, self-hosted fonts
Write Playwright verification testsDay 07 tests, all passing
Run ZAP + Fluid Attacks scansDay 0Confirmed all findings resolved
Complete 54-question SAQDay 1Claude searched codebase for each answer
Generate all documentationDay 1Remediation report, encryption docs, summaries
Respond to assessor follow-upsDay 1Database 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.