Detecting and stopping the DPRK developer chain: a control map for Terraform, macOS and cloud

The companion post to this one walks the TraderTraitor chain from a fake interview to a $292 million theft. This post is the working half: what to detect at each stage, what to change so the next chain fails, and where the existing controls were pointed at the wrong problem.

It is written to be usable. Every detection below names the telemetry it needs and why it survives the attacker rebuilding their tooling, and every mitigation is tied to the specific step it breaks rather than offered as general good practice.

Start with what did not work

Three failures define the problem, and none of them is a missing product.

  • The endpoint agent was running and did not alert. Both implants were on the LayerZero developer’s MacBook within a seven minute window, and the EDR in place did not detect them [1][2]. The exploit primitive was a provider binary fetched and executed by a tool the developer was expected to run.
  • SSO and mandatory MFA were enforced. They did not matter, because the attacker did not authenticate. They used the developer’s own sessions, harvested from an infected machine, to reach cloud and source control [2].
  • The cryptographic controls held and still lost. The verifier’s signing keys were never compromised and the on-chain quorum check passed, because the signatures were produced by the legitimate keys over a message that had been falsified upstream, in the RPC data path [2].

None of this makes the older guidance wrong. The joint advisory that named this actor in 2022 told organisations in this targeting profile to patch, to prioritise known exploited vulnerabilities, to train users to recognise and report phishing, and to enforce multifactor authentication [5]. Those remain the floor. What the 2026 chain demonstrates is that the floor is not the finish line: multifactor authentication was enforced throughout the LayerZero intrusion and the attacker never needed to defeat it, because they reused a session that had already passed it [2].

That third failure is the one worth designing around. Every control that verified authenticity worked. The attacker never attacked authenticity; they attacked the inputs and the session, which sit outside what cryptographic verification covers. Detection and mitigation for this chain therefore belong in three places the classic endpoint-centric model underweights: the toolchain, the identity layer, and the data path into anything that makes a trust decision.

Five planes, five sets of telemetry

Five planes of detection for the developer attack chain

Figure 1. Each plane saw something the others could not. A programme that only covers the endpoint is blind to three of the five.

Telemetry you needWhat it answers in this chainHow to get it
Process execution with signing status and parentageWas an unsigned or ad hoc signed binary launched from a user directory, and did an IDE launch it? This is the single highest-value alert for the implant stage [1]macOS Endpoint Security framework telemetry through the EDR, or an equivalent collector that reports code signing status and the full process tree
File integrity monitoring on the user Library treeDid anything new appear in ~/Library/LaunchAgents, ~/Library/com.apple.*, or the Spelling directory, and did a quarantine attribute get removed from a newly written binary? [1]FSEvents or an agent-based file monitor scoped to the home directory, plus periodic MDM inventory of LaunchAgents and LaunchDaemons
DNS and egress logs with process attributionWhich process talked to api.telegram.org, to a public Nostr relay, or to a newly registered host serving /app_version? [1][3]Network security monitoring with per-process attribution where possible, plus DNS logging on the corporate resolver and any VPN exit
Identity provider and cloud audit logsWas a known session used from an unfamiliar device, IP range or client, and did a workstation start performing administrative calls it never performed before? [2]IdP sign-in and session logs, GCP Cloud Audit Logs, GitHub audit log, exported to a store the endpoint cannot reach
Source control and CI activityDid a pull request or a build introduce a provider address outside the official registry, and when did that lock file first appear? [1][2][7]Repository audit log, CI job logs, and a policy stage in the pipeline that inspects .terraform.lock.hcl before any init runs
Control plane configuration change historyWhen did the verification threshold change, and who changed it? [2]Configuration change logging for the systems that decide trust, kept with the same retention as financial records

Detection catalogue 1: the developer endpoint

The implant stage is where this chain is most detectable, because it cannot fully hide what it must do: run unsigned code from a writable directory, keep itself running, and talk out.

