Record-level authorization for a Base44 equipment-rental marketplace
Page-level permissions were correct. Record-level permissions did not exist. Sequential identifiers meant any signed-in renter could read - and cancel - someone else's booking.Composite engagement. Details anonymised; figures are representative.
6 → 0
endpoints returning other users' records
- Platform
- Base44
- Industry
- Marketplace · Equipment rental
- Duration
- 4 weeks
- Findings
- 2 critical, 2 high, 2 medium
What we walked into
A two-sided marketplace for construction and event equipment, built in Base44 over about ten weeks. Roughly 900 renters and 140 equipment owners, with bookings running through the platform and payouts to owners twice a month.
The founders came to us about cost. They were on a plan that was starting to sting and wanted to know whether moving off the platform would pay for itself.
The app asked the right first question - is this user a renter or an owner?
Every engagement opens with a free consultation and a scoped audit. This one drew on the following:
Composite engagement. Details anonymised; figures are representative.
What we found
Severity is about consequence, not effort. A critical finding is one where a stranger can read data, move money, or destroy state.
- Critical
Record ownership never checked on read or write
Six endpoints loaded a record by the identifier in the request and returned it without asking who was asking.
- Critical
Owner payout details reachable by any account
The payout settings record was fetched by owner id with no membership check.
- High
Authorization written in the interface, not the data layer
Role checks lived in page-level conditions. The data operations underneath accepted any authenticated caller, so the API surface effectively had no rules.
- High
No audit trail on money-moving actions
Cancellations, refunds, and payout changes wrote no record of who performed them.
- Medium
Sequential identifiers exposed in URLs
Beyond making the authorization problem trivially exploitable, auto-incrementing ids leak business volume.
- Medium
Browse page loaded every listing
The directory fetched all active listings with all fields and filtered in the browser.
The work we did
Delivered over 4 weeks, on branches, alongside a founder who kept shipping product the whole time.
- 01
Wrote the authorization model down before touching code: eleven rules covering who may read, create, modify, and cancel each entity, reviewed line by line with the founders.
- 02
Moved every rule into the data layer, so the check happens where the record is fetched rather than where a page decides what to render.
- 03
Replaced sequential integers in public URLs with opaque identifiers, kept the internal keys, and added a lookup so existing links and bookmarked confirmations keep working.
- 04
Built an authorization test suite: for each of the eleven rules, one test that the permitted role succeeds and one test per other role that it receives a 403.
- 05
Added an append-only audit log for cancellations, refunds, and payout changes, recording the actor, the target record, and the previous value.
- 06
Reviewed six months of access logs with the founders to establish whether the gaps had been exploited, and helped them draft the note they sent to affected owners.
- 07
Added server-side pagination, indexed filters, and field selection to the browse and search pages.
- 08
Wired up structured logging and error tracking, so the next incident has a trail instead of a reconstruction.
Before and after
The same metrics, measured the same way on both sides of the work. Where a number got worse, it is in the table.
Endpoints returning other users' records
60Confirmed by replaying every request with a token from an unrelated account.
Authorization rules covered by tests
0 of 1111 of 11Each rule has a positive and a negative case; CI blocks a merge that breaks one.
Browse page p95
3.6 s620 ms2,400 listings, server-side pagination, indexed filters.
Money-moving actions with an audit trail
NoneAllAppend-only log with actor, target, and previous value.
Time to answer “who changed this?”
Not answerableUnder a minuteThe question that turned a routine security review into a two-day exercise.
Monthly infrastructure cost
$180/mo$310/moThe platform plan did not change. The increase is observability, log retention, and a staging environment.
Composite engagement. Details anonymised; figures are representative. Figures are rounded from what we measured across the engagements this study is drawn from.
What it runs on now
The builder stayed in the picture where it earned its place. Nothing here was rewritten for the sake of rewriting it.
- Base44
- PostgreSQL
- Node.js service layer
- Playwright
- GitHub Actions
- Sentry
- Structured log retention
We asked for a cost review. The first thing we were shown was our own booking table, opened from an account that had no business seeing it.
Other write-ups
Different builder, same shape of problem.
- Claude Code
A Claude Code B2B app with no way to deploy it twice the same way
40 min → 6 min - commit to production, unattended
Read the write-up - Bubble
A Bubble booking app whose platform bill grew faster than its revenue
$2,300 → $640 - monthly platform bill, same traffic
Read the write-up - Lovable
A Lovable client portal where every tenant could read every other tenant
11 → 0 - tables readable across tenants
Read the write-up
Same builder, same symptoms?
Send us a repo link and a paragraph about what it does. The audit is free and the report is yours either way.