Skip to content
AI Strategy & ROI

Britain Chose To Regulate The Firms That Use AI, Not The Labs That Build It. Korea's AI-Assisted Bank Hacks Show Why That Was Right - And The Engineering UK Firms Now Owe

In September the government told the Lords that its Cyber Security and Resilience Bill would place duties on organisations that use technology rather than on AI vendors, because regulating vendors 'would not address the harms'. Critics called it letting the labs mark their own homework. Then Korea happened. Since late September attackers have breached seven South Korean banks and lenders and exposed data on about 68,000 people, with investigators suspecting ARTEX, an open-source multi-agent penetration-testing framework. No model regulation would have stopped it: the tool was freely available, and every entry point was a weakness in the banks' own secondary systems - a loan-recruiter lookup that could be enumerated for 30 hours, a staff mobile app and a sales tool with weak session checks. Korea's regulator gave firms until 8 October to audit every internet-facing system. We think the UK's approach is right, and that it puts the burden exactly where engineering can carry it. Here is that engineering: an exposed-system inventory, enumeration defences and server-side session validation, in code.

AlchmAI Engineering16 min read

7

South Korean banks and lenders breached since late September, with about 68,000 people's data exposed

30 hours

Attackers spent cycling customer numbers on Shinhan's loan-recruiter lookup service, starting 28 September

8 Oct

Deadline Korea's Financial Services Commission set for firms to audit and fix every internet-facing system

Users, not vendors

Where the UK's Cyber Security and Resilience Bill places duties - a choice ministers defended in the Lords in September

The UK's choice was explicit. During the Lords stages of the Cyber Security and Resilience Bill in September, cybersecurity minister Baroness Lloyd argued that regulating AI vendors would not address the harms some AI products can pose, and the government rejected amendments to require vendors to demonstrate guardrails or to give ministers emergency shutdown powers over AI systems. Its answer was the voluntary AI Cyber Security Code of Practice, the ETSI EN 304 223 standard it informed, and the AI Security Institute's cooperative testing - with binding duties falling on the organisations that run essential and digital services. Baroness Kidron's objection was memorable: the NHS must protect itself, but the AI attacking it has no requirement to check itself.

Korea has now supplied the test case. Since late September attackers have breached at least seven banks and lenders - Shinhan, KB Kookmin, Hana, BNK Busan, two savings banks and a consumer lender - exposing names, phone numbers, incomes, loan limits and in some cases resident registration numbers for about 68,000 people. Investigators suspect ARTEX, an open-source penetration-testing framework whose documentation describes 'an autonomous system driven by multiple AI agents' using commercial models; officials say it did not act without human involvement. On 6 October President Lee said AI now makes it possible 'to hack with ease even without specialized skills'. The entry points were a lookup service Shinhan built for outside loan recruiters, bypassed by cycling through customer numbers for 30 hours; weak session validation on KB Kookmin's employee mobile work system; and a similar gap in Hana's sales-support tool.

1. Know What You Expose

Korea's regulator started where every firm should: an inventory of internet-facing systems, including partner, staff and vendor-hosted ones. Most firms' asset registers miss the side doors, because they were stood up by a business line, a vendor or a project that ended. Certificate transparency logs are the cheapest outside-in check: every publicly trusted TLS certificate is logged, so every hostname you have put on the internet with HTTPS is discoverable - by you, and by an attacker's agent.

pythonasm/inventory_diff.py
import json, urllib.request, socket

def ct_hostnames(domain: str) -> set:
    """Hostnames seen in certificate transparency logs for a domain (outside-in view)."""
    url = "https://crt.sh/?q=%25." + domain + "&output=json"
    with urllib.request.urlopen(url, timeout=60) as r:
        rows = json.load(r)
    names = set()
    for row in rows:
        for n in row.get("name_value", "").splitlines():
            n = n.strip().lower().lstrip("*.")
            if n.endswith(domain):
                names.add(n)
    return names

def resolves(host: str) -> bool:
    try:
        socket.getaddrinfo(host, 443)
        return True
    except OSError:
        return False

def diff(domain: str, registered: set) -> dict:
    seen = {h for h in ct_hostnames(domain) if resolves(h)}
    return {
        "unregistered_live": sorted(seen - registered),   # exposed, not in the asset register: investigate today
        "registered_not_seen": sorted(registered - seen), # in the register but no live cert: stale or internal
    }

# Run weekly per domain. Every 'unregistered_live' host gets an owner, a purpose and a test - or is switched off.
# Typical finds: broker/introducer portals, campaign microsites, staff app APIs, pre-production stacks.

2. Make Enumeration Loud And Expensive

The Shinhan breach was enumeration: one identity guessing its way through customer numbers for 30 hours. Ordinary rate limits miss it because each request looks normal. The signal is the number of distinct identifiers a single principal touches - and on a partner portal, a recruiter has no legitimate reason to look up hundreds of different customers an hour.

pythonsecurity/enumeration_guard.py
import time
from collections import defaultdict, deque