SignalWhy it survives the attacker rebuildingNotes for tuning
A Mach-O binary executing from ~/Library, ~/Documents or a project folder that is ad hoc signed or unsignedThe actor needs a path that survives being renamed and does not require a developer certificate. Ad hoc signing is the practical option, and the signing status is visible at execution [1][3]Expect noise from local development builds. Scope the rule to binaries whose parent is an IDE, a shell spawned by an IDE, or launchd, and to paths outside known build directories
A process whose name matches an Apple system component but whose path is inside the user’s home directoryThe masquerade is the point: SystemUpdate in ~/Library/com.apple.iTunesCloud and iSync in ~/Library/com.apple.internal.ck are designed to survive a glance [1]Alert on any process whose executable path is under /Users but whose name matches a known Apple component list. This rule has a very low false positive rate
A LaunchAgent plist created in ~/Library/LaunchAgents with a com. label, RunAtLoad true, and an argument such as –type=rendererPersistence has to be registered somewhere, and user LaunchAgents are the least privileged option available to a non-root implant [1]Baseline the LaunchAgents present on each managed Mac, then alert on additions. Brand new plists are rare on a settled machine
A command line that removes the quarantine attribute from another file, immediately followed by chmod +x on the same pathThis is the Gatekeeper bypass step and it is nearly impossible to avoid if you want a second stage to run without a prompt [1]Match on the xattr form with -r or -d and com.apple.quarantine, correlated within a short window with an executable bit change on the same path
An IDE process spawning shells that then launch a non-IDE binaryThe implants on the second victim were launched by Cursor seconds after a workspace was opened, through the integrated terminal [1]Alert when a process tree rooted at an IDE executable contains a binary that is neither an IDE component nor a developer toolchain binary, particularly from the home directory
Telegram Bot API polling from a process that is not TelegramThe command channel for FLATROOF is the Telegram Bot API, and the implant has no way to look like the real client for long [1][3]Distinguish by process and by user agent where the request can be inspected. Corporate Macs have little legitimate reason to poll the Bot API
WebSocket sessions to public Nostr relays from a client that is not a Nostr applicationROOFDECK resolves its command and control through live queries to relays such as relay.damus[.]io and nos[.]lol [1]Maintain a list of the well known public relays, and treat any session from a process outside your approved client set as an incident candidate
Reads of the login keychain database by a process that is not a system componentOne of the collection targets is a raw copy of login.keychain-db. Reading it triggers macOS protections, and the prompts are themselves evidence [1]Capture endpoint alerts for keychain access, and correlate with process signing status. Users may click through a prompt, so treat a granted prompt with suspicion
Clipboard reads from a non-standard process, and any clipboard writes from a process that is not a password manager or editorROOFDECK can read and write the clipboard, which covers both credential capture and address substitution [1]Clipboard write from an unknown process is a high-signal event on a machine that handles wallet addresses
Presence of /private/tmp/.pipe-airway, or ~/.config/.repl_history containing keys such as server_url or nostr_public_keysThese are artefacts the implant needs, and they are specific enough to hunt by name during an investigation [1]Treat as an incident trigger rather than a suspicious artefact. A file named .repl_history that contains configuration keys is not a shell history file
Any SHA-256 or SHA-1 from the published indicator setHashes rotate, but they are precise for retro-hunting the specific wave [1]Sweep the estate once, then rely on behavioural rules, because the actor rebuilt the tooling within a day of public disclosure
A TLS certificate whose subject carries a development CA and a workstation nameThe operator’s own pinned certificate leaked a QEMU machine name and username, which let the researchers pivot across domains [1]On the network side, flag self-signed or development-CA certificates presented by external hosts. During an investigation, use the certificate fingerprint given in the report to find other infrastructure

Detection catalogue 2: the toolchain is the exploit

Everything in the implant catalogue happens after the developer has already run attacker code. The toolchain signals below happen before, and they are cheaper.

SignalWhy it mattersWhere to enforce or observe
A .terraform.lock.hcl entry naming a registry host other than registry.terraform.ioThis is the exploit primitive. The provider address format allows an explicit hostname, so the anomaly is a policy question, not a syntax error [1][2][8]Pull request review, a pre-commit hook, and a pipeline gate before terraform init. See the check below
DNS or proxy requests to a registry host that resembles HashiCorpThe three domains observed were registry.hashicorp-aws[.]com, registry.hashicorp-aws[.]io and registry.hashicorp-terraform[.]io, all typosquats of a legitimate-looking namespace [1]DNS logging and egress filtering on developer networks and build runners. Block the observed domains, and alert on any registry host that is not the official one
A repository that is unexpectedly large for an interview task, or whose history shows little organic activityThe LayerZero lure repository was 1.37 GB, with the malicious provider buried inside realistic infrastructure code [2]Require interview repositories to be reviewed before they are cloned onto a corporate machine, and prefer running them from a disposable environment
A provider package download from a host that is not an approved artifact repositoryterraform init runs the provider as a local process, so the download is the moment of execution [2][7]Build runner egress policy, plus a check that provider sources in the lock file match an approved allowlist
IDE workspace auto-open or task auto-run on project openOn the second victim, opening the workspace was followed within seconds by shell and implant execution [1]Disable automatic task execution and workspace trust prompts where the platform allows it, and require explicit trust on first open
Dependency diffs in package manifests that add an unfamiliar transitive dependencyThe parallel axios case added a malicious dependency through a compromised maintainer account, which no reviewer could have recognised as hostile from the name alone [4]Dependency review with provenance and age checks, maintainer account protection such as mandatory hardware-backed MFA, and lockfile integrity checks in CI

