- Client
- A booking platform for fitness studios (fictional)
- System
- React and TypeScript web app; Supabase (Postgres, Auth, Storage); Stripe; hosted on Vercel
- Size
- One repository, about 38,000 lines of TypeScript; 40 studios and 1,800 customers
- Package
- Full-coverage audit: 4 days of work, report 8 working days after kick-off
- Access
- Read-only: code, database, hosting and Stripe dashboard; removed after the audit
- Prepared by
- Pavel Vnukov, twofed
Contents
- Summary
- What already works well
- Findings
- Fix plan
- Quick wins
- Scope and method
Appendix: How your system works
1. Summary
The app works and customers use it every day, but it is not yet safe to grow. Two problems need fixing
this week: customer data can be read by anyone who knows where to look, and a key that bypasses every
access rule is published in the website's code. Neither needs a rewrite. The first two take about two
days to fix; everything in this report, apart from one gradual clean-up, about 14 days.
Top five risks for the business
- Anyone can download your customer list (S1). Names, emails, phone numbers and booking
history of 1,800 people. Under the GDPR this is a personal data breach risk.
- A master key is public (S2). Whoever finds it can change or delete any data in the database.
- Bookings can be confirmed without paying (S3). Lost revenue, and studios stop trusting the numbers.
- Classes get overbooked (A1). 23 double bookings in the last 30 days, each one an unhappy customer and a refund.
- A lost database means a lost business (R1). No one has ever restored a backup, and there is no copy outside one account.
Short answers
| Your question | Short answer |
| Will the code, including the AI-generated parts, hold up? | Keep it and fix it. Fixing everything in this report takes about 14 days; rebuilding the app would take about 60 (A1, A2). |
| How safe are your data and access? | Not safe yet: two critical issues expose customer data (S1, S2). Both take about two days to fix. |
| What will slow down or break as you grow? | It holds with room to spare. On a test copy of your system, at twice your busiest time (80 virtual users at once), 95% of requests were answered within 0.6 seconds and none failed. At 100, the agreed limit, the studio schedule was the first page to slow down (D1), and 6 classes on the copy were overbooked during the test (A1). At ten times today's bookings, both would be daily problems. The test copy may be smaller or configured differently than the live system, so the numbers are indicative. |
| Are you overpaying for hosting and services? | No. Running costs, about €70 a month, are reasonable for the current traffic. |
| Where do you stand on GDPR and accessibility (EAA)? | The privacy policy doesn't name your processors or say how long data is kept (G1), and the booking form is hard to use without a mouse (G2). Together with S1, these are the gaps to close first. This is a technical view, not legal advice. |
| Can you release safely? | Not yet. Every push to the main branch goes straight to production, with no tests and no staging in between (I1, M1). Rolling back the code takes a minute in Vercel, but database changes are made by hand and can't be rolled back. About 40 releases in the last three months; 3 had to be fixed or rolled back. |
| How is development run? | One developer works directly on the main branch, partly through Lovable: about 9 in 10 changes go in without a pull request or review, and none come with tests (M1). Tasks live in a chat. That works for one person, but nobody else could take over without help today; the appendix to this report is a first step. |
Findings by area
| Area | Most severe | Findings |
| Security | Critical | 2 critical, 1 high, 1 medium, 1 low |
| Architecture and code | High | 1 high, 1 medium |
| Reliability | High | 2 high |
| Database | Medium | 1 medium |
| Infrastructure and costs | Medium | 1 medium; running costs are fine |
| Performance | Medium | 1 medium; the load test on a test copy held with room to spare |
| Maintainability | Medium | 1 medium |
| Data protection and accessibility | Medium | 2 medium |
2. What already works well
- Payments go through Stripe Checkout, so card details never touch the app.
- Sign-up and login use Supabase Auth with email confirmation instead of a home-made system.
- TypeScript runs in strict mode, and most of the code is readable and consistently formatted.
- Running costs, about €70 a month, are reasonable for the current traffic.
- The booking pages fit a phone screen without sideways scrolling, which matters because most bookings happen on phones.
3. Findings
Every finding has a severity, what it means for the business, a recommended fix and an estimate.
- CriticalActive risk to data or money. Fix now.
- HighLikely to cause real damage. Fix within weeks.
- MediumSlows you down or adds risk over time. Plan within one to three months.
- LowWorth fixing when convenient.
Security
S1 · SecurityCritical
Customer data can be read without logging in
- Found
- Row-level security is turned off for the
bookings and profiles tables. The public
API key, which is meant to be in the website's code, therefore gives read access to every customer's
name, email, phone number and booking history. Checked, with your permission, from a logged-out session.
- Impact
- A personal data breach risk for about 1,800 people. If the data has already been accessed, the GDPR may
require you to notify the data protection authority within 72 hours.
- Fix
- Turn on row-level security for every table; add policies so customers see only their own bookings and
studios only theirs; add an automated test that tries to read data while logged out. Review the API logs
for unusual reads and decide with your lawyer whether a notification is needed.
- Effort
- 1.5 days
S2 · SecurityCritical
The database master key is published in the website's code
- Found
- The admin page uses the Supabase
service_role key, which bypasses all access rules. Because it
is stored in a VITE_ environment variable, it is bundled into the JavaScript that every
visitor downloads.
- Impact
- Anyone who opens the browser's developer tools can read, change or delete all data.
- Fix
- Rotate the key today. Move the three admin actions to a server-side function that checks the user's role.
- Effort
- 0.5 days
S3 · SecurityHigh
A booking is marked as paid by the browser, not by Stripe
- Found
- The booking is confirmed when the customer lands on the payment success page. The Stripe webhook exists,
but it does not verify Stripe's signature and is not used to confirm payments.
- Impact
- Anyone who opens the success page address directly gets a confirmed booking without paying.
- Fix
- Confirm bookings only from the verified
checkout.session.completed webhook, make it safe to
receive twice, and reconcile the last three months against Stripe.
- Effort
- 1.5 days
S4 · SecurityMedium
Outdated dependencies with known vulnerabilities
- Found
- 31 packages are out of date; two of them have published high-severity security advisories.
- Impact
- Known holes are the first thing automated attacks try.
- Fix
- Update the two vulnerable packages now, the rest in small batches; turn on automatic update pull requests.
- Effort
- 0.5 days
S5 · SecurityLow
Debug messages print customer emails in the browser console
- Found
- Leftover debug logging prints the logged-in customer's profile, including their email, on every page.
- Impact
- Small on its own, but personal data ends up in screenshots and support recordings.
- Fix
- Remove the debug logging and add a lint rule that blocks it in production builds.
- Effort
- 2 hours
Architecture and code
A1 · ArchitectureHigh
Class capacity is checked in the browser, so classes get overbooked
- Found
- The app counts free spots in the browser and then saves the booking in a separate step. When two people
book the last spot at the same moment, both succeed. The database shows 23 double bookings in the last
30 days.
- Impact
- Unhappy customers, refunds and extra work for studios; it gets worse as traffic grows.
- Fix
- Move the capacity check and the booking into a single database transaction, so the last spot can only
be taken once.
- Effort
- 2 days
A2 · CodeMedium
The same logic exists in several slightly different copies
- Found
- There are three versions of the booking form, and the class price is calculated in four places with
small differences, a common result of generating code screen by screen.
- Impact
- Every change has to be made in several places; prices can already differ between the app and emails.
- Fix
- One shared booking form and one price function, merged step by step during regular maintenance. No rewrite.
- Effort
- Ongoing, as part of maintenance
Reliability
R1 · ReliabilityHigh
No one has ever restored a backup
- Found
- The provider takes database snapshots, but no restore has ever been tried, there is no copy outside the
provider account, and uploaded files are not backed up at all.
- Impact
- An accidental deletion, a bad migration or a locked account could lose all bookings and customer data.
- Fix
- A daily encrypted backup of the database and files to a second provider, a written restore procedure and
a restore drill every three months.
- Effort
- 1 day
R2 · ReliabilityHigh
Errors are discovered by customers, not by you
- Found
- There is no error tracking and no uptime check. The last outage, a failed deployment, was reported by a
studio owner two hours later.
- Impact
- Problems last longer than they need to, and studios lose bookings while nobody knows.
- Fix
- Error tracking for the app and server functions, an uptime check on the booking page, and alerts by email
and phone.
- Effort
- 0.5 days
Database
D1 · DatabaseMedium
The studio schedule is slow because of missing indexes
- Found
- The schedule query reads the whole
bookings table every time; the slowest 5% of requests take
1.9 seconds, and the time grows with every new booking. In the load test on a test copy, it was the first
page to slow down once the copy went past 80 virtual users.
- Impact
- The busiest page gets slower each month, and the database plan will need upgrading sooner than necessary.
- Fix
- Two indexes, on studio and start time, checked against the real query plans.
- Effort
- 0.5 days
Infrastructure and costs
I1 · InfrastructureMedium
Every change goes straight to production
- Found
- There is no staging environment. Code is deployed on every push, and database changes are made by hand in
the dashboard, so the repository does not describe the real database.
- Impact
- Each change is tested on real customers, and the database cannot be rebuilt from the code.
- Fix
- A staging copy with test data (the test copy made for the load test is a good start), database changes
as migrations in the repository, and deployment to production only after staging passes.
- Effort
- 1 day
Performance
P1 · PerformanceMedium
The booking page is heavy on phones
- Found
- The booking page loads 1.4 MB of JavaScript, including the admin screens. On a mid-range phone the main
content appears after 4.2 seconds (Google's target is 2.5).
- Impact
- Slower pages mean fewer completed bookings and weaker search rankings.
- Fix
- Load the admin area separately, compress images and drop two unused libraries.
- Effort
- 1 day
Maintainability
M1 · MaintainabilityMedium
No automated tests for booking and payment
- Found
- There are no automated tests. Changes are checked by clicking through the app by hand, when there is time.
- Impact
- Each new feature can silently break booking or payment, the two flows the business depends on.
- Fix
- End-to-end tests for sign-up, booking, payment and cancellation, run automatically before every deployment.
- Effort
- 2 days
Data protection and accessibility
G1 · Data protectionMedium
The privacy policy does not match what the app does
- Found
- The policy does not name Supabase, Stripe or Vercel as processors, and there is no rule for deleting old
bookings and closed accounts.
- Impact
- A gap the data protection authority would look at first, especially together with S1.
- Fix
- A list of processors and retention periods for your lawyer to review, and an automatic clean-up of data
past its retention period.
- Effort
- 0.5 days, plus your lawyer's review
G2 · AccessibilityMedium
The booking form is hard to use without a mouse
- Found
- Form fields have no labels for screen readers, the date picker cannot be used with a keyboard, and button
text has a contrast ratio of 2.9:1 (the minimum is 4.5:1).
- Impact
- Some customers cannot book at all. If the European Accessibility Act applies to you, these issues block
compliance.
- Fix
- Labels, a keyboard-friendly date picker and adjusted colours, then a check against WCAG 2.1 AA.
- Effort
- 1 day
4. Fix plan
Ordered by risk, not by convenience. Estimates are for the work itself; if you want me to do the work, a fixed
quote follows our call.
| When | What | Days |
| This week | S2 rotate the key and move admin actions to the server; S1 access rules and log review; S5 debug logging | 2.25 |
| Next 3 weeks | S3 payments confirmed by Stripe; A1 overbooking; R1 backups and restore drill; R2 error tracking and alerts | 5 |
| Next 1–2 months | I1 staging and migrations; M1 end-to-end tests; D1 indexes; S4 dependencies; P1 page weight; G1 privacy; G2 accessibility | 6.5 |
| Ongoing | A2 merge duplicated code during regular maintenance | — |
| Total | 15 findings | 13.75 |
5. Quick wins
Five changes of half a day or less that remove the most risk per hour, about two days in total:
- Rotate the leaked master key (S2).
- Remove debug logging of customer data (S5).
- Turn on error tracking and an uptime check (R2).
- Add the two missing database indexes (D1).
- Update the two packages with known vulnerabilities (S4).
6. Scope and method
What I reviewed
- The code repository: all server functions, and the booking, payment and account flows line by line.
- The database: schema, access rules, indexes, slow queries and backups.
- Hosting and deployment settings, environment variables and logs.
- Stripe settings: checkout, webhooks and how payments reach the app.
- The booking page with Lighthouse and a manual accessibility check.
- A load test on a test copy of your system: the main pages, the class schedule, login with test accounts and
booking up to the payment page, up to 100 virtual users. Your developer set up the copy for the audit, with
its own database, test data and Stripe in test mode.
- The CI/CD pipeline and the release cycle: the GitHub Actions workflow, the rules on the main branch and
40 production deploys over the last three months.
- How development is run: the Git history of the last 12 months (counts only, no names) and a 30-minute
interview with your developer.
Not in scope
- Load testing of the live system, and a full penetration test.
- Legal advice: GDPR and accessibility findings are technical and need your lawyer's review.
After the report
A 60-minute call to walk through the findings and answer questions. The plan and the appendix are yours to use
and share with any developer; if you want me to do the fixes, you get a fixed quote.
Appendix: How your system works
An overview for anyone who works on the system: a new developer, an agency or your own team. It describes the
system as it was during the audit and contains no passwords, keys or personal data. Problems are in section 3;
here they are only referred to by ID.
This sample shows three of its seven parts. The others are how data flows, third-party services, backups and
monitoring, and how to run the app locally.
Components
| Component | What it does | Built with | Runs on |
| Web app | Class schedule, booking, payment and customer accounts; the studio admin pages | React and TypeScript | Vercel |
| Database, sign-in and file storage | Customers, studios, classes and bookings; uploaded photos | Supabase (Postgres, Auth, Storage) | Supabase |
| Server functions | The Stripe webhook and booking emails | Supabase Edge Functions | Supabase |
Environments
| Environment | What it is for | Database and keys | Payments |
| Production | Your studios and customers | Production database and keys | Live |
| Test copy | The load test in this audit; not used for releases | Its own database with test data, its own keys | Test mode |
| Staging | None yet: changes go straight to production (see I1) | — | — |
How code reaches production
- Changes are made in Lovable or in the developer's editor and pushed straight to the main branch on GitHub.
The branch has no protection rules.
- A GitHub Actions workflow runs the linter and the type check, but nothing waits for the result.
- Vercel deploys every push to the main branch to production. There is no staging step and no automated
tests before a deploy (see I1, M1).
- Database changes are made by hand in the Supabase dashboard, not as migrations in the repository (see I1).
- To roll back the code: Vercel → Deployments → promote the previous deployment. Database changes have no
rollback.
Want a report like this for your system?
A report like this comes with the full-coverage audit, from €1,900 excl. VAT (1–2 weeks). Not sure about the
scope? Start with the one-day critical audit (€600): if you go on to the full-coverage audit after it, it costs
less, because part of the system is already reviewed. Both start with a short message on WhatsApp or by email.
Sample report · fictional data · October 2026