Back to blog

153 Million Documents Leaked: What Changes for Anyone Who Asks for an ID Photo

Hello HaWkers, on September 1, 2026 journalist Brian Krebs reported that a new dark web service called Nexus was selling scanned images of more than 153 million driver's licenses from the United States and Canada. Alongside them came more than 10 million identity cards, more than 3 million travel documents and around 579 thousand medical insurance cards. Krebs himself found his own Virginia license offered as a free sample.

Have you ever stopped to think about how many buckets are holding a photo of your ID right now because you rented a car, checked into a hotel or opened an account? In this article I walk through what is known about the case, why the most common identity verification architecture keeps stacking up that risk, and what you can change in your code this week so you don't become the next headline.

What Happened: 153 Million Documents and a Vendor in the Middle

The trail points to a single company. Based on interviews with people whose licenses were up for sale, the likely source of the images is IDScan.net, an identity verification company headquartered in New Orleans, Louisiana, which validates documents for customers such as Hertz, FedEx, Caesars Entertainment and a long list of cannabis dispensaries across the United States.

Two technical details tighten the case. The records did not carry only the front and back photo: they also carried the infrared and ultraviolet scans that professional readers perform to check the security features on the plastic. And they carried timestamps that line up with the exact moment those people rented a car or traveled. No loose database dump on the internet produces that combination: this is what comes out of a capture device, at the point of service, on its way to the cloud.

The seller claimed to have been exfiltrating data continuously for more than a year, with the collection being updated all the time, and at one point advertised almost 400 thousand new licenses added in 24 hours. To give the collection some public weight, the license of the United States Secretary of Defense, Pete Hegseth, issued in Minnesota, was listed for 100 dollars.

The FBI office in New Orleans opened a formal inquiry into the origin of the images, and Krebs reported being put on a call with half a dozen agents, including leadership from the cyber division. Gillian Cossman, chief operating officer at IDScan.net, confirmed the company was investigating the incident. The Nexus site went offline shortly after the story ran, which gives nothing back: whoever bought, bought.

Why the Verification Vendor Is the Perfect Target

The identity verification economy organized itself like almost our entire stack: instead of every company building document reading, they all plug into the same handful of vendors. It is the right call for fraud detection quality, and it is the call that concentrates all the risk in a single point.

Think about what that means in volume. A single car rental company might hold a few million documents. A vendor serving car rentals, hotels, concert venues, dispensaries and banks holds the union of all of them — which, at the limit, is the adult population of two countries. The attacker no longer has to pick a target: they pick the vendor.

The second problem is time. A leaked password you change in 30 seconds. A card number the bank reissues in three days. A driver's license with photo, number, date of birth, address and signature stays valid for the next five or ten years, and the only way to "change" it is to move to another state. That is why a document image is a different category of data from everything else you store: it does not expire and it has no revocation.

And there is a third one, more uncomfortable: almost nobody knows where the images live. When you upload your ID into an app, you have a relationship with that brand. In practice the image went to its subprocessor, which may use third-party storage, which replicates to another region. That is exactly the chain the Nexus case exposed — the victims had never heard of IDScan.net.

The Architecture Mistake: Keeping the Image After the Check

Here is the part that matters to whoever writes the code. In the overwhelming majority of products, the document photo is an input to a binary decision: this person is over 18, this name matches the card, this document is authentic. The decision is what the business needs. The image is the residue.

Except the standard flow does the opposite: it uploads the image to a bucket, runs the verification, writes the result into a column and leaves the image there, "just in case", "for auditing", "because compliance might ask". Years later nobody remembers the bucket exists, and it now holds 40 million objects.

The healthy pattern is to invert it: the image is ephemeral, the verdict is persistent. In TypeScript, inside an upload handler, that is less work than it sounds.

// In-memory verification: the image never reaches a bucket.
import { randomUUID, createHash } from 'node:crypto';

type Verdict = {
  id: string;
  userId: string;
  isAdult: boolean;
  nameMatches: boolean;
  documentIsAuthentic: boolean;
  issuingState: string; // derived attribute, not the document
  provider: string;
  verifiedAt: string;
  // Fingerprint to deduplicate attempts without storing the image.
  imageDigest: string;
};

