Case Study · 02 · Proven by attack, not by reading
One real business, rebuilt AI-native · The Peaceful Eating Way · this is its product arm

Secure enough to attack myself.

The Trance Tracker is a live app people trust with their most vulnerable moments. This page is for the technical reader: how its security was audited, what was found, and why every fix was proven by attacking the deployed build.

27
Fixes shipped, two releases
Seconds
To find what two reviews missed
5/5
Concurrent writes surviving — was 2/5

The stakes

People trust it with their most vulnerable moments.

A private companion for people working through food struggles — what they log is intimate. Losing it or leaking it wouldn't be a bug. It would be a betrayal.

The Trance Tracker is a production Progressive Web App at app.trancetracker.com: installable on a phone, offline-first with a sync queue, self-updating, with a privacy-first social layer — small weekly circles with reactions and comments — enforced by row-level security at the database. It runs on a real staging-then-production deploy pipeline with captured rollback points.

Because the data is sensitive and the users are real, security here was treated as product, not paperwork. That meant commissioning an audit against the app I built myself — and holding its findings to a standard most teams don't: no claim counts until it's demonstrated against the running system.

The method

Five auditors. One rule: no claim without proof.

Five independent audit lenses — data integrity, security, the social layer, app lifecycle, and an adversarial review of the newest changes — followed by primary-source verification of every major claim.

Confirmed by attack

Reproduced live against the deployed build — a hostile payload actually fired, or was actually stopped.

Confirmed by probe

Database behavior measured directly — real status codes, real policy responses, not assumptions.

Confirmed by reading

Unambiguous in the code itself — the weakest grade, and treated that way.

Refuted

Two auditor claims didn't survive the probes. They were recorded as refuted — an audit that can't say "we were wrong" can't be trusted when it says "this is real."

The finding

The XSS two review rounds missed. A script found it in seconds.

The app's device-migration link — the feature that moves your data to a new phone — could carry a stored cross-site-scripting payload.

Confirmed by attack · staging, live backend

One link click. Full account takeover.

A crafted migration link smuggled script through a field the sanitizer didn't cover. The payload was stored verbatim, survived the boot merge, and executed the moment the entry rendered — with the user's auth credential within reach in local storage. It had survived two full rounds of human code review. A scripted hostile payload against the staging build found it in seconds.

Confirmed by attack · follow-up release

The same link could quietly hand you someone else's identity.

On a fresh device, a forged migration link could install an attacker-chosen identity without asking — every entry the victim logged from then on would flow into an account the attacker controlled. No script injection required. Found in the same audit, closed in the next release.

The proof

Fixed. Then attacked again.

Reading a diff and nodding is not verification. Every security fix was re-proven by replaying the attack against the deployed fixed build — with the prior build as the control.

The response shipped as two releases totaling 27 fixes: output escaping at every render sink, a whitelist on every imported field, a Content-Security-Policy as defense-in-depth, and a confirmation step so no device ever adopts an identity from a URL silently. The original XSS payload was replayed against the fixed build: it rendered as inert text. The forged identity link was replayed: it now dies at a confirmation the attacker cannot skip — cancel rejects it, and only a deliberate confirm adopts.

The audit also caught a server-side race: concurrent reactions and comments on the same entry were losing each other's updates — measured at 2 of 5 reactions and 3 of 5 comments surviving five simultaneous writers. The read-modify-write was rewritten as a single locked update and re-measured with the same synthetic users: 5/5 and 5/5.

Fix built, deployed to staging Hostile payload seeded — for real Assert: did not fire (prior build = control) Ship to production re-run the attack against the live deploy CONTROL DISCIPLINE The prior build must still be vulnerable to the same payload — otherwise the test proves nothing.
The loop that closed every finding: no fix counts as fixed until the original attack fails against the deployed build — with the vulnerable build as the control.

What it produced

The rules the work wrote.

Each of these is now a standing rule, written into the project's operating documents — because each one was earned the hard way.

I.

Verify security fixes by attack, not by reading.

The XSS survived two human review rounds. A scripted payload found it in seconds. Reviewers reason about code; attacks interrogate the running system.

II.

A success response proves the call succeeded — not that it did what you meant.

Probing the live database turned up three silent-failure paths that returned success while losing data. Every one had to be handled explicitly.

III.

When a fix and its verification disagree, believe the verification.

Live testing beat three code reviewers on a bug that only existed in the interaction with the real backend. The measurement is the authority.

The honest ledger: one known cosmetic residual remains — a user who renames themselves leaves their old first name in comments they previously left on others' entries, the same behavior as any chat product. It's documented, ruled accepted, and on the record. A clean ledger you can read beats a perfect one you have to take on faith.
Why this matters to a venture builder
Hostile testing × honest ledgers × live proof = software people can trust with their inner life

Anyone can ship features. The discipline is shipping systems you've tried to break — and documenting what you found either way.

Bring me the messy problem