# Business Logic Exploitation

> Detection of business logic vulnerabilities — the bugs that scanners never find. Use when reviewing payment flows, subscription management, resource allocation, competitions/contests, coupon/discount systems, multi-step workflows, role/permission transitions, approval processes, or any feature where the correctness of behavior depends on business rules rather than technical implementation. Covers IDOR (Insecure Direct Object References), state machine violations, parameter tampering, price/quantity manipulation, privilege escalation via logic flaws, workflow bypass, and financial calculation errors. Relevant for Agent Arena, UberKiwi, and any product with money, permissions, or competitive mechanics.

- Skill: `nickgallick/business-logic-exploitation` (Agent Skill)
- Install (CLI): `npx skillmds add nickgallick/business-logic-exploitation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nickgallick/business-logic-exploitation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: nickgallick (https://skillmd.com/u/nickgallick)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/nickgallick/business-logic-exploitation

---


# Business Logic Exploitation

## Why This Is the Hardest Category

Business logic bugs are:
- **Invisible to scanners** — no CVE, no OWASP signature, no regex pattern
- **Invisible to type checkers** — TypeScript says it's valid
- **Invisible to tests** — unit tests verify the happy path, not adversarial paths
- **Unique to each application** — can't be cataloged like XSS or SQLi
- **Often the highest impact** — directly affect money, data, and trust

**Stat**: ~50% of all high/critical bug bounty findings are now broken access control and business logic flaws.

## Category 1: IDOR (Insecure Direct Object Reference)

### The Pattern
User-supplied ID determines which resource to access, without verifying the user owns/has access to that resource.

```typescript
// VULNERABLE — user supplies profile ID, no ownership check
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const profileId = searchParams.get('id')
  
  const { data } = await supabase
    .from('profiles')
    .select('*')
    .eq('id', profileId)  // Attacker changes ID to anyone's profile
    .single()
  
  return Response.json(data)
}
```

### Detection Questions
For EVERY endpoint that takes an ID parameter:
- [ ] **Who can supply this ID?** Any user? Only authenticated users?
- [ ] **Is ownership verified?** Does the code check that the authenticated user owns/has access to this resource?
- [ ] **Can the ID be enumerated?** Sequential integers are trivially enumerable. UUIDs are harder but not impossible.
- [ ] **Does RLS handle it?** If using Supabase with RLS, is the policy correctly scoped?

### Fix Pattern
```typescript
// SAFE — derive resource access from auth, not user input
export async function GET(request: Request) {
  const user = await getUser()
  if (!user) return new Response('Unauthorized', { status: 401 })
  
  const { data } = await supabase
    .from('profiles')
    .select('*')
    .eq('user_id', user.id)  // Scoped to authenticated user
    .single()
  
  return Response.json(data)
}
```

**Rule**: Prefer deriving the resource scope from auth context rather than user-supplied IDs. If IDs must come from user input, always verify ownership.

## Category 2: State Machine Violations

### The Pattern
Multi-step workflows that can be accessed out of order, or state transitions that skip required steps.

```
Expected flow:    CREATE → SUBMIT → REVIEW → APPROVE → PUBLISH
Attacker flow:    CREATE → PUBLISH (skip review)
```

### Real Examples

**Payment bypass**:
```
Expected: Select plan → Enter payment → Confirm → Activate subscription
Attack:   Select plan → directly call activate endpoint → Free subscription
```

**Contest manipulation**:
```
Expected: Register → Submit before deadline → Wait for judging → See results
Attack:   Submit after deadline (endpoint doesn't check time)
          Or: Modify submission after judging started
```

**Approval bypass**:
```
Expected: User creates document → Manager approves → Document published
Attack:   User calls publish endpoint directly (no approval check)
```

### Detection Questions
- [ ] **Can any step be skipped?** Try calling endpoint N+1 without completing step N
- [ ] **Can any step be repeated?** Try submitting the same step multiple times
- [ ] **Can any step be reversed?** Try going back to a previous state after advancing
- [ ] **Are time constraints enforced server-side?** Deadlines, cooldowns, rate limits
- [ ] **Is the current state checked before transitions?** Or just the action?

### Fix Pattern
```typescript
// Validate state BEFORE allowing transition
async function publishDocument(docId: string, userId: string) {
  const doc = await getDocument(docId)
  
  // Check ownership
  if (doc.author_id !== userId) throw new Error('Not your document')
  
  // Check current state allows this transition
  if (doc.status !== 'approved') throw new Error('Document must be approved first')
  
  // Check approval exists and is valid
  const approval = await getApproval(docId)
  if (!approval || approval.revoked) throw new Error('Valid approval required')
  
  // Now safe to transition
  await updateDocument(docId, { status: 'published' })
}
```

## Category 3: Parameter Tampering

### Price / Quantity / Amount Manipulation
```typescript
// VULNERABLE — trusts client-sent price
export async function POST(request: Request) {
  const { productId, quantity, price } = await request.json()
  const total = quantity * price  // Attacker sends price: 0.01
  await createOrder(productId, quantity, total)
}
```

### Detection Questions
- [ ] **Does the server trust client-sent prices?** Always look up prices server-side
- [ ] **Can quantities be negative?** `-1 * $50 = -$50` credit to attacker
- [ ] **Can quantities be zero?** `0 * any_price = $0` order
- [ ] **Can quantities be fractional?** `0.001 * $100 = $0.10` (rounding exploits)
- [ ] **Are discount codes applied correctly?** Can they be stacked? Applied to wrong products? Used after expiration?
- [ ] **Are there integer overflow possibilities?** Extremely large quantities wrapping to negative

### Fix Pattern
```typescript
// SAFE — server-side price lookup, input validation
const ProductSchema = z.object({
  productId: z.string().uuid(),
  quantity: z.number().int().positive().max(100),
  // NO price field — never trust client prices
})

export async function POST(request: Request) {
  const input = ProductSchema.parse(await request.json())
  
  // Look up price server-side
  const product = await getProduct(input.productId)
  if (!product) throw new Error('Product not found')
  
  const total = input.quantity * product.price  // Server-side calculation
  await createOrder(input.productId, input.quantity, total)
}
```

## Category 4: Privilege Escalation via Logic

### Role Assignment Manipulation
```typescript
// VULNERABLE — user can set their own role during registration
export async function register(data: FormData) {
  const user = await supabase.auth.signUp({
    email: data.get('email'),
    password: data.get('password'),
    options: {
      data: {
        role: data.get('role') || 'user'  // Attacker sends role: 'admin'
      }
    }
  })
}
```

### Feature Gate Bypass
```typescript
// VULNERABLE — plan check only on frontend
export function PremiumFeature() {
  const { plan } = useUser()
  if (plan !== 'premium') return <UpgradePrompt />
  return <ActualFeature />  // API calls made from here still work for free users
}

// The API endpoint doesn't check the plan
export async function POST(request: Request) {
  // Missing: verify user has premium plan
  const result = await runPremiumFeature(request)  // Free users can call this directly
  return Response.json(result)
}
```

### Detection Questions
- [ ] **Can users set their own role/permissions?** During signup, profile update, or any mutation?
- [ ] **Are permission checks server-side or client-side only?** Client-side = no check
- [ ] **Can users access admin endpoints?** Try calling admin API routes with regular auth
- [ ] **Can free users access paid features?** Call the API directly, bypassing frontend gates
- [ ] **Can users modify their own permission-related fields?** Supabase RLS: can users UPDATE their own role column?

## Category 5: Financial Calculation Errors

### Rounding Exploitation
```typescript
// VULNERABLE — floating point arithmetic
const discount = price * 0.1  // $9.999999... rounds to?
const total = price - discount

// With many small transactions, rounding errors accumulate
```

### Currency/Precision Issues
```typescript
// VULNERABLE — mixing cents and dollars
const amount = req.body.amount  // Is this 10.00 (dollars) or 1000 (cents)?
await stripe.paymentIntents.create({ amount })  // Stripe expects cents
// If user sends 1 thinking it's $1, Stripe charges $0.01
// If user sends 100 thinking it's cents, Stripe charges $1.00
```

### Detection Questions
- [ ] **Are all monetary calculations using integers (cents)?** Not floating point
- [ ] **Is rounding consistent?** Same rounding direction everywhere
- [ ] **Are currency conversions validated?** Can user claim different currency than charged?
- [ ] **Are refund amounts validated?** Can user get refund > original purchase?
- [ ] **Are partial refunds calculated correctly?** Can rounding create money?

## Category 6: Resource Abuse

### Unlimited Resource Creation
```typescript
// VULNERABLE — no limit on API key creation
export async function createApiKey(userId: string) {
  return supabase.from('api_keys').insert({ user_id: userId, key: generateKey() })
  // No check on how many keys the user already has
}
```

### Free Tier Abuse
- Create multiple accounts to multiply free tier allowances
- Use free trial repeatedly (same email with +alias, different domains)
- Exhaust shared resources (storage, compute) within free tier

### Detection Questions
- [ ] **Are there limits on resource creation?** API keys, projects, teams, invites
- [ ] **Are limits enforced server-side?** Not just frontend counters
- [ ] **Can free trials be repeated?** Same person, new account?
- [ ] **Are shared resources protected?** Can one user exhaust resources for all?

## Agent Arena-Specific Logic Checks

Given our product stack, specifically check:
- [ ] **Challenge submission**: Can entries be modified after deadline?
- [ ] **Judging**: Can users influence their own scores? Access judge criteria early?
- [ ] **Leaderboard**: Can scores be manipulated? Can fake entries inflate rankings?
- [ ] **Wallet/Credits**: Double-spend? Negative amounts? Transfer to self for profit?
- [ ] **API Keys**: Unlimited creation? Key sharing detection? Usage beyond plan?
- [ ] **Replay**: Can past submissions be replayed against new challenges?
- [ ] **Admin**: Can non-admin users access admin functions?

## Master Checklist for Logic Review

For every feature, ask:
1. **What's the intended flow?** Map the happy path state machine
2. **What happens if I skip a step?** Try accessing later steps directly
3. **What happens if I go backwards?** Try reverting to a previous state
4. **What happens with boundary values?** 0, -1, MAX_INT, empty string, null
5. **What happens if I do it twice?** Idempotency? Double-charge? Double-claim?
6. **What happens if I do it concurrently?** See `race-condition-async` skill
7. **What if I'm not who I claim to be?** Access as wrong user, wrong role
8. **What if I supply unexpected types?** String where number expected, array where object expected
9. **Who benefits from this bug?** Follow the money/power/data
10. **What would a motivated attacker try?** Not just random fuzzing — targeted exploitation

## References

For race condition specific patterns, see `race-condition-async` skill.
For access control via RLS, see `rls-bypass-testing` skill.
For IDOR detection at scale, see `references/idor-patterns.md`.

