In short

In May 2019 a journalist reported that First American's EaglePro application exposed hundreds of millions of title and escrow document images: changing the sequential document number in a link, which needed no login and never expired, opened other customers' documents. The company's own penetration test had reported the flaw as serious in January 2019, but a clerical error entered it as low severity, it missed even the longer deadline, and no one asked for a risk acceptance. The SEC found the executives who approved the company's statements had not been told about that report; NYDFS found access-control and risk-assessment failures. Authorize every object access, and make sure a finding cannot quietly lose severity on its way to the tracker.

  • The flaw was an insecure direct object reference (CWE-639): a link opened a document by number, with no check that the requester was entitled to it.
  • Links needed no authentication and, until 24 May 2019, never expired; some documents were also cached by public search engines.
  • An internal pen test reported it in January 2019; it was logged as level 2 (low risk, 90 days) instead of level 3 (45 days), and the 8 May deadline passed with no fix and no risk acceptance.
  • The SEC case is about disclosure controls: senior executives approved public statements without knowing about the January report. Penalty $487,616, without admitting or denying the findings.
  • NYDFS found violations of its cybersecurity regulation's access-privilege and risk-based policy sections and imposed a $1,000,000 penalty in a November 2023 consent order.
  • Unguessable IDs and expiring links reduce exposure, but only an authorization check on every request fixes an IDOR.

Incident at a glance

Dates
to
Category
Data breach
Organizations
  • First American Financial Corporation
  • First American Title Insurance Company
Impact
EaglePro links opened any document by sequential ID without a login, exposing title and escrow images back to 2003, some with Social Security numbers and bank details. The flaw was found in January 2019 and not fixed.
How it was found
First American's own penetration test reported it in January 2019; it became public when a cybersecurity journalist contacted the company on 24 May 2019.

Timeline

  1. EaglePro gains links that open documents without login or expiry.
  2. An internal penetration test report rates the flaw serious (level 3).
  3. The deadline set by the mistaken level 2 entry passes; no fix and no risk acceptance.
  4. A journalist reports the exposure; First American shuts down external access and disables all links.
  5. First American furnishes a Form 8-K without its executives knowing of the January report.
  6. The SEC finds a disclosure controls violation and imposes a $487,616 penalty.
  7. NYDFS announces a consent order with a $1,000,000 penalty.

Verify it

  1. For every endpoint that returns a record or file by ID, write a test that requests another user's ID with a valid session and expects a 403 or 404, and one without a session that expects 401.
  2. Find every shareable or public link your application generates and confirm each one expires, can be revoked, and grants access only to what was shared.
  3. Compare the severity in your last three penetration test reports with the severity in your tracker, finding by finding.
  4. List open findings past their deadline and confirm each has a recorded risk acceptance from the person your policy names.
  5. Check that your incident and disclosure process requires security to brief the people who approve public statements on prior findings about the affected system.
Applies to
  • First American EaglePro Document links from October 2014 until external access was shut down on 24 May 2019

Applications that share documents or records through links or object IDs, and the teams that triage penetration test findings, track remediation deadlines and brief the people who make public statements.

Part of
Workflow: Build an AppSec program. Collection: case studies

Fix it in your stack

Tested fixes: the vulnerable code, the fix, a plausible fix that still fails, and the regression tests that tell them apart.

Learning goals and prerequisitesAbout 28 minutes: 10 to read and 18 for the exercise

What you will learn

  • Explain how sequential IDs in unauthenticated links exposed other customers' documents, and test a route for the same flaw.
  • Trace how a correct penetration test finding was lost between the report, the tracker and the deadline.
  • Design remediation tracking that reconciles severities and forces a fix or a named risk acceptance.
  • Separate what the SEC and NYDFS found from what remains unknown.

Before you start

Application developers and AppSec engineers who build sharing and document features, and the security and engineering leads who run remediation tracking and incident communication

  • Know what an insecure direct object reference (IDOR, also called BOLA) is.
  • Know how your organization records penetration test findings and remediation deadlines.
  • For the code: Node 22 or later for the test, and Python 3 for the SLA check.

Refresh first: Enforce authorization for every object action

Go to the guided practice

How one link number opened another customer's file

