ASVS 5.0 V2.3.2 requires that business logic limits are implemented as the application's documentation says, so that logic flaws cannot be exploited. It is Level 2, and it pairs with V2.1.3.
Why it matters
A written limit that the code ignores is worse than none, because everyone believes it protects them. Abuse of unlimited coupons, repeated refunds, unlimited free trials and bulk resource creation all come from limits that exist on paper only.
How to implement it
Enforce limits in the service layer, next to the action, and make the check and the write one atomic step so concurrent requests cannot slip past it.
-- allow at most 5 payouts per user per day, atomically
INSERT INTO payouts (user_id, amount_cents, created_at)
SELECT :user_id, :amount, now()
WHERE (SELECT count(*) FROM payouts
WHERE user_id = :user_id
AND created_at > now() - interval '1 day') < 5;
Check the affected row count to see whether it was accepted. Put the limits in configuration with a name, so they match the document and can be audited. Track global limits with a shared counter, not per instance memory. Log every breach with the user and the limit name, and alert on bursts.
How to test it
Take the limits table from your documentation and test each row at the limit, one above it, and in parallel. A scripted run of 20 concurrent requests against a limit of 5 should result in exactly 5 successes.
Making limits observable
Build each limit as a named rule in code, such as payout_daily_user. Emit a metric and a log line with the rule name when it triggers. Then you can answer two questions that matter after an incident: which limits fire often, and which never fire. A limit that never fires may be set too high to matter, or may not be wired up at all.
For money and inventory, add reconciliation. A nightly job that totals payouts per user and compares with the documented caps will catch a limit that the code failed to enforce, including through a new endpoint that skipped the shared check.
Common mistakes
- Counting in memory on one instance of many.
- Check then act in two steps, which races.
- Limits keyed only on IP address, which is easy to rotate.
Quick check
Why test with parallel requests?
Many limits fail only under concurrency, because two requests both read the old count before either writes.