A minimal lock file gate, which catches the exact primitive used here and any future renaming of it, because it checks the property that makes the attack work rather than the domains observed:

# Reject any provider address that names a registry host other than the official one.
# Run in pre-commit and in the pipeline, before any terraform init.
import pathlib, re, sys

OFFICIAL = "registry.terraform.io"
violations = []

for lockfile in pathlib.Path(".").rglob(".terraform.lock.hcl"):
    text = lockfile.read_text(encoding="utf-8", errors="replace")
    for address in re.findall(r'provider\s+"([^"]+)"', text):
        parts = address.split("/")
        # HOSTNAME/NAMESPACE/TYPE has three parts; NAMESPACE/TYPE has two and
        # implies the official registry.
        host = parts[0] if len(parts) == 3 else OFFICIAL
        if host != OFFICIAL:
            violations.append(f"{lockfile}: {address}")

for v in violations:
    print(f"non-official provider registry: {v}")

sys.exit(1 if violations else 0)

The same principle extends beyond Terraform. Any build tool that fetches and executes a plugin, a dependency or a script from a source named inside a committed file has this property, and the control is always the same: constrain which sources are allowed, and check that constraint before execution rather than after.

Detection catalogue 3: identity, cloud and the control plane

The middle of this chain is quiet on the endpoint and loud in the logs, provided the logs are collected somewhere the endpoint cannot reach.

SignalWhy it catches this chainTelemetry source
A known identity authenticating or using a token from a commercial VPN exit rangeThe attacker routed cloud and source control access through ExpressVPN, NordVPN and Mullvad after harvesting sessions from the laptop [2]IdP and cloud audit logs enriched with IP reputation and ASN data. Treat VPN ranges as elevated risk for administrative identities rather than blocking them outright, since legitimate staff use them
Administrative API calls from a device fingerprint that has never administered that environmentThe developer’s laptop acquired administrative reach it had not previously exercised, which is a behavioural change independent of the credentials used [2]Device and session context in cloud audit logs, joined against a baseline of which devices normally perform administrative actions
The same session token used concurrently from two device fingerprints or two geographiesSession theft produces this signature almost immediately [2]IdP and cloud session telemetry with alerting on concurrent use from divergent device contexts
Unexpected processes or shared objects inside a node running a consensus or RPC clientThe poisoning step injected a launcher and a monitor library into the running op-geth process on GKE nodes [2]Node-level runtime monitoring, file integrity on /usr/lib, and core dump collection that is actually enabled, since the analysis here depended on crash dumps that were recovered by luck as much as design
Divergence between RPC providers serving the same queryThe poisoned nodes answered correctly for monitoring tools and lied only to the verifier. Comparing two independent providers turns that divergence into evidence [2]Infrastructure monitoring that queries more than one provider and alerts on disagreement, with the comparison itself running on a path independent of the nodes it watches
A change to the threshold or verifier set on a channel that carries valueOne configuration change from two verifiers to one is what made the forged attestation sufficient [2]Configuration change logs for the verification layer, alerted on with the same severity as a firewall rule change
Attestations produced while external data sources are degraded or failing overThe theft happened minutes after a denial of service forced dependency on compromised internal nodes [2]Correlate availability events with signing activity. An attestation issued during a failover window deserves a review, not a shrug
New service account keys, new OAuth grants, or new personal access tokens in a cloud or source control tenantSession access is often followed by creating durable access so the intrusion survives password resets [2]Cloud IAM and source control audit logs, alerted and reviewed rather than logged

There is an uncomfortable lesson in the second row of that table and in the RPC row. The organisation had strong authentication and its keys were never stolen. What it lacked was a way to answer a simple question quickly: is the data my signing service is reading the same data everyone else can see? Building that comparison is cheaper than rebuilding an environment after a theft [2].

The recruiter lure itself is not new, which is the strongest argument for architectural fixes over indicators. Unit 42 has tracked the fake interview approach since 2023 and has watched it survive repeated revisions of the malware it delivers, including the move from one stealer family to another [6]. The detection content in this post is therefore written around behaviours and tool properties rather than hashes or domains, because the actor rebuilt their tooling within a day of the LayerZero disclosure [1].

Mitigation: the four controls that break this chain

Four controls that break the TraderTraitor developer chain