First AmericanAnyone with alinkEagleProImage repositoryRemediationtrackerLink with a sequentialdocument ID1Same link,neighbouring ID2Fetch by ID, no ownercheck3Another customer'sdocument3Level 3 findingentered as level 24Trust boundaryUntrusted input1Step, described below
  1. Share a packageA First American employee selects documents for a transaction, and EaglePro emails the recipient a link that opens them without a login.
  2. Change the numberAnyone with a link changes the document number in the URL to a nearby value.
  3. No authorization checkEaglePro serves the document with that number, whoever asks and whatever transaction it belongs to.
  4. Found and lostAn internal penetration test reports the flaw in January 2019; it is logged at the wrong severity and is still open when a journalist reports it on 24 May 2019.
The link proved nothing about who was asking. The fix is a check that the requester may see that specific document, on every request.

A document link that opened other customers' documents

First American, a US title insurance and settlement company, used an application called EaglePro to send document images from real estate transactions to the people involved. On the morning of 24 May 2019 a cybersecurity journalist told the company its application was 'leaking more than 800 million documents from real estate transactions going back from 2003', including bank account numbers, Social Security numbers and driver's license images. Changing the document number in a link 'yielded other people's records'. First American shut down external access to EaglePro and disabled every link it had generated before the article was published that evening.

The flaw was ordinary: an insecure direct object reference (IDOR). What makes the case worth studying is what happened before May. The company's own security team had found the same flaw in a penetration test and reported it in January 2019. A clerical error recorded it at a lower severity, the longer deadline that gave it passed, and no one asked for a risk acceptance. When the company made its public statements, the executives approving them did not know any of that.

Two regulators examined the case. The SEC's order of 14 June 2021 is about disclosure controls, and NYDFS's consent order of November 2023 is about access controls and risk-based policies under New York's cybersecurity regulation. This page reports what those orders found; it draws no legal conclusions.

Timeline

Timeline
WhenEventSource
2013First American launches EaglePro.SEC order
October 2014An update lets employees create links to related document images; anyone with a link can open them without logging in, and the links do not expire.NYDFS consent order
2018-12-17Preliminary penetration test findings go to the remediation and application security teams, recommending the issue be addressed 'as soon as possible'.NYDFS consent order
2019-01-11The final report rates the flaw serious (level 3): replacing the document ID 'with another sequential number allows access to other non-related document sessions without authentication'.SEC order
February 2019The finding is entered in the tracking system as level 2 (low risk) by a clerical error; the system sets a deadline 90 days out, 8 May 2019, instead of 45 days.SEC order
2019-05-08The 90-day deadline passes; the flaw is not fixed and no waiver or risk acceptance is requested.SEC order
2019-05-24A journalist reports the exposure. The CISO learns of the January report that morning; First American shuts down external access and disables all links.SEC order; NYDFS consent order
2019-05-28First American furnishes a Form 8-K; the executives responsible still do not know about the January report.SEC order
2020-07-21NYDFS files a statement of charges.NYDFS consent order
2021-06-14The SEC finds a disclosure controls violation; penalty $487,616, settled without admitting or denying.SEC order
2023-11-28NYDFS announces the consent order and a $1,000,000 penalty.NYDFS press release

The flaw: sequential IDs, no login, no expiry

Every document uploaded to First American's image repository got a numbered document ID. EaglePro links pointed at documents by that number. According to the SEC, 'a user could take the URL generated as part of an EaglePro package ... and alter the digits in the URL to view other document images to which the user should not have had access.' NYDFS adds that the link 'provided access to title-related documents to anyone with the link', 'without login or authentication', and that 'until May 24, 2019, EaglePro hyperlinks had no expiration date'. Some images sent through unsecured packages had also been cached by public search engines.

Every protection the design relied on was outside the request. Staff were 'instructed not to transmit documents containing NPI through these hyperlinks, but there were no technical controls' to stop them. Documents with nonpublic personal information were supposed to be tagged by hand, and the SEC records a 2018 internal analysis that the repository potentially held 'tens of millions' of such images without the tag. NYDFS found the company's risk assessment had classified EaglePro as an application that did not contain NPI.

