Esprit CLI detects business logic flaws that exploit intended functionality to violate domain invariants. Unlike injection attacks, these vulnerabilities abuse legitimate features to achieve unauthorized outcomes like financial fraud, privilege retention, or workflow bypass.
Business logic flaws require understanding the business domain, not just payload catalogs. Every invariant must be enforced server-side.
Attack Surface Coverage
Critical Business Logic
Financial flows - Pricing, payments, refunds, credits, discounts
Account lifecycle - Signup, trials, upgrades, suspensions
Authorization workflows - Approval chains, role transitions
Quotas and limits - Rate limits, usage caps, inventory
Multi-tenant isolation - Cross-organization data/action bleed
Event-driven flows - Jobs, webhooks, sagas, idempotency
High-Value Targets
Esprit prioritizes testing:
Pricing and Payments
Price manipulation in cart/checkout
Discount stacking and mutual exclusivity
Tax and shipping calculation
Currency arbitrage
Payment capture/void/refund sequences
Partial refunds exceeding original amount
Credits and Vouchers
Gift card/credit issuance and redemption
Multiple redemption of single-use codes
Balance manipulation
Expiry bypass
Subscriptions
Trial extension
Upgrade/downgrade proration
Seat count manipulation
Metering and usage reporting
Quotas and Rate Limits
Daily/monthly usage limits
Inventory reservation leaks
Distributed counter inconsistencies
State Machine Vulnerabilities
Esprit detects workflow bypass:
Step Skipping
// Vulnerable: Multi-step checkout process
// Step 1: POST /cart/items
app . post ( '/cart/items' , authenticate , async ( req , res ) => {
const item = await addToCart ( req . user . id , req . body );
res . json ({ cartId: item . cartId });
});
// Step 2: POST /cart/shipping
app . post ( '/cart/shipping' , authenticate , async ( req , res ) => {
await setShipping ( req . user . id , req . body );
res . json ({ success: true });
});
// Step 3: POST /cart/finalize - VULNERABLE
app . post ( '/cart/finalize' , authenticate , async ( req , res ) => {
// VULNERABLE: No validation that shipping was set!
const order = await finalizeCart ( req . user . id );
res . json ({ orderId: order . id });
});
Exploitation:
# Skip shipping step to avoid shipping charges
curl -X POST /cart/items -d '{"productId": "prod_123", "qty": 1}'
curl -X POST /cart/finalize # Skip /cart/shipping entirely
State Replay
// Vulnerable: Apply discount endpoint
app . post ( '/cart/discount' , authenticate , async ( req , res ) => {
const { code } = req . body ;
const discount = await Discount . findOne ({ code });
// VULNERABLE: No check if already applied
await Cart . updateOne (
{ userId: req . user . id },
{ $inc: { discount: discount . amount } }
);
res . json ({ success: true });
});
Exploitation:
# Apply same discount code multiple times
for i in { 1..10} ; do
curl -X POST /cart/discount -d '{"code": "SAVE20"}'
done
Out-of-Order Execution
// Vulnerable: Refund before capture
app . post ( '/payments/:id/refund' , authenticate , async ( req , res ) => {
const payment = await Payment . findById ( req . params . id );
// VULNERABLE: No state validation
await payment . update ({ status: 'refunded' });
await issueRefund ( payment . amount );
res . json ({ success: true });
});
Exploitation:
# Refund payment that was only authorized, not captured
curl -X POST /payments/pay_pending/refund
# Result: Free money without deducting from captured funds
Concurrency Vulnerabilities
Esprit tests race conditions:
Parallel Resource Consumption
// Vulnerable: Credit redemption
app . post ( '/credits/redeem' , authenticate , async ( req , res ) => {
const user = await User . findById ( req . user . id );
if ( user . credits >= 10 ) {
// VULNERABLE: Non-atomic check-then-act
await User . updateOne (
{ _id: req . user . id },
{ $inc: { credits: - 10 } }
);
await grantPremiumFeature ( req . user . id );
res . json ({ success: true });
} else {
res . status ( 400 ). json ({ error: 'Insufficient credits' });
}
});
Exploitation:
# Send 10 parallel requests with 10 credits available
# Result: Premium feature granted 10 times, credits go negative
seq 10 | xargs -P10 -I {} curl -X POST /credits/redeem
Esprit detection: Sends parallel requests to detect race conditions
Idempotency Bypass
// Vulnerable: Idempotency key scoped incorrectly
app . post ( '/payments/charge' , authenticate , async ( req , res ) => {
const idempotencyKey = req . headers [ 'idempotency-key' ];
// VULNERABLE: Key not scoped to user
const cached = await cache . get ( idempotencyKey );
if ( cached ) return res . json ( cached );
const charge = await processPayment ( req . body );
await cache . set ( idempotencyKey , charge , 3600 );
res . json ( charge );
});
Exploitation:
# User A processes payment with key "abc123"
curl -X POST /payments/charge \
-H "Idempotency-Key: abc123" \
-H "Authorization: Bearer user-a-token" \
-d '{"amount": 100}'
# User B reuses same key to get User A's result
curl -X POST /payments/charge \
-H "Idempotency-Key: abc123" \
-H "Authorization: Bearer user-b-token" \
-d '{"amount": 999}'
# Result: Returns User A's charge info to User B
Numeric and Currency Issues
Esprit detects calculation flaws:
Price Manipulation
// Vulnerable: Client-computed total
app . post ( '/orders/create' , authenticate , async ( req , res ) => {
const { items , total } = req . body ;
// VULNERABLE: Trusts client-provided total
const order = await Order . create ({
userId: req . user . id ,
items ,
total // Should be server-computed!
});
res . json ( order );
});
Exploitation:
# Send manipulated total
curl -X POST /orders/create -d '{
"items": [
{"id": "prod_123", "price": 99.99, "qty": 10}
],
"total": 0.01
}'
Rounding Errors
// Vulnerable: Rounding in user's favor
app . post ( '/cart/discount' , authenticate , async ( req , res ) => {
const cart = await Cart . findOne ({ userId: req . user . id });
// Per-item discount with rounding
for ( const item of cart . items ) {
// VULNERABLE: Rounding down each item
item . discount = Math . floor ( item . price * 0.1 );
}
await cart . save ();
res . json ( cart );
});
Exploitation:
# Add many low-price items to maximize rounding loss
# 100 items at $1.00 each
# Expected discount: $10.00
# Actual discount: $0 (Math.floor(1.00 * 0.1) = 0 per item)
Currency Arbitrage
// Vulnerable: Stale exchange rates
app . post ( '/payments/currency-switch' , authenticate , async ( req , res ) => {
const { fromCurrency , toCurrency } = req . body ;
const balance = await getBalance ( req . user . id , fromCurrency );
// VULNERABLE: Uses cached/stale rate
const rate = await getExchangeRate ( fromCurrency , toCurrency );
const convertedAmount = balance * rate ;
await setBalance ( req . user . id , fromCurrency , 0 );
await setBalance ( req . user . id , toCurrency , convertedAmount );
res . json ({ success: true });
});
Exploitation:
# Abuse rate staleness or rounding
# Convert USD -> EUR -> USD repeatedly
# Gain value from rounding or delayed rate updates
Feature Gate Bypass
Esprit detects authorization logic flaws:
Client-Side Feature Flags
// Vulnerable: Feature flag checked client-side only
// Client code:
if ( user . plan === 'premium' ) {
showAdvancedFeature ();
}
// API endpoint - VULNERABLE
app . post ( '/api/advanced-feature' , authenticate , async ( req , res ) => {
// No server-side plan check!
const result = await executeAdvancedFeature ( req . user . id , req . body );
res . json ( result );
});
Exploitation:
# Basic user calls premium endpoint directly
curl -X POST /api/advanced-feature \
-H "Authorization: Bearer basic-user-token" \
-d '{"data": "..."}
Role Transition Leaks
// Vulnerable: Cached permissions after downgrade
app . post ( '/users/downgrade' , authenticate , async ( req , res ) => {
await User . updateOne (
{ _id: req . user . id },
{ $set: { plan: 'basic' } }
);
// VULNERABLE: Doesn't invalidate cached permissions
res . json ({ success: true });
});
// Premium feature check
app . post ( '/api/premium-feature' , authenticate , async ( req , res ) => {
// VULNERABLE: Uses cached user object
if ( req . user . plan === 'premium' ) { // Stale!
return res . json ( await executePremiumFeature ());
}
res . status ( 403 ). json ({ error: 'Premium required' });
});
Example Scenarios
Scenario 1: Double Refund
// Vulnerable code: src/api/refunds.js:45
app . post ( '/refunds/create' , authenticate , async ( req , res ) => {
const { orderId , amount } = req . body ;
const order = await Order . findById ( orderId );
if ( order . userId !== req . user . id ) {
return res . status ( 403 ). json ({ error: 'Forbidden' });
}
// VULNERABLE: No check for existing refunds
const refund = await Refund . create ({
orderId ,
amount ,
userId: req . user . id
});
await issueRefundToPaymentMethod ( refund );
res . json ( refund );
});
Exploitation:
# Refund via customer portal
curl -X POST /refunds/create -d '{"orderId": "ord_123", "amount": 99.99}'
# Also contact support who issues manual refund
# Result: Double refund totaling $199.98 for $99.99 order
Impact: Direct financial loss
Scenario 2: Inventory Reservation Leak
// Vulnerable code: src/services/inventory.js:78
app . post ( '/cart/reserve' , authenticate , async ( req , res ) => {
const { productId , quantity } = req . body ;
const available = await Inventory . findOne ({ productId });
if ( available . quantity >= quantity ) {
// VULNERABLE: Reserves without expiry or cleanup
await Inventory . updateOne (
{ productId },
{ $inc: { quantity: - quantity , reserved: quantity } }
);
await Cart . create ({
userId: req . user . id ,
productId ,
quantity
});
res . json ({ success: true });
}
});
Exploitation:
# Repeatedly add to cart without completing purchase
for i in { 1..1000} ; do
curl -X POST /cart/reserve -d '{"productId": "prod_123", "qty": 1}'
done
# Result: Inventory locked by abandoned reservations
# Legitimate customers see "out of stock"
Impact: Denial of inventory, revenue loss
Esprit recommends:
Enforce State Machine Transitions
// SAFE: Validate preconditions
const STATE_MACHINE = {
pending: [ 'processing' ],
processing: [ 'completed' , 'failed' ],
completed: [ 'refunded' ],
failed: [ 'pending' ],
refunded: []
};
app . post ( '/orders/:id/transition' , authenticate , async ( req , res ) => {
const { targetState } = req . body ;
const order = await Order . findById ( req . params . id );
// Validate ownership
if ( order . userId !== req . user . id ) {
return res . status ( 403 ). json ({ error: 'Forbidden' });
}
// Validate transition
const allowedTransitions = STATE_MACHINE [ order . state ] || [];
if ( ! allowedTransitions . includes ( targetState )) {
return res . status ( 400 ). json ({
error: `Cannot transition from ${ order . state } to ${ targetState } `
});
}
order . state = targetState ;
await order . save ();
res . json ( order );
});
Atomic Operations
// SAFE: Atomic check-and-update
app . post ( '/credits/redeem' , authenticate , async ( req , res ) => {
const result = await User . updateOne (
{
_id: req . user . id ,
credits: { $gte: 10 } // Atomic check
},
{
$inc: { credits: - 10 } // Atomic update
}
);
if ( result . modifiedCount === 0 ) {
return res . status ( 400 ). json ({ error: 'Insufficient credits' });
}
await grantPremiumFeature ( req . user . id );
res . json ({ success: true });
});
Server-Side Computation
// SAFE: Server computes all financial values
app . post ( '/orders/create' , authenticate , async ( req , res ) => {
const { items } = req . body ;
// Fetch current prices from database
const products = await Product . find ({
_id: { $in: items . map ( i => i . productId ) }
});
// Server computes subtotal
let subtotal = 0 ;
for ( const item of items ) {
const product = products . find ( p => p . id === item . productId );
subtotal += product . price * item . quantity ;
}
// Apply discounts (server-side validation)
const discount = await calculateDiscount ( req . user . id , items );
// Calculate tax (server-side)
const tax = await calculateTax ( subtotal - discount , req . user . address );
// Final total
const total = subtotal - discount + tax ;
const order = await Order . create ({
userId: req . user . id ,
items ,
subtotal ,
discount ,
tax ,
total
});
res . json ( order );
});
Proper Idempotency
// SAFE: User-scoped idempotency keys
app . post ( '/payments/charge' , authenticate , async ( req , res ) => {
const idempotencyKey = req . headers [ 'idempotency-key' ];
if ( ! idempotencyKey ) {
return res . status ( 400 ). json ({ error: 'Idempotency-Key required' });
}
// Scope key to user
const cacheKey = `payment: ${ req . user . id } : ${ idempotencyKey } ` ;
const cached = await cache . get ( cacheKey );
if ( cached ) return res . json ( cached );
const charge = await processPayment ({
userId: req . user . id ,
... req . body
});
// Cache with TTL
await cache . set ( cacheKey , charge , 86400 );
res . json ( charge );
});
Validate Invariants
// SAFE: Check domain invariants
app . post ( '/refunds/create' , authenticate , async ( req , res ) => {
const { orderId , amount } = req . body ;
const order = await Order . findById ( orderId );
// Check ownership
if ( order . userId !== req . user . id ) {
return res . status ( 403 ). json ({ error: 'Forbidden' });
}
// Check order state
if ( order . state !== 'completed' ) {
return res . status ( 400 ). json ({ error: 'Order not completed' });
}
// Calculate existing refunds
const existingRefunds = await Refund . aggregate ([
{ $match: { orderId: order . _id } },
{ $group: { _id: null , total: { $sum: '$amount' } } }
]);
const totalRefunded = existingRefunds [ 0 ]?. total || 0 ;
// Validate invariant: total refunds <= order total
if ( totalRefunded + amount > order . total ) {
return res . status ( 400 ). json ({
error: 'Refund exceeds order total' ,
orderTotal: order . total ,
alreadyRefunded: totalRefunded ,
requested: amount
});
}
const refund = await Refund . create ({
orderId ,
amount ,
userId: req . user . id
});
await issueRefundToPaymentMethod ( refund );
res . json ( refund );
});
Esprit validates that all business invariants are enforced at the service layer, not just at the API gateway or UI.
Detection Output
Esprit provides detailed business logic findings:
[HIGH] State machine bypass in checkout flow
Location: src/api/checkout.js:89
Invariant Violated: Shipping required before finalization
Vulnerable Flow:
POST /cart/items → 201 Created
POST /cart/finalize → 200 OK (SKIPPED /cart/shipping)
Expected Flow:
POST /cart/items → POST /cart/shipping → POST /cart/finalize
Impact:
- Zero shipping charges
- Orders shipped without address validation
- Per-order loss: $5-15 shipping fee
- Scalable via automation
Proof:
Order ord_123 created with:
- Subtotal: $99.99
- Shipping: $0.00 (should be $8.99)
- Total: $99.99 (should be $108.98)
Shipping address: null
Order status: confirmed
Remediation:
app.post('/cart/finalize', authenticate, async (req, res) => {
const cart = await Cart.findOne({ userId: req.user.id });
// Validate preconditions
if (!cart.shippingAddress) {
return res.status(400).json({
error: 'Shipping address required'
});
}
// Continue with finalization...
});
Next Steps
IDOR Detection Detect object-level authorization issues
SQL Injection Find database injection vulnerabilities