Detecting Business-Logic Abuse with the Spans Your APM Already Has

Cart cycling, refund fraud, free-tier abuse, coupon stacking — none of these match a WAF rule, but all show up in trace data if you know what to query.

May 9, 2026·7 min read

Detecting Business-Logic Abuse with the Spans Your APM Already Has

Most security tools are built to detect attacks that look obviously bad — SQL injection, XSS, malicious user agents. Business-logic abuse is the opposite. The requests are syntactically perfect, the user agents are real browsers, the IPs are residential proxies indistinguishable from your customers. Only the patterns are wrong.

This post walks through how to catch the four most common business-logic abuse patterns using trace data you already have. For broader context on the APM-as-security approach, see APM + security in one tool.

The four patterns

Cart cycling. A bot adds items to cart, then removes them, then adds them again, repeatedly. The goal is usually inventory probing (does this size still exist?) or competitive scraping. Hundreds of POST /cart/add followed by POST /cart/remove from the same session over a few hours.

Refund fraud. A customer (real or stolen identity) places orders, receives them, then submits refund requests claiming "item didn't arrive." Repeated across multiple accounts that share device fingerprint, payment method, or shipping address fragments.

Free-tier abuse. Mass account creation to consume free-tier resources at scale. Each individual signup is legitimate; the aggregate is abusive. Often correlated by email patterns (user1@gmail.com, user2@gmail.com), IP ranges, or signup timing.

Coupon stacking. Repeated checkout attempts with different coupon codes, looking for codes that aren't yet applied or are stackable. POST /checkout with coupon=FOO, then coupon=BAR, then coupon=BAZ, etc.

All four show up cleanly in trace data. Here's how.

Detection rule 1: cart cycling

SELECT
  resource_attributes['enduser.id'] AS user_id,
  COUNT() AS total_cart_actions,
  countIf(span_attributes['http.target'] = '/cart/add') AS adds,
  countIf(span_attributes['http.target'] = '/cart/remove') AS removes
FROM otel_traces
WHERE
  span_attributes['http.target'] IN ('/cart/add', '/cart/remove') AND
  timestamp > now() - INTERVAL 1 HOUR
GROUP BY user_id
HAVING adds > 30 AND removes > 20 AND total_cart_actions > 100
ORDER BY total_cart_actions DESC;

Adjust thresholds based on your normal cart behavior. Most legitimate users have under 20 cart actions per session.

Detection rule 2: refund fraud signals

Refund fraud is harder because the attack window is long (days to weeks). The detection has two parts: per-account anomaly and cross-account correlation.

Per-account: flag accounts where the refund-to-order ratio exceeds 30% over a 30-day window.

SELECT
  resource_attributes['enduser.id'] AS user_id,
  countIf(span_attributes['http.target'] = '/orders' AND span_attributes['http.method'] = 'POST') AS orders,
  countIf(span_attributes['http.target'] LIKE '/orders/%/refund' AND span_attributes['http.method'] = 'POST') AS refunds
FROM otel_traces
WHERE timestamp > now() - INTERVAL 30 DAY
GROUP BY user_id
HAVING orders > 5 AND refunds / orders > 0.3
ORDER BY refunds DESC;

Cross-account: find accounts that share device fingerprints or payment methods. This requires custom span attributes for the fingerprint — typically you'd add them in the checkout flow.

Detection rule 3: free-tier abuse

Look for signup bursts from one IP, ASN, or email pattern.

-- Burst signups from one IP
SELECT
  resource_attributes['client.address'] AS ip,
  COUNT() AS signups,
  groupArray(span_attributes['enduser.email']) AS emails
FROM otel_traces
WHERE
  span_attributes['http.target'] = '/api/auth/signup' AND
  span_attributes['http.status_code'] = '200' AND
  timestamp > now() - INTERVAL 1 HOUR
GROUP BY ip
HAVING signups > 10
ORDER BY signups DESC;

The groupArray(emails) is the giveaway — patterns like user1@gmail.com, user2@gmail.com, user3+test@gmail.com from one IP are not real customers.

Detection rule 4: coupon stacking

SELECT
  resource_attributes['enduser.id'] AS user_id,
  COUNT() AS coupon_attempts,
  COUNT(DISTINCT span_attributes['app.coupon_code']) AS distinct_codes
FROM otel_traces
WHERE
  span_attributes['http.target'] = '/checkout' AND
  span_attributes['app.coupon_code'] IS NOT NULL AND
  timestamp > now() - INTERVAL 30 MINUTE