The fix for an IDOR is an authorization check on every request: does this caller have the right to this object? Random, unguessable IDs and links that expire make enumeration harder and shorten exposure, but a link that leaks or is forwarded still works; they are defense in depth, not the control. The object authorization guide (/domains/appsec/authorization/object-level-authorization/) covers the check, and the missing-authorization fix cards (/fix/missing-authorization/) show it in ten stacks.

Found in January, lost in triage

Between December 2018 and January 2019 First American's information security staff ran a manual penetration test of EaglePro. The final report of 11 January 2019 rated the finding 'serious', level 3, and described it exactly: 'Access to PDF's and OrderDetails without authentication.' It said no nonpublic personal information was found in the documents reviewed, but 'It is unknown if any additional documents expose [sic] contained NPI. This requires further investigation by the application owner.' NYDFS found that no such investigation, and no review by senior management, took place before 24 May.

Under the company's vulnerability remediation policy, level 3 meant 'medium risk' and a 45-day fix. 'Due to a clerical error the vulnerability was erroneously input as a level 2 or low risk severity' in the automated tracking system, which then set a 90-day deadline of 8 May 2019. The policy said an owner who could not fix in time must go to information security, and if a fix was not possible, obtain 'a waiver or risk acceptance approval from the CISO.' The SEC records that the owner 'did not request a waiver or risk acceptance'. The deadline passed silently.

Every step here is a governance control that existed on paper. The report was right; the severity was copied wrongly; the tracker trusted what it was given; and a missed deadline triggered nothing. Compare the tracker against the source report, compute deadlines from the report's severity, and make a missed deadline either a fix or a recorded risk acceptance by a named person. The risk exceptions guide (/frameworks/risk-exceptions-and-metrics/) and the SLA guide (/frameworks/appsec-metrics-and-slas/) cover both.

What the people making statements did not know

On 24 May First American told the journalist it had 'learned of a design defect in an application that made possible unauthorized access to customer data'. On 28 May it furnished a Form 8-K whose press release reported '[n]o preliminary indication of large-scale unauthorized access to customer information.' The SEC found that the CISO learned of the January report on 24 May and the CIO on 25 May, and that both met the CEO and CFO repeatedly over those days, yet 'the company's senior executives responsible for the disclosures were not made aware' of the report or the missed remediation before either statement.

The SEC also found that as of 24 May 2019 First American 'did not have any disclosure controls and procedures related to cybersecurity'. It found a violation of Exchange Act Rule 13a-15(a), ordered the company to cease and desist, and imposed a $487,616 penalty; First American consented without admitting or denying the findings. For engineering teams the practical point is simple: when an incident touches a system, the people writing the public statement need the history of known findings about that system, and someone has to be responsible for handing it to them.

A regression test for object-level authorization

This test uses only Node built-ins (node:http, node:test and fetch) and passed on Node 26. The handler looks the document up, then checks that the caller owns it, and answers 404 both for 'not yours' and 'does not exist', so neighbouring IDs reveal nothing. The test asks for the next sequential ID with a valid session and expects 404, and asks with no session and expects 401. Put a test like it on every route that takes an object ID.

Testdocuments.test.mjsJavaScript
// Regression test: a document link must not open someone else's document.
// Run with: node --test documents.test.mjs (Node 22 or later, no dependencies)
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {createServer} from 'node:http';

const documents = new Map([
  [1001, {owner: 'alice', body: 'Alice closing disclosure'}],
  [1002, {owner: 'bob', body: 'Bob wire instructions'}],
]);
// Stand-in for real authentication: the session cookie names the user.
const userOf = req => /(?:^|;\s*)session=(\w+)/.exec(req.headers.cookie ?? '')?.[1];

function handler(req, res) {
  const match = /^\/documents\/(\d+)$/.exec(new URL(req.url, 'http://x').pathname);
  const doc = match && documents.get(Number(match[1]));
  const user = userOf(req);
  if (!user) return res.writeHead(401).end();
  // Same answer for "not yours" and "does not exist", so IDs cannot be probed.
  if (!doc || doc.owner !== user) return res.writeHead(404).end();
  res.writeHead(200, {'content-type': 'text/plain'}).end(doc.body);
}

