---
title: "When JWT Signatures Go Unchecked: A 17 Trillion Row Exposure"
description: "How one internal analytics API accepted unsigned JWTs, exposing an estimated 17 trillion rows, and what developers should audit in their own authentication code."
slug: "when-jwt-signatures-go-unchecked-a-17-trillion-row-exposure"
published: true
read_time: 7
created_at: "2026-09-30 21:37:35.502 +0000 UTC"
updated_at: "2026-09-30 21:37:35.505 +0000 UTC"
author: "Typen"
author_url: "https://typen.blog/@typen"
tags:
  - "Featured"
  - "Backend"
  - "Security"
  - "Databases"
---

# When JWT Signatures Go Unchecked: A 17 Trillion Row Exposure

How one internal analytics API accepted unsigned JWTs, exposing an estimated 17 trillion rows, and what developers should audit in their own authentication code.

![https://media.typen.blog/img/id/01a0f440-6146-76be-824e-3ddd11002abd](trendbot-736010812.webp)

## A Single Missing Check

Authentication systems often look complete from the outside. Tenant validation, audience checks, application allowlists, user lookups — each layer adds friction for an attacker. But as a recent Microsoft bug bounty disclosure shows, a single missing verification step can render all of that effort meaningless.

Security researcher Faav documented how an internal Microsoft analytics service called Titan accepted JSON Web Tokens (JWTs) without ever verifying their cryptographic signature. By crafting a token with `"alg": "none"` and an empty signature section, the researcher could impersonate an administrator and execute arbitrary SQL against connected analytics databases. The estimated reachable data volume: 17,333,335,124,315 rows.

The finding is a case study in how access-control logic can be comprehensive yet fundamentally broken. It is also a reminder that the most important part of JWT validation is the one developers most often skip.

## How the Bypass Worked

### The attack surface

The Titan API was discovered through automated subdomain enumeration. Its frontend sat behind a VPN-required page, but a separate API endpoint resolved to an Azure Cloud Services host. A public Swagger file listed four routes:

- `/GetConfiguration`
- `/GetOnboardedTables`
- `/v2/Query`
- `/v2/Insert`

Three of the four specified Azure AD bearer authentication. The exception was `/v2/Query`, which also accepted raw SQL. That route became the entry point.

### Walking through validation layers

The researcher started with a token from an external Entra test tenant. Each modification revealed the next validation check:

1. Changing the tenant produced a tenant error.
2. Changing the audience produced an audience error.
3. Changing the application ID produced an application allowlist error.
4. Changing the user principal name (UPN) reached a user lookup.

Throughout these changes, the token's signature remained untouched. Titan accepted new claims without re-verifying the signature — the first strong signal that signature validation was absent.

### The unsigned token

The researcher then replaced the token entirely with a synthetic JWT using this header:

```json
{
  "alg": "none",
  "typ": "JWT"
}
```

A normal signed JWT has three sections: `header.payload.signature`. This token ended with a bare period because the signature section was empty:

```
base64url(header).base64url(payload).
```

The payload used values Titan expected for audience, tenant, and application ID, but with a UPN the researcher controlled. Titan cleared the tenant, audience, and application checks, then returned:

```
User '[email protected]' not found
```

### The human hunch

Automated tooling had spent ten days testing email-formatted UPNs — the conventional format for Entra identities. All failed. The breakthrough came when the researcher stopped assuming the `upn` claim was being used as an email and considered that the backend might treat it as a local application username.

Changing the unsigned token's `upn` to `admin` returned the number `1`. The SQL query executed. Titan had resolved the unsigned claim to local user ID 1, which held the Admin role.

## What Was Reachable

Once inside, the researcher limited testing to metadata, table descriptions, and bounded sample rows. The platform metadata database contained:

- Approximately 25,000 account and email records
- 17,990 employee email records
- 15,001 employee organization records
- 355 database configurations
- 20,979 virtual-dataset SQL definitions
- 24,569 dashboards, 425,891 charts, and 27,347 dataset definitions

A separate Bing analytics source was also reachable. Two one-row queries confirmed access to search analytics containing search terms, identifiers, and country or state-level location derived from reverse IP. The researcher noted that MUIDs appeared across multiple datasets, making cross-service correlation plausible, though this was never attempted.

The total row count came from metadata across 17 ClickHouse databases. The researcher verified the count through two independent metadata paths: `system.tables.total_rows` and active `system.parts`. Both returned the same figure. The number is a storage estimate that likely includes historical, duplicated, and derived data, but it represents the technical scale of what was reachable.

## Why This Class of Bug Persists

JWT signature verification is not optional. The signature is what proves the token was issued by a trusted authority and has not been tampered with. Without it, every claim in the payload is attacker-controlled.

The Titan case is unusual only in scale. The underlying pattern — validating claims but not the signature — appears in production systems because:

- **Framework defaults can mislead.** Some JWT libraries require explicit configuration to enforce signature verification. A developer who decodes a token and reads claims may never realize verification was skipped.
- **Layered validation creates false confidence.** Tenant, audience, and application checks all passed. Each success made the system feel more secure, even though the foundation was missing.
- **Local username resolution obscures identity assumptions.** The `upn` claim is conventionally an email, but nothing enforces that. When a backend uses it as a database lookup key, an attacker can supply any value that resolves to a privileged account.

## What Developers Should Audit

### Verify signatures before reading claims

The order matters. Decode the token, verify the signature against the expected algorithm and key, then read claims. Never trust claims from an unverified token, even for logging or routing decisions.

Most JWT libraries provide a verify function that performs this step. If your code calls a decode function without verification, treat it as a bug.

### Reject the `none` algorithm explicitly

The `alg: none` case exists in the JWT specification for unsecured tokens. Production systems should reject it. Many libraries do this by default, but not all, and configuration can override defaults.

### Do not use claims as database keys without validation

If your application resolves a claim like `upn`, `email`, or `sub` to a local user record, validate that the claim matches an expected format and that the resolved user is authorized for the requested operation. An unsigned claim should never map directly to a privileged local account.

### Test with tampered tokens

A simple test: take a valid token, modify a claim, and send it to your API. If the request succeeds, signature verification is missing or broken. This test belongs in your integration suite.

### Audit all authentication paths

Titan had four routes. Three required Azure AD bearer authentication. One did not. The unprotected route accepted raw SQL. When auditing, enumerate every endpoint and verify that authentication is enforced consistently, especially on routes that accept powerful operations.

## Tradeoffs and Practical Considerations

Strict signature verification adds a small amount of latency and requires key management. For most applications, the cost is negligible compared to the risk. The harder tradeoff is organizational: authentication logic often lives in shared libraries or middleware, and a single misconfiguration can affect many services.

Centralizing JWT validation in a well-tested library reduces the chance of per-service mistakes. It also makes it easier to rotate keys and update algorithms without touching every codebase.

Another consideration is observability. The researcher noted that the `User not found` error should have been a signal that authentication had already succeeded. Logging authentication failures separately from authorization failures can help teams distinguish between a rejected token and a token that passed validation but mapped to no user.

## The Disclosure Timeline

The researcher reported the issue to Microsoft's MSRC on September 5, 2026. The API endpoint was locked down on September 9. A $5,000 bounty was awarded on September 17. Microsoft had editorial control over the published writeup, and the researcher noted that sections and figures were removed before publication.

Microsoft's statement on the finding: "We appreciate the opportunity to investigate the findings reported by Faav. Their submission and coordinated vulnerability disclosure helped us to better protect our customers by hardening our services."

## The Takeaway

The Titan vulnerability was not a sophisticated exploit. It was a missing check — the most important check in any token-based authentication system. The access-control logic around it was extensive, but it all depended on a foundation that was not there.

If you maintain authentication code, verify signatures first. Reject unsigned tokens. Never trust claims you have not verified. And when you test your system, try the simplest attack: change a claim and see what happens.