class EnumerationGuard:
    """Tracks distinct object identifiers touched per principal in a sliding window."""
    def __init__(self, window_s=3600, max_distinct=50, max_failures=20, alert=print):
        self.window_s, self.max_distinct, self.max_failures, self.alert = window_s, max_distinct, max_failures, alert
        self.touched = defaultdict(deque)     # principal -> deque[(ts, object_id)]
        self.failures = defaultdict(deque)    # principal -> deque[ts] of not-found / verification failures

    def _trim(self, dq, now):
        while dq and now - (dq[0][0] if isinstance(dq[0], tuple) else dq[0]) > self.window_s:
            dq.popleft()

    def check(self, principal: str, object_id: str, found: bool, now: float = None) -> bool:
        """Return True to allow, False to block. Call on every lookup, before returning data."""
        now = now or time.time()
        t, f = self.touched[principal], self.failures[principal]
        t.append((now, object_id)); self._trim(t, now)
        if not found:
            f.append(now); self._trim(f, now)
        distinct = len({oid for _, oid in t})
        if distinct > self.max_distinct or len(f) > self.max_failures:
            self.alert({"principal": principal, "distinct": distinct, "failures": len(f), "action": "block_and_review"})
            return False
        return True

# Also: return identical responses and timings for 'not found' and 'not authorised', so guessing reveals nothing;
# scope partner lookups to cases the partner submitted, so there is nothing to enumerate in the first place.

3. Validate Sessions On The Server, Every Request

KB Kookmin's staff app and Hana's sales tool fell to insufficient session validation - the classic pattern where a client-side flag or a long-lived token is trusted after login. Every request to every internal or partner app should be authorised server-side against a short-lived session bound to the user and device, with the object being requested checked against what that user may see.

typescriptsecurity/sessionGuard.ts
export async function sessionGuard(req: Request, sessions: SessionStore, authz: Authorizer): Promise<Principal> {
  const token = req.headers.get("authorization")?.replace(/^Bearer /, "");
  if (!token) throw unauthorized();
  const s = await sessions.get(hashToken(token));                    // server-side session record, never a client claim
  if (!s || s.revoked || Date.now() > s.expiresAt) throw unauthorized();
  if (Date.now() - s.lastSeenAt > 15 * 60_000) throw unauthorized(); // idle timeout for staff and partner apps
  const deviceId = req.headers.get("x-device-id");
  if (s.deviceId && deviceId !== s.deviceId) {                      // device-bound sessions for mobile work apps
    await sessions.revoke(s.id, "device_mismatch");
    throw unauthorized();
  }
  await sessions.touch(s.id);
  const objectId = new URL(req.url).searchParams.get("customerId");
  if (objectId && !(await authz.canView(s.principal, objectId)))    // object-level check on every read
    throw forbidden();
  return s.principal;
}
  • Short-lived access tokens with server-side revocation; refresh only through the identity provider.
  • Object-level authorisation on every request - the step most often missing from secondary apps.
  • Identical errors for 'does not exist' and 'not yours', so the API does not confirm valid identifiers.
  • Run AI-assisted testing of your own: the same class of tool that found Korea's side doors will find yours first if you point it at them.

“Korea did not show that AI models need licensing. It showed that the forgotten portal on the edge of a bank is now probed by software that never gets bored.”


What Government Should Add

The UK's user-focused model works only if users are equipped. Two additions would help. First, NCSC guidance specific to AI-assisted attack - a short, concrete list like the one above, aimed at partner portals, staff apps and enumeration - published now, while Korea is fresh. Second, a shared, outside-in attack-surface service for regulated financial firms, so smaller lenders and fintechs can see what the attackers' agents see. Britain chose not to regulate the tools. It should make sure every bank can defend against them.

The Bottom Line

Britain's decision to put cyber duties on firms that use technology rather than on AI vendors was criticised as letting the labs off the hook; Korea's seven AI-assisted bank breaches, carried out with a freely available multi-agent tool through a loan-recruiter lookup, a staff app and a sales tool, show it was right, because nothing about the model would have mattered and everything about the firms' own systems did. The engineering the UK model asks of firms is ordinary and urgent: an outside-in inventory of exposed systems using certificate transparency, enumeration detection on distinct identifiers per principal, and server-side, device-bound, object-level session checks on every app. That is the security and AI engineering we deliver for financial firms as an AI agency in London, and it is how the British approach proves itself.

References & Further Reading

AI Agency LondonCyber Security and Resilience BillKorea bank hacksAgentic AI London codeAI Automation London codeWorkflow Automation Londonattack surface management
Share Email
AI

AlchmAI Engineering

Engineering, London

Written by the AlchmAI engineering team in Mayfair, London. We build trading platforms, real-time charts, market data pipelines and AI features for brokers, prop firms and fintech teams. The Playbook is where we explain how we approach these systems, with code you can run and sources you can check.

Code in this guide is illustrative and supplied without warranty. Review and test it before production use. Nothing here is investment advice. Important information