async function get(server, path, user) {
  const {port} = server.address();
  const headers = user ? {cookie: `session=${user}`} : {};
  return fetch(`http://127.0.0.1:${port}${path}`, {headers});
}

test('object-level authorization on document links', async t => {
  const server = createServer(handler).listen(0, '127.0.0.1');
  await new Promise(resolve => server.once('listening', resolve));
  t.after(() => server.close());

  assert.equal((await get(server, '/documents/1001', 'alice')).status, 200);
  // The next sequential ID belongs to another customer.
  assert.equal((await get(server, '/documents/1002', 'alice')).status, 404);
  assert.equal((await get(server, '/documents/1003', 'alice')).status, 404);
  // No session, no document, even with a valid ID.
  assert.equal((await get(server, '/documents/1001')).status, 401);
});

Catch a finding that lost severity or missed its deadline

The script reads an export of findings with the severity from the source report and the severity in the tracker. It flags any finding whose tracker severity is lower than the report's, and any open finding past the deadline computed from the report's severity that has no recorded risk acceptance. Run on the sample export as of 24 May 2019, it reports the EaglePro-like finding twice: wrong severity, and 88 days past due. The SLA table is an example; use your own policy's numbers.

Testsla_check.pyPython
"""Cross-check findings between the test report and the tracker.

Input: a CSV export with id, report_severity, tracker_severity, reported
(date the report was issued), status, and risk_acceptance (approver or empty).
The deadline comes from the REPORT's severity, not the tracker's, so a
mistyped severity cannot buy extra time.
"""
import csv
import sys
from datetime import date, timedelta

SLA_DAYS = {"critical": 15, "high": 30, "medium": 45, "low": 90}  # set from your policy
RANK = {name: i for i, name in enumerate(["low", "medium", "high", "critical"])}


def review(rows, today):
    for row in rows:
        report, tracker = row["report_severity"], row["tracker_severity"]
        if RANK[tracker] < RANK[report]:
            yield row["id"], f"tracker says {tracker}, report says {report}"
        due = date.fromisoformat(row["reported"]) + timedelta(days=SLA_DAYS[report])
        if row["status"] != "fixed" and today > due and not row["risk_acceptance"]:
            yield row["id"], f"open {(today - due).days} days past {due} with no risk acceptance"


if __name__ == "__main__":
    with open(sys.argv[1], newline="", encoding="utf-8") as f:
        rows = list(csv.DictReader(f))
    today = date.fromisoformat(sys.argv[2]) if len(sys.argv) > 2 else date.today()
    issues = list(review(rows, today))
    for finding, problem in issues:
        print(f"{finding}: {problem}")
    sys.exit(1 if issues else 0)

Which controls would have stopped or limited it?

The SSDF column names NIST SP 800-218 version 1.1 practices and tasks; the ASVS column names ASVS 5.0 requirements.

Which controls would have stopped or limited it?
ControlSSDF practice or standardWould it have helped?Guide
Check on every request that the caller may access the specific object; same response for missing and forbiddenSSDF PW.5.1; ASVS V8.2.2, V8.3.1Yes: this is the fix for the flaw itself./domains/appsec/authorization/object-level-authorization/
Tests that request another user's object and expect a refusal, in CI for every object routeSSDF PW.8.2Yes: it would have failed when the 2014 link feature was built./fix/missing-authorization/
Shared links that expire, can be revoked and carry random identifiersSSDF PW.1.1 (design for the threat)Limits exposure; not a substitute for authorization./domains/appsec/authorization/function-property-authorization/
Deadlines computed from the source report's severity, with tracker and report reconciledSSDF RV.2.1, PO.4.1Yes: the 45-day deadline would have fallen in the first quarter of 2019, months before the journalist's report./frameworks/appsec-metrics-and-slas/
A missed deadline requires a fix or a risk acceptance by a named approverSSDF RV.2.2, PO.2.1Yes: it would have put the finding in front of the CISO months earlier./frameworks/risk-exceptions-and-metrics/
Scope the data actually exposed when a test finds an access flawSSDF RV.2.1Yes: the report asked for this investigation; it did not happen./domains/appsec/vulnerability-response/vdp-bug-bounty-pentest/
Classify data by what a system can reach, not what it is meant to hold; minimize what shared links can reachSSDF PW.1.1Limits impact: EaglePro was classified as holding no NPI./domains/identity-crypto/privacy/data-minimization-retention/