export async function verifyDocument(
  userId: string,
  image: Buffer,
  expectedName: string
): Promise<Verdict> {
  // 1. Send it to the provider and get attributes back only.
  const analysis = await kycProvider.analyze(image);

  // 2. Extract what the business actually needs.
  const verdict: Verdict = {
    id: randomUUID(),
    userId,
    isAdult: analysis.age >= 18,
    nameMatches: normalize(analysis.name) === normalize(expectedName),
    documentIsAuthentic: analysis.authenticityScore > 0.9,
    issuingState: analysis.state,
    provider: 'provider-x',
    verifiedAt: new Date().toISOString(),
    imageDigest: createHash('sha256').update(image).digest('hex'),
  };

  // 3. Discard the image before responding. No bucket, no queue, no log.
  image.fill(0);

  return verdict;
}

Notice what is left: no date of birth, no document number, no photo. If this database leaks tomorrow, the attacker walks away with booleans. And imageDigest still lets you answer "has this same document been used on another account?" without keeping the document.

If You Have to Store It, Store It With an Expiration Date

There are legitimate retention cases. Financial institutions have record-keeping obligations, and chargeback disputes call for evidence. The answer is not "never store", it is "store with a deadline, with a per-record key and with automatic deletion".

The per-record key matters because it changes the economics of a leak. With a single key for the whole bucket, whoever gets the key gets everything. With envelope encryption, each document has its own data key, encrypted by the KMS master key. The attacker who copies the bucket walks away with random bytes.

// Envelope encryption + expiration: the record destroys itself.
import { KMSClient, GenerateDataKeyCommand } from '@aws-sdk/client-kms';
import { createCipheriv, randomBytes } from 'node:crypto';

const kms = new KMSClient({});
const RETENTION_DAYS = 90;

export async function storeWithExpiry(image: Buffer, recordId: string) {
  // A fresh data key for every single document.
  const { Plaintext, CiphertextBlob } = await kms.send(
    new GenerateDataKeyCommand({ KeyId: 'alias/documents', KeySpec: 'AES_256' })
  );

  const iv = randomBytes(12);
  const cipher = createCipheriv('aes-256-gcm', Plaintext!, iv);
  const encrypted = Buffer.concat([cipher.update(image), cipher.final()]);

  await s3.putObject({
    Bucket: 'kyc-documents',
    Key: `${recordId}.bin`,
    Body: Buffer.concat([iv, cipher.getAuthTag(), encrypted]),
    // The expiration lives on the object, not on a process spreadsheet.
    Expires: new Date(Date.now() + RETENTION_DAYS * 864e5),
    Metadata: { key: CiphertextBlob!.toString('base64') },
  });

  // Wipe the plaintext key from memory as soon as it has been used.
  Plaintext!.fill(0);
}

Complete it with a lifecycle rule on the bucket that actually deletes the objects once the deadline passes. A policy declared in the infrastructure survives a team handover; a cleanup routine written by hand dies on the first deploy nobody reviewed.

The Metadata Nobody Looks At

The Nexus case carries a lesson that is easy to apply and easy to forget: the records for sale contained timestamps and the infrared and ultraviolet scans. In other words, along with the identity, the context leaked too — where and when that person was.

The same thing happens in your upload. A photo taken on a phone arrives with EXIF: device model, date, time and, quite often, GPS coordinates. If you store the file exactly as you received it, you did not store a document, you stored a document plus the location of whoever sent it.

// Normalize the image and drop every metadata field before any persistence.
import sharp from 'sharp';

export async function sanitize(input: Buffer): Promise<Buffer> {
  return sharp(input)
    .rotate() // applies the orientation and then throws the EXIF away
    .resize({ width: 1600, withoutEnlargement: true })
    .jpeg({ quality: 82, mozjpeg: true })
    .withMetadata({ exif: {} }) // no GPS, no device, no timestamp
    .toBuffer();
}

The same discipline applies to logs. A console.log(req.body) inside an upload handler ships the whole image, in base64, to your log aggregator — which usually has longer retention and looser access control than your database. Over there the leak does not even need a sophisticated attacker.

Age Verification Without Asking for the Document

