A reference of real, publicly documented business-logic seam bugs — defects that live where money, authorization, and multi-tenant data intersect, and that pattern-matching scanners miss because every line of code is locally correct. The bug is in what the composition permits.
This is the bug class no public benchmark covers. Each entry catalogs the pattern an incident revealed — the reusable shape you can check your own code against — grounded in a public source that documents what actually happened. No hypotheticals: every case traces to a postmortem, court ruling, regulatory filing, vendor advisory, or disclosure by a named security researcher.
Method and honesty. Facts here were verified against primary sources. Where a first-party document exists, it is cited; where a case rests on secondary reporting or where the evidence is thinner than ideal, the entry says so in-line rather than implying more certainty than the record supports. Source tiers: Tier 1 = first-party postmortem / court ruling / regulatory filing / vendor documentation; Tier 2 = expert security research with technical detail; Tier 3 = general news establishing that an incident occurred.
The set skews toward access-control failures (seven of the thirteen). That reflects what gets publicly documented — authorization breaches carry disclosure obligations and draw press coverage — not a claim about which seam bugs are most common.
Treat each pattern as "questions to ask of your own code," not a checklist that guarantees coverage. These are the seams where careful judgment, not a linter, is what catches the bug.
| # | id | seam | category | impact | top source |
|---|---|---|---|---|---|
| 1 | default-release-unless-all-suppression-flags-set | payment UI: "hold" required an all-of-N flag combination; partial config released funds | money-path · soft-enforcement-drift | ~$893.9M mis-wired | T1 court opinion |
| 2 | dead-path-reactivated-by-flag-reuse | deploy/config: reused flag + residual dead code + partial rollout | money-path · dead-path-reactivation | $460M / ~45 min | T1 SEC order |
| 3 | idempotency-key-covers-only-one-of-two-charge-paths | retry boundary: idempotency on one charge path, not a second | money-path · retry-idempotency-gap | undisclosed (duplicate/triple charges) | T1 vendor docs + issue tracker |
| 4 | borrowed-collateral-double-counted-as-equity | position proceeds credited as free equity, feeding borrowing capacity | money-path · soft-enforcement-drift | $4k → $1M buying power (example) | T1 news (CNBC) |
| 5 | enumerable-document-ids | doc endpoint authorized possession of a link, not ownership | auth · IDOR | ~885M documents | T2 (Krebs) |
| 6 | authenticated-but-unauthorized-api | API checked "logged in," never "allowed to see this record" | auth · broken-access-control | ~60M users | T2 (Krebs) |
| 7 | access-control-silently-broken-by-later-change | a working access control was disabled by an unrelated change and the regression went undetected | auth · regression | ~9.5M customers | T1 (OAIC) + mechanism via T3 |
| 8 | unauthenticated-api-ignores-privacy-flag | API served data without honoring the record's own privacy setting | auth · privacy-control-bypass | private-profile data exposed | T2/3 (TechCrunch) |
| 9 | underprotected-lookup-with-public-inputs | sensitive lookup trusted public/guessable inputs as authorization | auth · identity-verification-failure | credit scores, tens of millions | T2 (Krebs) |
| 10 | sequential-ids-plus-absent-rate-limit-enables-full-scrape | predictable object IDs + no ownership check + no rate limit | auth · missing-anti-automation | ~whole platform archived | T2 (Salt Security) |
| 11 | public-by-default-plus-open-api-enables-bulk-scrape | public-default data exposed through an open, weakly-throttled API | auth · insecure-default | ~207M+ transactions scraped | T3 (TechCrunch) |
| 12 | shared-connection-cross-user-cache-bleed | canceled request corrupts a pooled connection; next user reads its data | multi-tenant · race | chat titles + ~1.2% Plus billing info | T1 postmortem |
| 13 | shared-cache-cross-user-bleed | authenticated per-user responses cached under a non-user-scoped key | multi-tenant | ~34,000 users | T1 first-party |
- Seam. The payment-system UI boundary: to suppress an outbound wire and route funds to an internal "wash" account, the operator had to set three fields — FRONT, FUND, and PRINCIPAL — to the wash account. Setting only PRINCIPAL left the default behavior in force: release the full amount as live wires.
- Category. money-path · soft-enforcement-drift
- Incident. On August 11, 2020, Citibank — administrative agent on Revlon's ~$1.8 billion 2016 syndicated term loan — intended to wire only ~$7.8 million in monthly interest but instead wired $893,944,008.52, the full outstanding principal, to 315 lenders. The transaction ran through Flexcube, Citibank's loan-processing application, which releases any payment as an outbound wire unless the operator suppresses the default; suppressing a principal payment required setting all three of the FRONT, FUND, and PRINCIPAL fields to a "wash account." Contrary to the manual, the operator (and the two employees who reviewed the transaction) believed setting only PRINCIPAL would suffice.
- Pattern. An action defaults to the irreversible, high-value outcome unless the operator sets a non-obvious combination of flags. Partial configuration silently produces the dangerous default instead of erroring or warning.
- Why scanners miss it. Every checkbox behaved exactly as specified. The defect is that the UI's contract for "hold these funds" diverged from the mental model of the operator and the two people who reviewed and approved the transaction — a human-factors/composition failure, not a code fault. A confirmation warning (a "stop sign") did fire, but it showed neither the amount nor whether the payment was the intended interest or the full principal — a guard that existed yet could not distinguish the safe case from the catastrophic one.
- Impact. $893,944,008.52 wired in error; a substantial portion was not voluntarily returned, triggering litigation. (The distinct figure of $558,558,375.74 that appears in the case is the principal and interest owed specifically to the lenders who declined to return the funds — not the total wired.)
- Sources.
- Tier 1 — In re Citibank August 11, 2020 Wire Transfers, Findings of Fact and Conclusions of Law, No. 20-CV-6539 (JMF) (Furman, J., S.D.N.Y. Feb. 16, 2021):
https://www.nysd.uscourts.gov/sites/default/files/2021-02/20cv6539%20Citibank%20Opinion.pdf. Confirms the $893,944,008.52 payout to 315 lenders, the intended ~$7.8M interest, that "any payment entered into the system is released as a wire payment unless the maker suppresses the default option," the requirement that "ALL of the below field[s] must be set to the wash account: FRONT[;] FUND[; and] PRINCIPAL," that the operators set only PRINCIPAL contrary to that instruction, the six-eye maker/checker/approver review sharing the same misunderstanding, and the "stop sign" warning ("Account used is Wire Account and Funds will be sent out of the bank. Do you want to continue?") that did not indicate the amount or distinguish interest from principal. - Legal-outcome note: the district court ruled the lenders could keep the funds; the Second Circuit reversed (In re Citibank August 11, 2020, 2d Cir., Sept 8, 2022). The operational-bug facts cited above are established and undisputed in the district opinion; the catalog relies on those facts, not on the contested legal question of who keeps the money.
- Tier 1 — In re Citibank August 11, 2020 Wire Transfers, Findings of Fact and Conclusions of Law, No. 20-CV-6539 (JMF) (Furman, J., S.D.N.Y. Feb. 16, 2021):
- Questions it raises.
- When your system performs an irreversible action, is the safe outcome the default, or does safety require correct configuration?
- Does "suppress" / "hold" require setting several fields — and what happens if the operator sets only some of them?
- Does the UI's contract for a field match what the operator believes that field does?
- Seam. The deploy/config boundary: a flag that had formerly activated retired code was repurposed to activate new code, while the old code still existed on the fleet and an incomplete rollout left one server running it.
- Category. money-path · dead-path-reactivation
- Incident. On August 1, 2012, Knight Capital deployed new code for the NYSE Retail Liquidity Program (RLP) to its SMARS order router. The new code repurposed a flag that had previously activated discontinued "Power Peg" functionality — but a technician copied the new code to only seven of eight servers, and the retired Power Peg code was never removed. On the eighth server, setting the flag re-activated the dormant Power Peg logic. SMARS sent millions of unintended orders — 4 million executions in 154 stocks in ~45 minutes.
- Pattern. A flag, field, or identifier is repurposed for a new meaning while old code that still branches on its previous meaning remains present somewhere in the fleet. An incomplete rollout leaves a node where the flag resurrects the abandoned behavior.
- Why scanners miss it. Each server's code is internally valid. The defect lives in distributed state — (repurposed flag) × (residual dead code) × (partial deployment) — which no static analysis of a single codebase can see.
- Impact. A $460 million realized loss in approximately 45 minutes; the firm's near-collapse.
- Sources.
- Tier 1 — SEC Administrative Order, Release No. 34-70694, In re Knight Capital Americas LLC (Oct 16, 2013):
https://www.sec.gov/litigation/admin/2013/34-70694.pdf(canonical sec.gov URL; blocked automated fetch during assembly — HTTP 403 — verified via Internet Archive snapshot:https://web.archive.org/web/20131029192835id_/http://www.sec.gov/litigation/admin/2013/34-70694.pdf). Confirms the date, the $460M loss, "the new RLP code also repurposed a flag that was formerly used to activate the Power Peg code," the deployment to seven of eight servers, and the un-removed dormant code on the eighth.
- Tier 1 — SEC Administrative Order, Release No. 34-70694, In re Knight Capital Americas LLC (Oct 16, 2013):
- Questions it raises.
- When you repurpose a flag/field/constant, does any older code still branch on its previous meaning?
- Does your deploy guarantee every node runs the same code — and what executes on the one node that didn't get the update?
- Is retired code actually deleted, or merely unreachable until some flag flips?
- Seam. The client↔payment-provider retry boundary: an idempotency key protected one charge code path but not a second path that could also create a charge, so retries through the unprotected path were treated as fresh charges.
- Category. money-path · retry-idempotency-gap
- Incident. In the WooCommerce Stripe Gateway plugin (GitHub issue #2339), the idempotency key was applied to the legacy
/chargesAPI call but not to/payment_intentsrequests. Users reported being charged two or three times for a single order or renewal. - Pattern. Idempotency protection covers one money-moving code path but not a second one; a retry (network timeout, webhook re-delivery, scheduled-job re-fire) through the unprotected path double-charges.
- Why scanners miss it. Both code paths are correct in isolation. A linter cannot know that two different endpoints represent the same money-moving operation that must share one idempotency key — it's a business-invariant gap, not a syntactic fault.
- Impact. Undisclosed in aggregate; individual duplicate and triple charges reported.
- Evidence basis (honest caveat). Stripe's own documentation establishes the general mechanism — idempotency keys let a retried request "safely repeat … without risk of creating a second object," and their absence lets a retried create-charge create a duplicate. The specific WooCommerce incident (key on
/chargesbut not/payment_intents) rests on aggregated user bug reports plus a code-gap observation in the issue thread, not a single confirmed root-cause trace; reporters note the double-charges occurred "sporadically." The pattern itself, however, is confirmed by first-party vendor documentation. - Sources.
- Tier 1 (mechanism) — Stripe idempotency docs:
https://docs.stripe.com/api/idempotent_requests("if a connection error occurs, you can safely repeat the request without risk of creating a second object") andhttps://docs.stripe.com/error-low-level. - Tier 1 (the specific gap, via user reports) — WooCommerce Stripe Gateway issue #2339:
https://github.com/woocommerce/woocommerce-gateway-stripe/issues/2339. Documents the key applied only to/charges, and a user report of a customer "charged 3 times for a subscription renewal."
- Tier 1 (mechanism) — Stripe idempotency docs:
- Questions it raises.
- Does every code path that can move money share the same idempotency key, or only some of them?
- If a request is retried, will the second attempt be recognized as the same operation — on all paths?
- Have you tested the retry path, not just the happy path?
- Seam. The boundary between a position's proceeds and the equity/buying-power calculation: proceeds were credited as free equity without netting the liability that funded them, so borrowing increased the capacity to borrow.
- Category. money-path · soft-enforcement-drift
- Incident. Around November 5, 2019, some Robinhood users obtained leverage far beyond limits — dubbed "infinite leverage." One trader took a $1,000,000 position on a $4,000 deposit; another turned $2,000 of stock into $50,000 of buying power. Per CNBC, the glitch "allowed users to overstate the amount of money users had in their accounts to borrow against."
- Pattern. Proceeds of a leveraged position are credited as new equity/buying power without deducting the offsetting liability, so the output of a borrowing action increases the input available to the next one — an unbounded feedback loop.
- Why scanners miss it. Each ledger operation is individually correct. The invariant "borrowed proceeds are not free equity" spans two subsystems and is a domain rule, not a code smell any single function violates.
- Impact. Aggregate undisclosed; documented individual examples ($4,000 → $1,000,000 buying power; $2,000 → $50,000). Robinhood patched the issue.
- Evidence basis (honest caveat). CNBC confirms the incident, the dollar examples, and the high-level mechanism ("overstate the amount … to borrow against"). The finer causal detail widely reported elsewhere — that selling covered calls funded with margin credited the option premium as equity — is from trade press (Finance Magnates, The Verge), not confirmed by the CNBC source. The card states the pattern at the level CNBC supports.
- Sources.
- Tier 1 / major news — CNBC, "Some Robinhood users were able to trade with unlimited borrowed money" (Nov 5, 2019):
https://www.cnbc.com/2019/11/05/some-robinhood-users-were-able-to-trade-with-unlimited-borrowed-money.html(canonical cnbc.com URL; blocked automated fetch during assembly — HTTP 403 — verified via Internet Archive snapshot:https://web.archive.org/web/20191105213416id_/https://www.cnbc.com/2019/11/05/some-robinhood-users-were-able-to-trade-with-unlimited-borrowed-money.html). CNBC credits Bloomberg as first to report.
- Tier 1 / major news — CNBC, "Some Robinhood users were able to trade with unlimited borrowed money" (Nov 5, 2019):
- Questions it raises.
- When a position generates proceeds, does your system add them to buying power before netting the liability that funded them?
- Can the output of a borrowing action increase the input to the next borrowing action?
- Is there a global leverage invariant, enforced across every path that credits equity?
- Seam. A document-view endpoint authorized possession of a link, never whether the requester owned the document — object access with no per-object authorization.
- Category. auth · IDOR
- Incident. Disclosed May 24, 2019: First American Financial exposed roughly 885 million title-insurance and escrow records (Social Security numbers, bank-account numbers, wire receipts, driver's-license images, dating to 2003) through a website design defect. The document URLs used sequential record numbers; changing the number returned someone else's document, with no authentication required.
- Pattern. A sequential or enumerable record ID in the URL is treated as a de-facto credential. Increment or decrement the number to walk every other user's record; no login required.
- Why scanners miss it. Each request is a well-formed fetch of a record that genuinely exists. There is no missing null-check, no injection — the flaw is the absent ownership check across the composition.
- Impact. ~885 million documents exposed.
- Sources.
- Tier 2 — KrebsOnSecurity, "First American Financial Corp. Leaked Hundreds of Millions of Title Insurance Records" (May 2019):
https://krebsonsecurity.com/2019/05/first-american-financial-corp-leaked-hundreds-of-millions-of-title-insurance-records/. Confirms the 885M figure, the sequential nine-digit record numbers, that "no authentication was required to read the documents," and that modifying the number yielded other people's records. (Krebs is the primary disclosure for this incident.)
- Tier 2 — KrebsOnSecurity, "First American Financial Corp. Leaked Hundreds of Millions of Title Insurance Records" (May 2019):
- Questions it raises.
- Does your app check that the requester owns the object, or only that the ID is valid?
- Are record IDs guessable or sequential — and if so, what prevents enumeration?
- Is authorization enforced at the data layer, or merely assumed from the UI?
- Seam. The API authenticated the caller (they were logged in) but performed no authorization scoping the caller to their own records — authentication mistaken for authorization.
- Category. auth · broken-access-control
- Incident. Reported November 2018: the USPS "Informed Visibility" API let any logged-in usps.com user query account details — email, address, phone, account number — belonging to any of roughly 60 million other users, and in some cases modify them. The only access control was that the caller was logged in at all.
- Pattern. "Logged in" is treated as sufficient. Any authenticated user can read (or write) any other user's record by supplying its identifier or a wildcard search — the relationship check (caller == subject) is absent.
- Why scanners miss it. The endpoint correctly requires and validates a session. The missing control is a relationship between caller and record that no signature-based scan can infer.
- Impact. Account data on ~60 million users exposed.
- Sources.
- Tier 2 — KrebsOnSecurity, "USPS Site Exposed Data on 60 Million Users" (Nov 2018):
https://krebsonsecurity.com/2018/11/usps-site-exposed-data-on-60-million-users/. Confirms the API name, the ~60M scale, and that any logged-in user could query others' account details. (Primary disclosure.)
- Tier 2 — KrebsOnSecurity, "USPS Site Exposed Data on 60 Million Users" (Nov 2018):
- Questions it raises.
- Does your endpoint gate on "logged in," or on "logged in and allowed to see this specific record"?
- Can a query parameter (a user ID, a wildcard) return records belonging to someone else?
- Is the ownership check present on the API path, or only in the web UI?
- Seam. An internet-facing API whose access control existed but was rendered ineffective by an unrelated later code change. The regression went undetected, and a partial fix left one domain still exposed — a boundary that once held, quietly stopped holding.
- Category. auth · regression
- Incident. Between September 17–20, 2022, an attacker retrieved records for ~9.5 million current and former Optus customers — including over 3.1 million physical addresses and over 2.4 million identity documents (passport, driver's-licence, Medicare numbers). According to the ACMA's Federal Court filing (reported by outlets quoting the pleading), a September 2018 coding error broke one of the access controls protecting an internet-facing API; Optus remediated it in 2021 for only one of two domains, leaving the other exposed. The filing characterizes the extraction as "a simple process of trial and error."
- Pattern. A working authorization control is silently disabled by an unrelated change; no test or monitor catches the regression, so an endpoint that was once protected becomes reachable — sometimes for years, and sometimes past a partial remediation that only covered where the problem was first noticed.
- Why scanners miss it. At any single point in time the code "looks" intentional — there is no obviously missing check to flag. The defect is that a control which once worked no longer does, a property visible only by comparing intent across time or by testing the boundary, not by reading one revision.
- Impact. ~9.5 million customers' records (3.1M+ addresses, 2.4M+ identity documents); the endpoint was pulled offline September 21, 2022; ACMA Federal Court action followed.
- Evidence basis (honest caveat). The two layers of this case have different sourcing. The shell facts — ~9.5 million affected, the September 2022 breach, and formal regulator action for failure to protect personal information — are confirmed by fetched regulator primaries (OAIC and ACMA, below). The technical mechanism — the 2018 coding error, both domains internet-facing, the 2021 fix applied to only one domain, and "trial and error" extraction — appears only in the ACMA Federal Court concise statement (a filed pleading that is not publicly downloadable) and is available solely through secondary outlets that quote it; treat it as an allegation reported via secondaries, not a fetched regulator primary. Two precision points from that filing matter: (1) the access control was weakened / rendered ineffective by the 2018 change, not a from-scratch unauthenticated endpoint — the pleading deliberately distinguishes this from earlier reporting that called it simply "unauthenticated"; (2) extraction was by "trial and error," and no source establishes that the identifiers were strictly sequential — so this card does not claim sequential IDs.
- Sources.
- Tier 1 (regulator primary — scale + action, fetched live) — OAIC, "Australian Information Commissioner takes civil penalty action against Optus" (Aug 8, 2025):
https://www.oaic.gov.au/news/media-centre/australian-information-commissioner-takes-civil-penalty-action-against-optus. Confirms ~9.5 million Australians affected, that the breach was made public September 22, 2022, and the civil-penalty action for failing to take reasonable steps to protect personal information. - Tier 1 (regulator primary — proceedings + breach dates) — ACMA, "ACMA statement on 2022 Optus data breach" (updated May 22, 2024):
https://www.acma.gov.au/acma-statement-2022-optus-data-breach(acma.gov.au blocked automated fetch during assembly — WAF reset — verified via Internet Archive snapshot:https://web.archive.org/web/20251210060252/https://www.acma.gov.au/acma-statement-2022-optus-data-breach). Confirms ACMA filed Federal Court proceedings and the September 17–20, 2022 breach window; it does not describe the coding-error mechanism. - Tier 3 (the mechanism, quoting the ACMA Federal Court pleading) — The Register, "Optus data breach caused by faulty API" (June 21, 2024):
https://www.theregister.com/2024/06/21/optus_data_breach_faulty_api/, and CSO Online, "Optus breach occurred due to a coding error, alleges ACMA":https://www.csoonline.com/article/2492520/optus-breach-occurred-due-to-a-coding-error-alleges-acma.html. Both quote the filing on the 2018 access-control coding error, the two domains, the partial 2021 fix, and 9.5M records — the source for the mechanism the regulator primaries do not themselves publish.
- Tier 1 (regulator primary — scale + action, fetched live) — OAIC, "Australian Information Commissioner takes civil penalty action against Optus" (Aug 8, 2025):
- Questions it raises.
- When an unrelated change touches shared code, could it silently disable an authorization control elsewhere?
- Do you have a test or monitor that would fail if a protected endpoint became reachable — or would that regression pass unnoticed?
- When you fix such a regression, are you sure you covered every domain and path it affected, not just the one you first saw?
- Seam. The API served profile data without verifying the requester was permitted to see it, and did not honor the record's own "private profile" setting — read authorization absent at the API layer.
- Category. auth · privacy-control-bypass
- Incident. Disclosed May 5, 2021 (reported to Peloton in January): a Peloton API returned private account data — age, gender, city, weight, workout statistics, birthday — for users whose profiles were set to private. The requests initially required no authentication at all; even after a partial fix, any member could query any other member.
- Pattern. Unauthenticated (or under-authorized) requests to a user-data endpoint that never checks whether the caller may read that user, bypassing the profile-privacy setting entirely.
- Why scanners miss it. The privacy flag is enforced correctly in the UI layer. The API is a separate composition where the same check simply isn't wired — no local code smell in either place.
- Impact. Private-profile data of Peloton members (the service had over three million subscribers at the time) was queryable; the exact number of records accessed is undisclosed.
- Sources.
- Tier 2/3 — TechCrunch, "Peloton bug exposed users' private account data" (May 5, 2021):
https://techcrunch.com/2021/05/05/peloton-bug-account-data-leak/, reporting research by Jan Masters / Pen Test Partners. Confirms unauthenticated API requests returned private-profile data.
- Tier 2/3 — TechCrunch, "Peloton bug exposed users' private account data" (May 5, 2021):
- Questions it raises.
- Does your API honor the same privacy settings your UI does, or does the flag live only in the front end?
- Does a record marked "private" actually refuse unauthorized reads at the data layer?
- Is every user-data endpoint checking caller permission, or do some assume the UI already filtered?
- Seam. A lookup endpoint trusted low-entropy, publicly available inputs as sufficient proof of authorization — no strong identity or consent check between the input and a high-sensitivity action.
- Category. auth · identity-verification-failure
- Incident. Disclosed April 28, 2021: a partner-lender-facing Experian API returned FICO credit scores (plus up to four "risk factors") given a person's name, address, and date of birth. The date-of-birth check could be bypassed by submitting all zeros. The exposure covered tens of millions of Americans.
- Pattern. A sensitive record is returned in exchange for public or guessable inputs treated as a credential — automatable and enumerable across an entire population.
- Why scanners miss it. Each field is validated and each call is well-formed. The defect is that the composition of public inputs is accepted as authorization for a high-sensitivity action — a policy gap, not a code fault.
- Impact. Credit scores of "most Americans" (KrebsOnSecurity) / tens of millions; exact count undisclosed.
- Sources.
- Tier 2 — KrebsOnSecurity, "Experian API Exposed Credit Scores of Most Americans" (April 2021):
https://krebsonsecurity.com/2021/04/experian-api-exposed-credit-scores-of-most-americans/. Confirms the name+address+DOB lookup, the all-zeros DOB bypass, and the partner-API design. (Primary disclosure.)
- Tier 2 — KrebsOnSecurity, "Experian API Exposed Credit Scores of Most Americans" (April 2021):
- Questions it raises.
- What does your endpoint treat as proof the caller is entitled to this data — and is that proof public or guessable?
- Can a verification step (DOB, PIN) be bypassed with trivial input such as zeros?
- Would this lookup still be safe if an attacker knew only what's printed on someone's mail?
- Seam. Content objects were addressable by guessable sequential IDs, some endpoints required no authentication, and no rate limiting throttled bulk iteration — an authorization boundary and an anti-automation boundary, both absent at once.
- Category. auth · missing-anti-automation
- Incident. In January 2021, archivists downloaded a very large volume of Parler posts, videos, and images (some carrying GPS metadata) by iterating over predictable post identifiers, ahead of the platform's removal from AWS.
- Pattern. Predictable object IDs + no per-object ownership check + no rate limit = anyone can walk the entire dataset by counting.
- Why scanners miss it. Each fetch-by-ID is a legitimate, correct API call. The vulnerability is the enumerable ID space combined with the absent throttle across the composition, not any single request.
- Impact. Effectively the platform's public content archived at scale (reported in the tens of terabytes).
- In-card note. Not the entire API was unauthenticated — the confirmed mechanism is predictable IDs plus no rate limiting, with authentication missing on at least some endpoints. Framed as "enumerable IDs + no anti-automation," not "no auth anywhere."
- Sources.
- Tier 2 — Salt Security, "Unpacking the Parler Data Breach":
https://salt.security/blog/unpacking-the-parler-data-breach. Confirms that archivists iterated over predictable IDs to harvest content, that rate limits were not in place, and that at least one media endpoint required no authentication.
- Tier 2 — Salt Security, "Unpacking the Parler Data Breach":
- Questions it raises.
- Are your object IDs sequential or predictable — and is there a per-object authorization check regardless?
- Is there rate limiting or anti-automation on endpoints that can be enumerated?
- If someone simply counted from zero, how much of your data could they retrieve?
- Seam. A business-logic default (data public unless the user changes it) combined with an unauthenticated API and permissive rate limits — a privacy-default boundary and an access-control boundary compounding.
- Category. auth · insecure-default
- Incident. In 2018–2019, Venmo's public-by-default transactions were scraped at scale through its public/developer API: one researcher downloaded ~207 million transactions (2018), another ~7 million more (2018–2019). Each record exposed sender and receiver names, a timestamp, and the memo.
- Pattern. A "public by default" data setting, exposed through an unauthenticated and weakly-throttled API, turns an opt-out privacy choice into a bulk-enumerable public dataset.
- Why scanners miss it. Nothing is "broken" — the API and the default both behave as designed. The defect is a product/business-logic decision (public default) that composition-level scraping weaponizes.
- Impact. ~207 million + ~7 million transactions scraped and republished as research datasets.
- In-card note. The driver here is public-default + open API + weak rate limiting, not sequential-ID enumeration specifically.
- Sources.
- Tier 3 — TechCrunch, "Millions of Venmo transactions scraped in warning over privacy settings" (June 16, 2019):
https://techcrunch.com/2019/06/16/millions-venmo-transactions-scraped/. Confirms transactions were public by default, the developer API was reachable without user permission, and rate limits were permissive.
- Tier 3 — TechCrunch, "Millions of Venmo transactions scraped in warning over privacy settings" (June 16, 2019):
- Questions it raises.
- What is the default privacy state of user-generated data — and did the user actively choose it?
- Does an open or unauthenticated API expose that data in bulk regardless of per-record intent?
- Do your rate limits assume human use — and what happens at machine speed?
- Seam. The boundary between a per-request connection and a shared connection pool: a request canceled at the wrong moment corrupted a pooled connection, so the next unrelated request received data left behind by the previous user.
- Category. multi-tenant · race (TOCTOU)
- Incident. On March 20, 2023, a bug in the
redis-pyAsyncio client caused some ChatGPT users to see titles from other active users' chat histories. During a ~9-hour window, the same bug may have exposed payment-related information — first and last name, billing address, credit-card type, expiration date, and the last four digits — of ~1.2% of ChatGPT Plus subscribers active in that window. Full credit-card numbers were not exposed. - Pattern. A pooled or shared connection retains a prior request's response. If a request is canceled between being pushed onto the incoming queue and popped from the outgoing queue, the connection is corrupted and the next borrower reads the previous user's payload.
- Why scanners miss it. Each function — cache read, connection reuse, request cancellation — is locally correct. Only the race across the pool boundary permits the cross-user read, and it appears only under a spike of cancellations.
- Impact. Chat titles (and possibly the first message of a new conversation) visible to other users; partial payment info of ~1.2% of active Plus subscribers exposed in a ~9-hour window; full card numbers never exposed.
- Sources.
- Tier 1 — OpenAI postmortem, "March 20 ChatGPT outage: Here's what happened" (Mar 24, 2023):
https://openai.com/index/march-20-chatgpt-outage/(canonical openai.com URL; served a bot challenge to automated fetch during assembly — verified via Internet Archive snapshot:https://web.archive.org/web/20240503032019id_/https://openai.com/index/march-20-chatgpt-outage). Confirms the redis-py Asyncio bug, the enqueue/dequeue cancellation race, the titles-only chat leak, and the 1.2% / nine-hour / last-four-digits payment exposure.
- Tier 1 — OpenAI postmortem, "March 20 ChatGPT outage: Here's what happened" (Mar 24, 2023):
- Questions it raises.
- When you reuse pooled resources (connections, buffers), can a canceled or timed-out request leave state the next user reads?
- Are identity and payload bound together, or can a response be served on a connection that now serves someone else?
- What happens to your pool under a spike of request cancellations?
- Seam. The cache-key boundary: authenticated, per-user responses were cached and served under a key that did not include the user's identity, collapsing many users into one cache entry.
- Category. multi-tenant
- Incident. On December 25, 2015, during a denial-of-service attack that spiked Steam store traffic ~2000%, a second caching configuration incorrectly cached responses generated for authenticated users. Between roughly 11:50 and 13:20 PST, about 34,000 users' store/account pages were served to other users, exposing billing address, purchase history, the last two digits of a credit card, the last four of a Steam Guard phone number, and email.
- Pattern. A caching layer keyed for anonymous or shared content is applied to authenticated per-user responses; the cache then serves one user's personalized page to the next requester.
- Why scanners miss it. The page-rendering and caching layers are each correct in isolation. The defect is in the composition — a cache rule that fails to include identity in the key — introduced operationally under load.
- Impact. ~34,000 users' pages exposed over an ~80-minute window; no full card numbers or passwords.
- Sources.
- Tier 1 — Valve, "Update on Christmas Issues" (Steam news, Dec 2015):
https://store.steampowered.com/oldnews/19852. First-party confirmation of the incorrectly-cached authenticated-traffic mechanism, the ~34,000 figure, the time window, and the exposed fields.
- Tier 1 — Valve, "Update on Christmas Issues" (Steam news, Dec 2015):
- Questions it raises.
- Does your cache key include the identity of the user a response is personalized for?
- Can a caching rule meant for public content ever apply to authenticated responses?
- Under load or a config change, what stops one user's page from being served to another?
Some famous cross-tenant incidents are out of scope because they are not composition/logic seams — they are bugs a fuzzer or a memory-safety tool is built to catch.
- Cloudflare "Cloudbleed" (2017). A one-character error in a generated HTML
parser (
==where>=was needed) stepped a pointer past a buffer, leaking adjacent memory — including other customers' tokens and cookies — into responses. The cross-tenant outcome is real, but the root cause is a classic memory-safety over-read, and it was in fact found by fuzzing (Google Project Zero). This catalog is about locally-correct code whose composition is wrong; a locally-wrong single character that a fuzzer flags is a different class of bug, and including it would blur the boundary the catalog exists to draw.
The catalog only includes patterns for which a real incident could be confirmed against a public source. Some important seam patterns are missing not because they're rare but because there is not yet a first-party-confirmed public case:
- Migration / tenant-partition failure — a data migration or cutover re-maps identities to records, and a partitioning or session-binding defect lets one customer's session resolve to another's account. This pattern is widely believed to have occurred in the 2018 TSB core-banking migration, but on inspection the cross-account-visibility detail rested only on press reporting that framed it as under regulator assessment — the first-party regulatory findings available describe the outage and governance failures without confirming that specific mechanism. Rather than publish a case whose defining feature is unconfirmed, the slot is left open. A citable, first-party- confirmed instance of this pattern would be a welcome contribution.
Maintained by the author of SeamStress, an open-source tool for reviewing the kind of seam bugs this catalog documents.