Command: cat projects/business-logic-review.md
A security review of the business logic of my own system
I reviewed the business logic of my own system, with payments, credits, rankings and paid content, and fixed thirteen classes of flaws, each with a regression test. The process was an adversarial review that never touched production: every hypothesis reproduced in a failing test and fixed at the narrowest boundary. The costliest flaws were the ones scanners and generic checklists miss.
- Role
- Author of the process and owner of the decisions
- Period
- Aug 2026 – Sep 2026
- What I did
Me: Threat model, deciding what is critical, the review process design, trade-off decisions and final approval.
Private project, described without identifying the client, product or company.
Diagram
The whole review runs in a local environment with synthetic data; production is out of scope. A security review and a business-logic review raise hypotheses, and a validator tries to refute each one: a refuted one is dropped, and a confirmed one becomes a failing test on real database connections. The fix goes in at the narrowest boundary, behind a review gate, and the test stays as a regression test, with a CI guard.
Context
Between August and September 2026 I reviewed a system of mine with checkout, credits, rankings, timed exams, 2FA and a personal-data export. The reviews ran against local code and Postgres with synthetic data. The system wasn’t open for sales yet.
This is a security review of my own code. It is not a professional pentest, and it does not claim the system is free of flaws.
What weighed the most
- False positives. The validator knocked down several suggestions from automated reviewers that had no real attack path. Only confirmed findings moved on to a fix.
- Partial external reviews. Reviews that timed out or brought nothing useful were logged as unavailable, not as approval.
- Honest counting. Overlapping test runs were not added up, and a red run caused by disk contention was not reported as green.
- The pattern that kept coming back. Granting a right without the revocation path in the same diff broke five times before it became a mandatory checklist item.
What I learned
The costliest flaws weren’t on any list of known vulnerabilities. They lived in the gap between what a business rule promises and what two simultaneous requests can do. That builder’s way of thinking is what I bring to security testing: knowing a system well enough to know where it gives.
Constraints
- Never attack the live service; only code, a local environment and synthetic data.
- Every hypothesis must be falsifiable and reproduced in a test that fails before the fix.
- Opinions do not count as proof: only a test that reproduces the flaw.
- Mocks don't prove database isolation; races need real connections.
- No lowering coverage or loosening assertions to get a green run.
Security report
Scope and authorization
My own system, reviewed locally with synthetic data. No testing against production.
Threat model
- Assets: paid content, money and credits, rankings, admin accounts, personal data.
- Actors: a curious user, a malicious user with several accounts, bots, a competitor scraping, an insider.
- Surfaces: Server Actions treated as public endpoints, webhooks, cron jobs, signed links and forms.
Method
- Investigation in two roles: security (authorization, 2FA, injection, secrets, headers) and business logic (concurrency, value, skipped steps, state machines).
- Refutation: a validator tries to kill every finding. Does the code really lead to the effect? Can the attacker reach it with the privileges they have? Is the impact inflated?
- Fix at the narrowest boundary or in the shared function, with a mandatory review gate that can block any change to money and access rules.
- Purple: the reproduction becomes a regression test; the root cause becomes a CI guard when the pattern could reappear elsewhere.
Flaw classes found and fixed
| CWE | Flaw classes found and fixed | Reproduced effect |
|---|---|---|
| CWE-362 | Race on a single-use credential | The same 2FA backup code was accepted by two concurrent logins. |
| CWE-613 | Second-factor boundary | A session opened before 2FA was enabled later gained admin access. |
| CWE-362 | Token reuse | A password-reset token could be consumed twice in parallel. |
| CWE-367 | TOCTOU on a balance | Two concurrent redemptions debited credits twice. |
| CWE-362 | Double counting | Two simultaneous exams awarded two ranking credits. |
| CWE-362 | Webhook idempotency | Two simultaneous deliveries of the same event ran the handler twice. |
| CWE-269 | Excessive SQL privilege | An anonymous role could TRUNCATE an RLS-protected table, because RLS does not cover TRUNCATE. |
| CWE-359 | Personal data exposure | The data-subject export included third parties' IPs and anti-fraud signals. |
| CWE-200 | Payload leak | The URL of a paid file was serialized to the client. |
| CWE-636 | Failing open on missing configuration | Without one configuration variable a private file was treated as external. |
| CWE-841 | Incomplete revocation | A chargeback kept access; a refund did not cancel the remote subscription. |
| CWE-840 | Selling without delivering | Checkout sold an item with no published content. |
| CWE-20 | Input validation | A price typed in the admin panel was multiplied by 100. |
Fixes
- Atomic conditional consumption (compare-and-set) for single-use codes and tokens.
- Proof of the second factor in the session, and a new login after enabling 2FA.
- A per-resource transactional lock for ranking credits; a re-read inside the transaction for balances.
- An atomic claim for webhook events.
- A REVOKE migration for anonymous roles, on top of RLS.
- An allow-list projection for the data-subject export.
- A signed link per request, with no public URL in the payload.
- Failing closed when configuration is missing.
- Revocation and remote cancellation in the same refund and chargeback flow.
- A single deliverable-offer predicate, and a price parser without floating point.
Validation
- Races reproduced with two real Postgres connections and a barrier before the insert.
- Every fix shipped with a test that failed before it.
- Database privileges checked with tests that switch roles (SET ROLE).
- CI guards that fail hand-written access filters outside the canonical helper.
Stack and why
- PostgreSQL
- Advisory locks, RLS, grants and SKIP LOCKED as concurrency and isolation primitives.
- Vitest with isolated Postgres schemas
- Concurrency and privilege tests against a real database.
- Playwright
- CSP and HTTP flow checks.
- GitHub Actions
- Dependency audit and regression guards derived from the review.
Results
Thirteen classes of flaws reproduced and fixed, each with a regression test.
audited private repositoryProject review reports(Project documentation (not re-run in this audit), Sep 2026)About 16 test files create an isolated schema in a real Postgres and about 31 deal with concurrency.
audited private repositoryApproximate count from a repository search(Audit of the private repository, Sep 2026)A dependency-audit job and a regression-guard job in CI.
audited private repositoryWorkflow structure(Audit of the private repository, Sep 2026)
Evidence
- audited private repositoryAudited private repository(Audit of the private repository, Sep 2026)