The most honest question comes before all of this: do you actually need the image? For a large share of the cases — checking that someone is of age, confirming residence in a country, proving a person holds a valid license — the answer in 2026 is no.

The W3C Digital Credentials API landed in Chrome 141 and Safari 26 in September 2025, and Firefox already ships a basic implementation. It talks to the operating system wallet and uses the mdoc format from the ISO/IEC 18013-5 standard, the same one behind mobile driver's licenses around the world. The second edition of the standard is up for vote, with publication expected in the third quarter of 2026.

What changes for your code is selective disclosure: you ask for an attribute, not for a document.

// Ask only for "is over 18", never the number nor the address.
const response = await navigator.credentials.get({
  digital: {
    requests: [
      {
        protocol: 'openid4vp',
        data: {
          response_type: 'vp_token',
          nonce: serverNonce, // generated on the backend, single use
          dcql_query: {
            credentials: [
              {
                id: 'drivers-license',
                format: 'mso_mdoc',
                meta: { doctype_value: 'org.iso.18013.5.1.mDL' },
                // The entire request fits into one boolean field.
                claims: [{ path: ['org.iso.18013.5.1', 'age_over_18'] }],
              },
            ],
          },
        },
      },
    ],
  },
});

// A signed proof of "true" comes back. No photo, no number, no date.
await fetch('/api/age', { method: 'POST', body: JSON.stringify(response.data) });

The credential stays encrypted on the device, and what travels is a signed proof of the attribute. There is no image to leak because there is no image. This is the same debate that showed up around the California AB 1856 law on age verification: regulation pushes platforms to verify, and the way they verify decides whether the outcome is protection or a liability of 153 million records.

What to Ask Before Plugging In a Vendor

If verification goes to a third party, and it almost always does, the contract is part of the architecture. Four questions cover most of it:

  • Do you retain the image after the verdict? For how long, and how do I force deletion? If the answer is vague, the answer is "forever".
  • Who are the subprocessors and in which regions does the data sit? That list has to be written down and versioned, not mentioned in a sales call.
  • Is the encryption per record or per bucket? That decides whether unauthorized access costs one document or all of them.
  • Is there an API for on-demand deletion, and does it wipe backups? Under the GDPR and similar laws you are the controller. The obligation to answer the data subject is yours, even if the data sits with the vendor.

Add the hygiene basics on top: vendor API credentials with rotation, minimum scope and volume alerting. The Nexus seller claimed to have been exfiltrating for more than a year, with 400 thousand records in 24 hours. A read-per-credential chart would have screamed long before that.

What to Do On Our Side

As a user, what you can do is limited, but it is not nothing. Ask why a business needs to copy your document and what happens to the copy. Prefer a digital pass or an on-site scan instead of emailing the photo or sending it over a messaging app. Turn on credit alerts with the bureaus. And be suspicious of approaches that quote correct details from your document: with a collection like this one, the social engineering scam is convincing by default.

The broader point is the same one I discussed in the post about the WhatsApp lawsuit around end-to-end encryption: data that exists is data that can be subpoenaed, stolen or sold. The only protection that does not depend on somebody else's competence is the data that was never collected.

What This Leaves for 2026

Three practical takeaways.

The first is that the "just upload a photo of your ID" era is ending, and not out of kindness: it is getting expensive. European regulators already treat retaining document images as disproportionate when a technical alternative exists, and now the technical alternative exists in stable browsers.

The second is that identity verification became a critical dependency, on the same level as a payment provider. It deserves a contingency plan, a contract review and volume monitoring — not an integration shipped in one sprint and never touched again.

The third is the most direct one. Open up, today, the inventory of where your product stores document images. Measure how many objects exist, since when, and how many would still be necessary if you deleted everything past its deadline. On most teams that number is scary, and that is exactly why it is worth running before somebody on the outside runs it for you.

Let's go! 🦅

📚 Want to Keep Up With What Is Coming?

This article covered the leak of 153 million documents and what it changes about identity verification, but the ecosystem shifts every week and not everything becomes an article here.

On X I share what I am testing, the behind the scenes of my projects and the news that shows up before it turns into a post.

Follow Me There

👉 Follow @jeffbruchado on X

💡 Daily content about development, career and the tools I actually use

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments