← Back to list
● Based on the 2026-10-01 Shinhan Bank incident (25,000 customers' data leaked)

I reproduced the identity-verification bypass exactly,
and I'm publishing the code that stops it

I reproduced the exact pattern that broke through Shinhan Bank's loan-broker service — an IDOR vulnerability where identity verification is bypassed to look up other customers' records — and built the defense that binds every lookup to the caller's own session identity. Both are free right now.

See live demo → Jump to code

Who this is for

Not for real banking-system operators — this is a learning resource for backend developers who want to see, in code, exactly what IDOR (Insecure Direct Object Reference) means and why OWASP keeps ranking it near the top.

OWASP #1
BOLA(IDOR)'s rank on the OWASP API Security Top 10
25,000
Customers affected by the Shinhan Bank incident
ARTEX
Verified in an isolated sandbox against ARTEX, the real AI pentesting agent (github.com/Autumn-27/ARTEX)
$0
What this code costs right now — free

Code — vulnerable-lookup-api.js

A vulnerable loan-lookup API that trusts whatever customer ID the client sends.

vulnerable-lookup-api.js
// Vulnerable loan-lookup endpoint — modeled on the Shinhan Bank incident
// (2026-10-01): identity verification on a loan-broker service was bypassed,
// exposing ~25,000 customers' personal data. The flaw: the endpoint trusts
// whatever customer id the CLIENT sends, instead of the caller's session.
class VulnerableLoanLookup {
  constructor(customerDb) {
    this.db = customerDb; // { customerId: {name, rrn, ci, phone, income, loanLimit} }
  }

  // session is the caller's authenticated identity.
  // requestedId is a client-supplied parameter — e.g. ?customerId=CUST-1042.
  // BUG: requestedId is used directly, session is never checked.
  lookup(session, requestedId) {
    const record = this.db[requestedId];
    if (!record) return { status: 'not_found' };
    return { status: 'ok', record }; // leaks any customer's data to anyone logged in
  }
}

module.exports = { VulnerableLoanLookup };

Code — session-bound-guard.js

The fix: every lookup is bound to the caller's own session identity.

session-bound-guard.js
// The fix: every lookup is bound to the caller's own session identity.
// A request for any other customer's id is rejected and logged, regardless
// of what the client sends — this is what should have stopped the
// Shinhan Bank-style IDOR bypass.
class SessionBoundLoanLookup {
  constructor(customerDb) {
    this.db = customerDb;
    this.deniedAttempts = [];
  }

  lookup(session, requestedId) {
    if (!session || !session.customerId) {
      return { status: 'unauthenticated' };
    }
    if (requestedId !== session.customerId) {
      this.deniedAttempts.push({
        sessionOwner: session.customerId,
        attemptedId: requestedId,
        at: Date.now(),
      });
      return { status: 'denied', reason: 'session_id_mismatch' };
    }
    const record = this.db[requestedId];
    if (!record) return { status: 'not_found' };
    return { status: 'ok', record };
  }
}

module.exports = { SessionBoundLoanLookup };

Code — layer1-extensions.js

A summary of 2 extra layers actually running in a separate isolated sandbox verified against ARTEX — full implementation lives in proxy.js + isolation-forest.js.

layer1-extensions.js
// Extension layers — close 2 evasions that session-bound-guard.js alone can't
// (1) Cross-identity source correlation: defeats "get blocked, log in as someone else"
// (2) Isolation Forest anomaly model: catches zero-violation abnormal patterns too
// (e.g. a single identity hammering its OWN record at inhuman speed)

const ipViolatingOwners = new Map(); // ip -> Set<ownerCustomerId>
const ipBlocked = new Set();

function onAuthorizationViolation(owner, ip) {
  const owners = ipViolatingOwners.get(ip) || new Set();
  owners.add(owner);
  ipViolatingOwners.set(ip, owners);
  // once one source has violated under 2+ distinct identities, block the source itself
  if (owners.size >= 2) ipBlocked.add(ip);
}

function onEveryRequest(owner, ip, requestsPerMinute, distinctIdsAttempted) {
  if (ipBlocked.has(ip)) {
    return { status: 'blocked', reason: 'source_blocked_cross_identity_pattern' };
  }
  // scored even with zero violations — "an unusual shape" alone can trip this
  // (full model in isolation-forest.js — seeded PRNG, same score on every restart)
  const anomalyScore = anomalyModel.score([requestsPerMinute, distinctIdsAttempted]);
  if (anomalyScore >= 0.70) {
    // log-only by default — real blocking needs an operator to set ML_BLOCK=true
    logFlag({ owner, anomalyScore });
  }
  return { status: 'ok' };
}

Price

Free (for now)
A deeper version (rate limiting, anomaly detection, production deployment guide) ships later — pay-what-you-want, starting at $0.
goalsgo7574@gmail.com
This isn’t a purchase — just email me "notify me about the paid version" and I’ll reach out when it ships.
Note: The first two files are a scaled-down learning implementation. The cross-identity correlation and anomaly detection shown in the 3rd file (layer1-extensions.js) were verified against the real ARTEX agent in a separate isolated sandbox, but that full implementation (proxy.js, isolation-forest.js) isn't public yet — don't drop any of this into a real financial system as-is.

Leave feedback

Questions or feedback about this product? Leave your email if you'd like a reply (optional).