GROUP BY user_id
HAVING coupon_attempts > 5 AND distinct_codes > 3
ORDER BY coupon_attempts DESC;

This requires you to attach the coupon code as a span attribute (app.coupon_code), which is a one-line change in your checkout handler. Once you do, the rule above catches stacking attempts almost immediately.

What custom instrumentation you need

The default OpenTelemetry auto-instrumentation captures HTTP method, path, and status. For business-logic detection you need to add:

  • enduser.id — your authenticated user identifier (most teams already have this in JWT claims)
  • enduser.email — for signup-pattern detection (mind the privacy implications, hash if needed)
  • Custom attributes specific to the action (app.coupon_code, app.order_id, app.cart_size)

These are one-line additions per handler:

import { trace } from '@opentelemetry/api';

app.post('/checkout', (req, res) => {
  const span = trace.getActiveSpan();
  span?.setAttribute('enduser.id', req.user.id);
  span?.setAttribute('app.coupon_code', req.body.coupon || null);
  span?.setAttribute('app.cart_size', req.body.items.length);
  // ... rest of handler
});

Once added, every span carries the attributes and your detection queries can pivot on them.

The pattern across all four

Three commonalities in business-logic detection:

  1. Group by user / session, not by endpoint. The endpoint is fine — that's why a WAF can't catch it. The same user doing too many of an action is the signal.
  2. Time-window thresholds. Per-minute or per-hour rates, not absolute counts. Threshold tuning is iterative.
  3. Multiple weak signals beat one strong signal. No single rule catches all abuse; combine cart cycling + free-tier abuse + coupon stacking detection and the false-positive rate drops.

What you need from your data layer

For these detections to be practical, the trace backend has to support:

  • Flexible GROUP BY and HAVING clauses (most do; Datadog's query language is awkward but workable)
  • Custom attributes preserved without sampling (some APM tools sample these out)
  • Reasonable retention (30+ days) for the cross-time correlations

OpenTelemetry-native tools like SecureNow and SecureNow dedicated instance handle this natively because the underlying storage is ClickHouse with full attribute preservation. Per-host APM tools sometimes sample attributes; check before assuming the data is there.

The honest limitation

These detection rules catch the obvious version of each pattern. Sophisticated attackers will adapt — distribute their cart cycling across multiple accounts, use IP rotation to evade signup bursts, write coupon-stacking attempts that each look like a normal session.

For sophisticated abuse, no SQL rule on its own is sufficient. The detection becomes ML-based: behavioral models that learn each user's baseline, then flag deviations. That's a separate stack.

For the 80% of business-logic abuse that's run by unsophisticated bots and competitors, the SQL rules above are enough. Deploy them tomorrow, tune the thresholds against your real traffic, iterate.

Related

Frequently Asked Questions

What is business-logic abuse?

Attacks that exploit valid application functionality in unintended ways — cart cycling, coupon stacking, mass account creation for free-tier abuse, refund fraud. The requests look syntactically legal; the patterns reveal abuse.

Why don't WAFs catch this?

WAFs match payload signatures (SQL injection, XSS) against rule sets. Business-logic abuse uses legitimate inputs — valid coupon codes, valid product IDs, valid user accounts. There's no malicious payload to match.

Can RASP detect it?

Some RASP products have basic business-logic rules but they're typically narrow. The flexibility of trace-data SQL queries beats canned RASP rules for this category.

What's the simplest detection rule?

User-aware aggregation. Group requests by user (or session) and count actions per minute. Anomalous users — way more cart additions, refund requests, free signups than peers — surface immediately. Threshold tuning is the work.

Recommended reading

A Next.js AWS App That Investigates And Blocks Its Own Attack

We deployed a real Next.js app to AWS, connected SecureNow traces, logs, body capture, multipart metadata, and firewall, then used an AI-assisted MCP workflow to block the attacker IP.

May 18
10 Web Development Security Best Practices for Node.js

A prioritized checklist of web development security best practices for Node.js teams, from secure coding to production monitoring and incident response.

May 12
web development security best practices
What 1.2B Requests Look Like: Anomaly Patterns from the SecureNow Firewall Fleet

Aggregated, anonymized data from 1.2B requests across the SecureNow customer fleet. Top anomaly types, peak hours, and the day-of-week patterns nobody publishes.

May 9