Figure 2. Each of these four, applied alone, would have stopped the March 2026 chain at a different stage. None of them is a product purchase.

ControlThe step it breaksEffort and trade-offs
Run interview and take-home projects in a disposable environment with no credentials, no source control tokens and no corporate browser profileExecution and everything after it. A sandboxed run converts a full intrusion into a laptop rebuild [1][2]Low effort, real friction. Provide the disposable environment as a first-class service, otherwise engineers will use the workstation because it is the path of least resistance
Gate provider sources in CI: fail any lock file that names a registry other than the official one, and allowlist artifacts on build runnersThe execution primitive itself, before the binary is ever fetched [1][2][7][8]Low effort, low friction once the check is written. Expect an early wave of legitimate exceptions from teams using private registries, which is exactly the conversation to have
Remove standing credentials from developer endpoints: short-lived tokens, device-bound sessions, just-in-time elevation with review for administrative actionsThe theft and reuse of session material. A token bound to a device the attacker is not on stops being useful [2]High effort, high friction if done badly. It requires identity engineering, not a policy document, and LayerZero’s own rebuild shows the shape: no legacy credentials, just-in-time elevation, multi-person review for IAM changes, and device and session validation on administrative requests
Require independent verification in the path that makes trust decisions: at least two required verifiers, and cross-check data from sources that do not share a failure modeThe poisoning step. With a second independent verifier, the attacker must forge agreement between systems with different data paths [2]Configuration decision with a cost. LayerZero’s verifier now refuses to be the sole required attestor, which is a strong default for anyone operating a similar service

Everything else in a security programme still matters, but these four are the ones specific to this chain, and they map to where the attacker’s actual advantage lay: execution by design, a file that names a source, a session that already had access, and a single source of truth.

The AI triage problem, which is new

One detail in this campaign has no precedent in older malware reporting. The FLATROOF implant embeds roughly 3.5 KB of text presented as 38 fabricated system messages, engineered to make an LLM-assisted triage pipeline abandon or refuse its own analysis of the sample [3]. It is a prompt injection aimed at the defender, not at a sandbox.

If your SOC uses a model anywhere in triage, enrichment or summarisation, treat the following as requirements rather than ambitions.

  • Malware strings, filenames, command lines, registry values and network data are untrusted input. Anything inside an artefact that addresses an assistant should be treated the way you would treat a suspicious attachment, not read as context.
  • Never let a model’s conclusion be the last word on containment. A deterministic check, such as a hash match, a signature status or a rule hit, should be able to override a model’s judgement and cannot be talked out of it by the sample.
  • Log the model’s inputs when it makes a triage decision, so that after the fact you can see what it was shown. Without that, an adversary who shapes the input has an unlogged influence on your response.
  • Keep a human-verifiable artefact for every automated decision, and test your pipeline with a deliberately hostile sample that contains instructions, so the failure mode is understood before an actor uses it [3].

A 30, 60, 90 plan

WindowWorkDone when
First 30 daysSweep for the published indicators across the estate. Deploy the lock file gate in pre-commit and in the pipeline. Publish the interview-on-a-corporate-machine policy and stand up a disposable environment so compliance is easier than non-compliance. Baseline LaunchAgents and collect the process telemetry needed for the IDE-child ruleYou can answer, in minutes, whether any lock file in any repository names a non-official registry, and you have a platform your engineers can actually use for take-home tasks
Days 31 to 60Turn on the endpoint rules that matter most: unsigned code from home directories launched by an IDE, Apple-named processes with non-Apple paths, LaunchAgent additions, quarantine-removal sequences, Telegram and Nostr egress. Export identity, cloud and source control logs to a store outside the endpoint’s reach. Agree a definition of an administrative identity and start baselining which devices use itThe four highest-value endpoint rules are alerting, and you have a 30 day baseline of what normal looks like on a developer Mac
Days 61 to 90Move developer access to short-lived, device-bound credentials with just-in-time elevation. Add a second required verifier or an independent cross-check on any path that makes trust decisions. Rehearse the response runbook below on a real Mac. Extend hostile-input testing to any AI-assisted triageThe two architectural controls are live in at least one environment, and the team has practised handling a laptop that ran an interview project rather than reading about it

