Independent testing report · Sample

Pre-launch findings

BookWell — appointment booking & payments (fictional demo application)

RED
Do not launch yet Three findings must be fixed before strangers use this. One exposes customer data to anyone with a browser. Retest recommended after fixes.

Summary

What we found

Six findings in total. The three below are the ones blocking launch — the remaining three were cosmetic and are listed in the full report.

#FindingAreaSeverity
01Customer records readable without logging inData exposureCritical
02Tip field accepts unlimited values, breaking the order totalPaymentsHigh
03Reward applies to services it was never configured forBusiness logicHigh
04–06Cosmetic and copy issues — see full reportInterfaceLow

Findings in detail

How we write them up

Every finding gives you what we did, what happened, what should have happened, why it matters commercially, and what to change. You should be able to hand any one of these to a developer without explaining it.

01 — Customer records can be read without logging in

Critical · Data exposure

What's wrong

The endpoint that returns a customer's bookings doesn't check who is asking. Supply any phone number in the URL and it returns that customer's record — no login, no token, no ownership check. Because phone numbers are guessable, the entire customer base can be walked one request at a time.

How to reproduce

  1. Open a private browser window and confirm you are not logged in.
  2. Request the customer bookings endpoint with any registered phone number in the path.
  3. Observe a complete customer record in the response.
  4. Change the phone number and repeat to retrieve a different customer.
# No authentication header of any kind
GET /api/customer/bookings/%2B15550100123
Host: api.bookwell-demo.example

# Response — 200 OK
{
  "customers": [{
    "firstName": "[CUSTOMER NAME]",
    "phoneNumber": "[PHONE]",
    "venueId": "[ID]"
  }],
  "bookings": [{
    "status": "scheduled",
    "scheduledTime": "2026-07-02T03:30:00.000Z",
    "paymentStatus": "paid",
    "paymentMethod": "card",
    "price": 400,
    "customerInfo": { "name": "[NAME]", "phone": "[PHONE]" }
  }]
}

Note: values above are redacted placeholders. In a real report this block contains the actual response so your developer can confirm the problem in seconds.

What's exposed

  • Customer name and phone number
  • Full booking history with dates and services
  • Prices paid, payment status and payment method
  • Internal customer and venue identifiers

Why it matters

Anyone who finds this URL can harvest your entire customer list — names, phone numbers, spending history — without an account. Beyond the immediate privacy exposure, that data is exactly what's needed to run convincing phishing against your customers while impersonating you. If you hold personal data on residents of a regulated jurisdiction, this is also a reportable breach.

What to change

Require authentication on this endpoint, then verify that the authenticated user actually owns the record being requested.
  • Reject unauthenticated requests before any lookup happens.
  • Derive the customer identity from the session, not from the URL.
  • Apply the same ownership check to every endpoint following this pattern — we found this one, but it is a pattern, not an isolated bug.
  • Prefer non-sequential, non-guessable identifiers.

02 — Tip field accepts unlimited values and breaks the total

High · Payments

What's wrong

The custom tip field has no maximum and no format validation. Entering an absurdly large number is accepted without error, and the order total is then rendered in scientific notation rather than as currency.

How to reproduce

  1. Add any service to the basket and go to checkout.
  2. Choose Custom under the tip options.
  3. Enter 100000000000000000000000000000.
  4. Observe the order total.
Service subtotal   $40.00
Custom tip         100000000000000000000000000000
Order total        $1e+30       ← accepted, no error

Expected

The field should accept currency only — digits with at most two decimal places — enforce a sensible maximum, and reject anything else with a clear message. A monetary total should never render in scientific notation.

Why it matters

Anything that reaches your payment processor or your books in this state causes real damage: failed charges, corrupted reporting, reconciliation that doesn't balance. It also signals that monetary input isn't validated anywhere — which is worth checking across every other amount field in the product.

What to change

Validate on both the client and the server — client-side alone is trivially bypassed.
  • Accept digits and at most two decimal places.
  • Enforce a maximum tip, either a fixed ceiling or a multiple of the subtotal.
  • Format all monetary output as currency, never as a raw number.
  • Handle amounts as integer minor units rather than floating point.

03 — Reward applies to services it was never configured for

High · Business logic

What's wrong

A complimentary reward set up for one specific service can be redeemed against any other service, including more expensive ones. The booked service is never checked against the reward's configuration.

How to reproduce

  1. Create a loyalty reward for a low-priced service (a $30 service).
  2. Unlock the reward on a test customer account.
  3. As that customer, book a different, higher-priced service (a $95 service).
  4. Apply the reward at checkout.
Reward configured for:  Service A  ($30)
Customer books:        Service B  ($95)
Reward applied:        accepted — Service B becomes free
Margin lost:           $95 instead of the intended $30

Why it matters

This one is quiet, which makes it worse. Nothing errors and nothing is logged — you simply lose more margin than intended on every redemption, and you won't see it in any dashboard. Once customers notice, they will always redeem against your most expensive service.

What to change

Validate the booked service against the reward's eligible services before applying it, on the server.
  • Block redemption and explain why when the service doesn't match.
  • Decide deliberately whether a reward is service-specific or a value credit — and if it's a credit, cap it at the configured value.
  • Log every redemption with the reward and the service it was applied to, so this is visible if it recurs.

Want this for your app?

Same format, your application, findings you can hand straight to a developer — plus a verdict on whether it's ready for strangers.

Break my app — $997 Launch Check — $297