Lessons that still apply

  • An object ID in a URL is a request, not a permission. Check the caller against the object on every request.
  • Unguessable IDs and expiring links are worth having, and are not authorization.
  • A finding is only as good as its record: reconcile the tracker against the source report and compute deadlines from the report.
  • A missed deadline must force a decision, fix or a named risk acceptance, not pass silently.
  • When an incident hits a system, the people speaking for the company need its history of known findings.

What the public evidence does not establish

Neither order establishes how many documents unauthorized people actually viewed or downloaded before 24 May 2019; First American's May 28 press release spoke of '[n]o preliminary indication of large-scale unauthorized access', and NYDFS describes 'potential unauthorized access to NPI'. The document counts differ: the SEC describes about 800 million images in the repository, while the 885 million figure comes from the journalist's article as NYDFS recounts it. The SEC settled without First American admitting or denying its findings, and the NYDFS order records the department's findings in a negotiated consent order. Neither order explains how the clerical error happened.

Guided practice

You are the application security lead. A penetration test of your document-sharing service just came back, and you are reviewing how its findings were handled. Decide what each fact means and what to do. Treat the facts as true.

Supplied scenario material
Plain text
T1: Report of 2026-07-01, finding F-12 rated High: GET /share/{id} returns any document for any valid share ID; share IDs are sequential integers; links never expire.
T2: The tracker shows F-12 as Medium, due in 45 days from 2026-07-03; status open; no risk acceptance.
T3: Policy: High 30 days, Medium 45 days; an owner who cannot meet a deadline must obtain risk acceptance from the CISO.
T4: The data catalog lists the service as 'no personal data', because users are told not to share personal documents.
T5: Today is 2026-08-20. Marketing is drafting a statement about a 'potential issue' after a customer reported seeing another customer's file.
  1. 1. Fix F-12 properly

    What change closes F-12, and what is only defense in depth?

    Expected result and reasoning

    Add an authorization check that the caller may access that share on every request, with a regression test for another user's ID. Random IDs, expiry and revocation reduce exposure but do not fix it.

  2. 2. Reconcile T1 and T2

    What went wrong between the report and the tracker, and what is the real deadline?

    Expected result and reasoning

    The severity was lowered from High to Medium without a recorded reason. The deadline is 30 days from the report, 2026-07-31; it has passed with no fix and no risk acceptance, which the policy forbids.

  3. 3. Challenge T4

    Is the 'no personal data' classification sound?

    Expected result and reasoning

    No. An instruction to users is not a control, and the service can reach whatever was uploaded. Reclassify it by what it can reach, scope which documents were exposed, and minimize what share links can reach.

  4. 4. Brief T5

    What must the people approving the statement know?

    Expected result and reasoning

    That the flaw was found by the July test, rated High, mis-entered as Medium, and is past due without risk acceptance, plus what is known and unknown about access. Security owns giving them this before the statement goes out.

Your deliverable: A short memo: the fix and its test, the corrected deadline and who must accept or fix, the data classification change, and the facts for the people approving the statement.

This is a fictional service modeled on the pattern in the First American orders. Real disclosure obligations depend on jurisdiction and are for your legal team; this exercise covers the engineering and governance facts they need.

Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.

No draft saved on this device yet.

Sources & further reading

The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.

  1. SEC: In the Matter of First American Financial Corporation, Release No. 34-92176 (order). 14 June 2021.
  2. NYDFS: Consent order, In the Matter of First American Title Insurance Company. November 2023.
  3. NYDFS press release: $1 million cybersecurity settlement with First American Title Insurance Company. 28 November 2023.
  4. KrebsOnSecurity (journalism that first reported the exposure): First American Financial Corp. leaked hundreds of millions of title insurance records. 24 May 2019.
  5. OWASP ASVS 5.0.0: V8 Authorization. Version 5.0.0.
  6. MITRE CWE-639: Authorization Bypass Through User-Controlled Key

Reference information

ASVS 5.0.0 requirements
v5.0.0-8.2.2 · v5.0.0-8.3.1

Download this guide as Markdown