If a developer ran an interview project

  1. Do not power the machine off and do not start cleaning it. The interesting artefacts here live in memory and in the crash dumps that analysis depended on, and a reboot may discard the process list and any injected code [2].
  2. Capture the process tree and the launch configuration before anything else: running processes with paths and signing status, LaunchAgents and LaunchDaemons plists, and anything recently created under ~/Library [1].
  3. Check for the named artefacts from this wave: the two implant paths, a plist in ~/Library/LaunchAgents, the configuration file at ~/.config/.repl_history with server_url or nostr_public_keys keys, and the pipe at /private/tmp/.pipe-airway [1].
  4. Treat every credential reachable from that machine as exposed, and rotate from a different device: cloud sessions and refresh tokens, source control personal access tokens and SSH keys, cloud service account keys, browser sessions, and any keychain item that was present [1][2].
  5. Review cloud, source control and identity logs for that identity for unusual source IPs, VPN ranges, device fingerprints and administrative calls, going back to the first day the project was cloned, not just the day the alert fired [2].
  6. Inspect the lock file in the offending repository. The provider address tells you where the payload came from, and the domains are worth blocking across the estate even though the actor rotates them [1].
  7. Check the repository’s provider manifests and any sibling repositories from the same organisation, since the lures are distributed as a family of projects [1].
  8. Rebuild rather than clean the machine. Both implants here installed persistence, deployed follow-on builds and deleted evidence, and the second build was pushed specifically to evade analysis [1].
  9. Report the repository and the impersonated recruiter profile to the hosting platforms, and notify your peers if the same lure targeted other staff, because the same actor approaches multiple employees [1].
  10. Feed what you learn back into the pipeline gate and the endpoint rules, then run the exercise again on a test machine to confirm the rule fires [1].

Three questions for this week

  1. If one of our engineers ran a take-home task from a stranger’s repository tomorrow, where would it run, and what could it reach from there? [1]
  2. If a developer’s session were stolen today, how long would the token remain valid, and would it be usable from a machine that is not theirs? [2]
  3. In the systems where we make trust decisions, how many independent sources would have to agree before a forged statement got through? [2]

Sources

  1. SentinelOne Labs, “Don’t Call Us, We’ll Call Your APIs: TraderTraitor Backdoors Resurface on Victim With No Crypto Ties” (18 September 2026). Source for the implant mechanics, the second victim, the interview repositories, the detection recommendations and the indicator set. · https://www.sentinelone.com/labs/dont-call-us-well-call-your-apis-tradertraitor-backdoors-resurface-on-victim-with-no-crypto-ties/
  2. LayerZero Labs, “KelpDAO Incident Report” (18 May 2026). Source for the intrusion timeline, the session key reuse, the RPC poisoning analysis, the five layer failure review and the security changes made afterwards. · https://layerzero.network/publications/kelpdao-incident-report.pdf
  3. SentinelLABS, “macOS.Gaslight: Rust Backdoor Turns Prompt Injection on the Analyst, Not the Sandbox” (23 June 2026). Source for the Telegram command channel, the self-redacting bot token and the prompt-injection payload aimed at LLM triage. · https://www.sentinelone.com/labs/macos-gaslight-rust-backdoor-turns-prompt-injection-on-the-analyst-not-the-sandbox/
  4. Google Threat Intelligence Group, “North Korea-Nexus Threat Actor Compromises Widely Used Axios NPM Package in Supply Chain Attack” (1 April 2026). Source for the parallel dependency-based developer campaign. · https://cloud.google.com/blog/topics/threat-intelligence/north-korea-threat-actor-targets-axios-npm-package
  5. FBI, CISA and US Treasury, joint Cybersecurity Advisory AA22-108A on TraderTraitor, including the baseline mitigations of patching, phishing awareness and multifactor authentication for organisations in this targeting profile. · https://www.cisa.gov/news-events/cybersecurity-advisories/aa22-108a
  6. Unit 42, “Contagious Interview: DPRK Threat Actors Lure Tech Industry Job Seekers to Install New Variants of BeaverTail and InvisibleFerret Malware”, for the longer-running fake recruiter campaign and how it has persisted across malware revisions. · https://unit42.paloaltonetworks.com/north-korean-threat-actors-lure-tech-job-seekers-as-fake-recruiters/
  7. HashiCorp, “Dependency Lock File (.terraform.lock.hcl)”, documenting what the lock file records, that terraform init maintains it, and how package hashes are applied. · https://developer.hashicorp.com/terraform/language/files/dependency-lock
  8. HashiCorp, “Provider Requirements”, defining the provider source address format and the default registry hostname used in the CI check above. · https://developer.hashicorp.com/terraform/language/providers/requirements

The point of the exercise

This chain did not succeed because a control failed at one layer. It succeeded because four reasonable assumptions held at once: that code you are asked to run for an interview is a normal part of work, that a lock file is bookkeeping, that a valid session means the right person, and that one trusted data source is enough to decide what is true. Each of those can be changed, and three of the four changes are cheap.