01

Valid is not correct

A request can satisfy its schema, pass every type check, and still be wrong. Business logic vulnerabilities live in that gap between syntax and intent. The software accepts the request because it is well formed; the outcome is unacceptable because the request arrives at the wrong time, in the wrong order, or under conditions the design did not anticipate.

This makes the class difficult to detect with pattern matching. There is no malformed payload to flag. The request is often simply a legitimate action taken somewhere the workflow assumed it could not happen. The vulnerability is not in the parser. It is in the model of how the business is supposed to behave.

The system does exactly what it was told. The flaw is that nobody wrote down what it was supposed to mean.
02

The rule is the surface

Start with the invariants: what must always be true for the business to be correct? A refund should not exceed the original payment. An approval should not be granted by the person requesting it. A trial should not restart indefinitely. A balance should not become negative. These sentences are the specification, and they are also the research surface.

The interesting questions are about sequence and state. Can a step be skipped? Can it be repeated? Can it be performed after the object has moved to a terminal state? What happens when two requests arrive at once? What is authoritative: the value the client sends, the value the server calculated a moment ago, or the value stored by another service?

Interfaces that look linear often conceal parallel paths. A mobile client, an admin tool, an export job, and a support console may each reach the same workflow through a different route. If the rule is enforced in one path and assumed in another, the business process has two different definitions of correctness.

  • Limits, prices, quotas, and eligibility should be enforced server-side.
  • State transitions need explicit rules, not implicit ordering in the interface.
  • Repeated submissions, retries, and concurrency are part of the threat model.
  • Alternate clients and internal tools can bypass assumptions made by the primary UI.
03

Testing without breaking

Business logic testing requires more care than many other classes because the effects are real. Use controlled accounts, test data, and the lowest-value path that can demonstrate the issue. Stay within scope, respect rate limits, and never move other people’s money, inventory, or data to prove a point.

Write each hypothesis as a sentence before testing it: “A completed order cannot be completed again,” or “A user cannot approve their own request.” Then look for the smallest observation that confirms or contradicts it. If the test requires a state you cannot safely create, describe the gap rather than forcing production into it.

Concurrency is a common source of real impact, but it is also easy to make disruptive. Check for permission and guidance first. Prefer the minimum number of requests, use test objects, and stop as soon as the behavior is demonstrated. Restraint is part of the finding, not an afterthought.

04

Encode the intent

The durable fix is not another validation rule at the edge. It is an explicit model of the business invariant, enforced where the decision is made. State machines help. So do server-authoritative pricing and limits, idempotency keys for repeatable actions, and transactional checks for operations that must not interleave.

Teams also need negative tests. Test suites tend to confirm that the happy path works; business logic flaws live in the paths that should fail. A useful test asserts that a completed order cannot be completed again, that a refund cannot exceed its payment, and that a user cannot approve their own request. These tests encode intent and make it easier to notice when a new route quietly bypasses it.

  • Make state transitions explicit and reject invalid ones by default.
  • Keep pricing, eligibility, and limits server-authoritative.
  • Use idempotency and transactions for operations that can repeat or race.
  • Add negative tests that assert what the system must refuse to do.