CitySchedule

Municipal Appointment Management
Security OverviewPrepared for government procurement review
Version 1.4 · July 2026
Infrastructure & Hosting
  • Application: Next.js on Vercel (US regions)
  • Database: Supabase (PostgreSQL) on AWS US-East-1
  • File Storage: Supabase Storage (S3-compatible) on AWS US-East-1
  • Authentication: Supabase Auth (SOC 2 Type II)
  • Email: Resend (US-based transactional email)
  • Data residency: All resident data and files stored on US soil
  • Uptime SLA: 99.9% via Vercel and Supabase infrastructure
Data Protection
  • In transit: TLS 1.2+ enforced on all endpoints; HTTPS-only
  • At rest: AES-256 encryption (Supabase managed)
  • PII collected: Full name, email, phone number, and (only for services that require it) a site address; no SSNs or payment data
  • Files: Resident attachments and inspector field photos stored in isolated cloud storage buckets, distinguished by file kind. All downloads require authenticated request + cryptographically signed URL (1-hour expiration). File access validated against reservation ownership.
  • File upload validation: Server-side MIME-type allowlist (JPEG, PNG, GIF, WebP, PDF, Word docs); 10 MB size limit; filenames sanitized; no executable types accepted
  • Data retention: Records retained for 3 years after reservation close. Deletions in the app are soft deletes: records are hidden from view but preserved for municipal records-retention compliance. Cities may request deletion at any time.
  • Log archival: Audit logs, status history, and inspection-record detail tables are partitioned by year; partitions older than the current and two prior years are exported to write-once (immutable) archive storage and removed from the live database
  • Backups: Automated daily backups via Supabase; encrypted at rest
Input Validation & Output Encoding
  • SQL injection: Prevented via parameterized queries (Supabase client library)
  • Cross-site scripting (XSS): React automatically escapes JSX output; email templates use explicit HTML escaping for all user input
  • CSV injection: Exported files do not include formulas
  • HTML entity handling: Special characters (<, >, &, ") are escaped in all user-controlled output
File Access Controls
  • Ownership validation: Residents can only upload/download files for their own reservations; city staff can access files for any reservation in their city
  • Path validation: File download requests must reference a path under the reservation ID; cross-reservation path traversal attempts are rejected (403 Forbidden)
  • Storage isolation: Each reservation has its own storage directory; bucket-level policies prevent direct public access
Compliance & Certifications
SOC 2 Type II: CitySchedule is not yet SOC 2 certified. Our infrastructure providers are certified:
  • Supabase — SOC 2 Type II (authentication, database, storage)
Application-level controls (city isolation, role enforcement, signed URLs, input validation) are implemented in code and available for security review.
  • CCPA / GDPR: See our Privacy Policy for resident rights and data handling
  • Testing: Automated unit and integration tests validate access controls, data formatting, and error handling
Access Control & Authorization

Authentication is handled by Supabase Auth. Features:

  • Email/password and OAuth 2.0 (Google)
  • SSO support (SAML 2.0) available on Supabase enterprise plans
  • Mandatory multi-factor authentication (MFA): every account must enroll a TOTP (authenticator-app) second factor. There are no exemptions. Enrollment and AAL2 step-up are enforced server-side in the proxy on every page and API request, and the gate fails closed: a user who has not stepped up (or whose assurance level cannot be read) cannot reach protected resources.
  • MFA recovery: lost-device recovery is an administrator-initiated reset of the user's enrolled factors (audited, super-admin only); the user must re-enroll a new second factor before regaining access. There is no email-only bypass of the second factor.
  • Bot protection: Cloudflare Turnstile captcha on sign-in, sign-up, and password-reset; tokens verified server-side by Supabase Auth before credentials are accepted
  • Session management with automatic token refresh

Authorization is enforced at the API level on every request. Roles and city assignments are checked before any data access. Postgres row-level security (RLS) policies provide a second, database-level enforcement layer on data reads, so a bypassed or buggy API check alone cannot expose another user's rows:

RoleAccess scopeRestrictions
ResidentOwn reservationsCannot edit approved/denied reservations; cannot see other residents
StaffReservations in their cityCannot delete inspection records; cannot manage staff; cannot access other cities
AdminReservations + staff in their cityCannot modify other cities; cannot grant super-admin role
Super AdminAll cities + system configCitySchedule staff only; enforced at infrastructure level

City isolation is enforced on every API call: staff/admins cannot access data from cities where they are not assigned. Super admins can view all cities but their access is logged.

No privilege escalation: Roles are assigned by city administrators; users cannot modify their own role. Role grants are logged for audit purposes.

Job-function capabilities (such as inspector or plan reviewer) are a separate dimension from the access tier, managed only through the service client. Inspection outcomes and field photos can be recorded only against an already-approved appointment, and only by the staff member the appointment is assigned to, not by capability alone.

Security Testing & Monitoring
  • Automated tests: Unit tests for authorization helpers, time formatting, and data validation. Integration tests for core reservation flows.
  • E2E smoke tests: Playwright tests validate public pages render correctly and unauthenticated users are redirected from protected routes
  • Type safety: Full TypeScript coverage; all API parameters validated at compile time
  • Linting: ESLint checks for common mistakes (unescaped entities, missing dependency arrays)
  • Audit logging: All reservation actions (create, update, approve, deny) logged with user ID, timestamp, and changed fields. Logs are PII-free by design: entries record field names and outcomes, never field contents; API access logs record only the method and URL path (no query strings, bodies, or headers).
  • RLS test harness: Row-level security policies are replayed against the real migrations and exercised by an automated test suite on an embedded Postgres, validating cross-city and cross-user isolation at the database layer
Network & Abuse Controls
  • Rate limiting: Per-identity limits enforced in the proxy on every API request — a global ceiling, a stricter ceiling for all mutating (write) requests, and a tight dedicated bucket for reservation booking. Requests over the limit receive HTTP 429.
  • Cross-origin protection: API requests carrying a foreign Origin are rejected with 403; only same-origin and explicitly allowlisted origins may call the API. CORS preflight is handled accordingly.
  • Captcha: Cloudflare Turnstile gates the authentication forms against automated credential-stuffing and signup abuse
  • MFA enforcement: All accounts are gated at the proxy on both pages and API routes; an AAL1 session cannot bypass the second-factor prompt, and the gate fails closed
Incident Response & Vulnerability Reporting
  • Security contact: security@cityschedule.app (monitored 24/7)
  • Breach notification: Affected residents and city staff notified within 72 hours of confirmed incident, with description of exposure and remediation steps
  • Vulnerability disclosure: We welcome responsible disclosure. Please email security concerns rather than posting publicly.
  • Remediation SLA: Critical vulnerabilities patched within 24 hours; high-severity within 7 days
Third-party Risk Management
  • Supabase: Authentication, database, and file storage; SOC 2 Type II; data encrypted at rest
  • Resend: Transactional email only (confirmations, status updates); no sensitive PII in email subject lines; no resident lists sent to Resend
  • Cloudflare: Turnstile captcha for bot/abuse protection on authentication forms; receives browser challenge signals (including IP address) only at solve time; no resident reservation data shared
  • Vercel: Application hosting; automatic DDoS protection; edge caching
  • Data sharing: No resident data is ever shared, sold, or rented to third parties except as required by law