Business Logic Bugs Static Analysis Will Never Catch

AI generates code that satisfies the prompt — not the business rules it was never given. Static analysis can't catch what only your requirements document knows.

Copper Sun Brass Team · · 6 min read
securityai-code-reviewengineering

AI generates code that satisfies the prompt. The prompt doesn’t include your pricing rules, your inventory constraints, or your account limits — so the code doesn’t either. A static scanner can’t supply those rules, because they don’t exist anywhere in the source for a pattern to match.

Business logic bugs are the class of security vulnerability where the code is shaped correctly and does the wrong thing for your specific application. Static analysis has a fixed answer for this class: it can’t reach the layer where they live.

What Business Logic Rules Are (and Aren’t)

Business logic rules are constraints on application behavior that derive from domain requirements, not from general programming correctness. BrassCoders’s 12 scanners — Bandit, Pylint, Pyre/Pysa, Semgrep, ast-grep, detect-secrets, and six custom detectors — cover general correctness patterns: SQL injection, credential exposure, unsafe subprocess calls. None of them know that a subscription discount cannot stack with a referral credit, that an account cannot withdraw more than its verified balance, or that a trial user cannot export more than five records.

Those rules have no structural marker. CWE-840 (Business Rule Bypass) from MITRE documents this class explicitly: it covers situations where an attacker subverts a business rule because the rule was either not encoded in the system or not enforced at every required point. The weakness is in the design, not the implementation.

OWASP A04:2021 — Insecure Design makes the same point at the category level: design flaws — including missing or incorrectly applied business constraints — are distinct from implementation bugs, and adding more static analysis passes doesn’t fix them. The fix is threat modeling and use-case testing before implementation, not more scanner rules.

How AI Generates Plausible Code Without the Rules

An AI coding assistant generates the code path it was shown. BrassCoders can flag what results structurally — a hardcoded constant in the discount calculation, a parameter that accepts both a percentage and a fixed amount without a type discriminator — but the rule the function was supposed to enforce is invisible to any scanner, because it lives in a product specification the source code never encoded.

Asked for a discount application function, the model produces code that reads a discount value, computes a new price, and returns it. The shape is correct. The output compiles and returns reasonable values on valid input.

What the model wasn’t given: whether a discount may reduce the price below the product’s cost floor, whether the same coupon code is tracked for single-use enforcement, or whether a negative discount input should be rejected or treated as zero. Those rules didn’t appear in the prompt, so they don’t appear in the code. The generated function is not wrong in any structural sense — it performs the computation it was asked to perform. The gap is between “performs the computation” and “enforces the domain constraints the computation is supposed to respect.”

The E-Commerce Class: Discount and Inventory Violations

Discount and inventory rules are the most common AI-generated business logic gap BrassCoders teams surface in production Python codebases — precisely because they’re the domain-specific constraints a well-formed prompt always leaves out, present in the product spec and absent from every file the scanner reads.

An AI-generated coupon-application endpoint typically handles a valid, unused coupon code correctly. It often fails to:

  • Reject a coupon already used by the same account in a prior session.
  • Enforce a minimum purchase amount before the discount applies.
  • Cap the maximum discount when the code specifies “up to 20% off.”
  • Block stacking two discount codes on the same order when the business rule prohibits it.

None of these omissions are structural. An endpoint that skips single-use enforcement has no bad pattern — it simply lacks the database check that enforces it. A rule that flagged every discount function for missing single-use enforcement would flag every correct one too, because the safe and unsafe versions are structurally the same. The rule has to be verified by a test that applies the same coupon twice against the actual application state and checks for rejection.

What BrassCoders Flags in This Space

BrassCoders flags the structural layer around business logic code — not the semantic correctness of the business rules themselves. In a payment or discount handler, that structural layer includes hardcoded price or discount values embedded in code rather than loaded from configuration, SQL injection in query construction against a pricing or inventory table, missing authentication on endpoints that modify account balances or apply credits, and type ambiguity where a percentage discount and a fixed-amount discount share the same parameter without a discriminator.

Each of those is a structural finding the scanner catches deterministically on every run. The OWASP Application Security Verification Standard chapter on business logic (ASVS V11) distinguishes these structural controls from semantic correctness requirements. The ASVS V11.1.7 control — verify that the application only processes business logic flows in sequential step order — describes a property that must be tested against the running application, not verified by reading source. BrassCoders clears the structural findings so that review and testing effort lands on the logic, not the boilerplate.

Where Business Logic Verification Belongs

Business logic correctness is caught by tests that encode the rules explicitly — BrassCoders runs first, clears the structural findings, and then the test suite verifies that the remaining logic respects the domain model the scanner was never given.

The rules have to be stated before code can enforce them. If a requirement says “a promotional code may not be applied to an order that already includes a loyalty discount,” that rule needs to exist as an assertion in the test suite before any scanner or AI reviewer can evaluate whether the code enforces it. Integration tests that assert a coupon cannot be applied twice, boundary tests that assert a balance cannot go below zero, and scenario tests that walk discount edge cases against the actual pricing model — these are the controls where business logic lives.

Pattern scanning clears the structural layer. Encoding the domain model as tests is what catches the gaps that only your product requirements document can name.

pip install brasscoders
brasscoders --offline scan /path/to/your/project

BrassCoders catches the hardcoded price literal and the SQL injection in the discount handler. The business rule the handler was never told to enforce is the test suite’s job — and knowing the scanner won’t find it is the first step toward building the tests that will.

Frequently Asked Questions

What's the difference between a business logic bug and a security vulnerability?

The categories overlap. A business logic bug is a violation of an application-specific rule — a discount applied twice, a balance allowed to go negative, an age gate bypassed. When that violation is exploitable, it's also a security vulnerability. OWASP A04:2021 (Insecure Design) explicitly includes business logic flaws as a class of security finding, distinct from implementation bugs that static analysis can catch.

Can any automated tool catch business logic violations?

Not reliably. DAST tools that replay recorded user journeys can catch some violations — a purchase flow with a negative price, a coupon applied twice in a session. But they catch only the cases their test scenarios include, not every possible business rule. Comprehensive coverage requires encoding the rules explicitly as assertions in integration tests.

Why do AI assistants generate business logic bugs?

Because business rules live in requirements documents and domain knowledge, not in the code the model was trained on. An AI generating a discount function was shown a pattern: compute discount, subtract from total. Whether that discount may not reduce the price below the product's cost floor is a rule the business requires that the model has no way to know. It implements the code path it was shown.

What's an example of a business logic bug in AI-generated code?

A checkout endpoint that applies a percentage discount before checking whether the result falls below a product-specific minimum price floor. The code is structurally sound — no SQL injection, no hardcoded secrets — but it allows purchases below cost on any item with a discount code. The bug is a missing business rule, not a missing structural pattern.

What does BrassCoders catch around business logic?

The structural layer: hardcoded pricing literals embedded in discount logic, missing authentication on payment endpoints, SQL injection in query construction, and type confusion where a discount percentage and a fixed amount share the same parameter without a discriminator. Business rule correctness — whether the discount logic is right for your product model — is above what any pattern scanner can reach.

What actually prevents business logic bugs?

Tests that encode the rules explicitly: integration tests that assert a coupon cannot be applied twice, boundary tests that assert a balance cannot go below zero, scenario tests that walk discount edge cases against the actual pricing model. The rules have to exist somewhere before code can enforce them; the test suite is where they live when the requirements document is too vague to serve as the source of truth.