Contagious Interview — BeaverTail / OtterCookie Developer-Workstation Hunt
Executive Summary
Contagious Interview is a North Korean (DPRK) threat cluster — tracked as Famous Chollima / Void Dokkaebi / HexagonalRodent under the Lazarus umbrella — that has industrialized its 2022-era fake-interview social engineering into a high-volume supply-chain operation. In its June 2026 evolution the actor sends recruitment-themed phishing emails (250+ emails in a six-week window to individuals at nearly 100 organizations in finance, crypto, education and tech) that link to actor-controlled GitHub repositories (e.g. the stardev0914 account). Victims — often mid-assessment job candidates — clone the repo, open it in VS Code or Cursor, and approve the editor's "trust the authors" prompt. That trust authorizes automatic execution of the project's tasks.json / postinstall hooks, which run OS-specific loaders (shell script on macOS/Linux, VBScript on Windows) that fetch the next stage from Vercel-hosted infrastructure.
The delivered payloads are the BeaverTail first-stage stealer/loader and the OtterCookie beaconing agent (both increasingly launched by GenAI-assisted loaders such as XORIndex/HexEval), pulling in the InvisibleFerret backdoor as a later stage. The objective is theft: browser-extension and desktop cryptocurrency wallets, browser credential stores, and clipboard contents. The campaign has seeded 1,700+ malicious packages across npm, PyPI, Go, Rust and Packagist, exfiltrated 26,584 crypto wallets from 2,726 infected developer systems, and stolen roughly $12M in cryptocurrency in the first three months of 2026.
The decisive defensive angle is developer-workstation behavior, not atomic IOCs (packages and repos rotate hourly). The unmistakable signal is a code editor (Code.exe / cursor / node / npm) spawning a network-download utility (curl, powershell, wscript, bash -c curl) immediately after a repo is opened, followed by access to browser wallet-extension directories and an outbound connection to hardcoded IP-based HTTP infrastructure.
Defender priority: Hunt for npm / node / VS Code (Code.exe) / Cursor processes spawning curl, powershell -enc, wscript, or osascript on developer endpoints, then pivot any hit to browser-wallet-directory reads and outbound connections to raw IPv4 HTTP endpoints (e.g. 23.137.105[.]75:5173). Treat editor-triggered network downloads on a workstation as guilty until proven benign.
Source Review & Web Hunter Notes
| Tier | Source | Key Finding | Carry Fwd |
|---|---|---|---|
| 1 | The Hacker News — "NK hackers turning..." (Jun 2026) | Industrialized phishing → GitHub repos; Overlord Go framework; BeaverTail/OtterCookie/InvisibleFerret; VS Code runOn:folderOpen; C2 23.137.105[.]75:5173; git-hook & postinstall persistence; $12M / 26,584 wallets / 2,726 systems | yes |
| 1 | The Hacker News — "N-Korean spread 1,700 packages" (Apr 2026) | Named npm/PyPI/Go/Rust/Packagist package IOCs; XORIndex → BeaverTail → InvisibleFerret chain; hardcoded IP HTTP exfil endpoint | yes |
| 1 | Infosecurity-Magazine — Contagious Interview | XORIndex/HexEval loaders, npm download volumes, Lazarus attribution, BeaverTail second-stage / InvisibleFerret third-stage | partial |
| 2 | Microsoft Security Blog — Contagious Interview (Mar 2026) | Fake-interview delivery, VS Code trust-prompt abuse, repo-open task execution | partial |
| 2 | Silent Push / Security Affairs / Trend Micro (Void Dokkaebi) | Front-company infrastructure; OtterCookie via 197+ npm packages; stardev0914 GitHub account; Vercel payload staging | partial |
| — | DarkReading — npm package factory | HTTP 403 at fetch time; corroborated by THN/Infosec coverage instead | no |
Hunter note: Atomic package names and GitHub repos rotate continuously (1,700+ packages, 18+ npm accounts). Detection weight is placed on the behavioral chain; named packages are included as point-in-time blocklist/hunt seeds with appropriate expiry, every one traced to a cited source.
Hunt Brief & Attack Chain
Hunt hypotheses (fidelity-ordered)
- H1 (high): A code editor or package manager (
Code.exe,cursor,node,npm,yarn,pnpm) spawns a network-download interpreter (curl,wget,powershell -enc,wscript,cscript,osascript) shortly after a repo is opened. (T1059, T1204.002) - H2 (high):
npm/nodeexecutes a lifecycle/postinstallhook that immediately reaches an external host or raw IPv4 endpoint. (T1195.002, T1071.001) - H3 (high): A developer-tool process tree connects outbound to a hardcoded IP-literal HTTP endpoint on a non-standard port (e.g.
:5173) — BeaverTail/InvisibleFerret exfil. (T1071.001, T1041) - H4 (medium): A non-browser process reads browser wallet-extension or Login Data directories (MetaMask/Phantom IDs,
Local Extension Settings,Login Data). (T1555.003, T1539, BeaverTail wallet theft) - H5 (medium): Windows persistence via Run-key or Scheduled Task pointing at
node/python/wscriptin a user profile/Temp path; macOS/Linux persistence via git hooks. (T1547.001, T1053.005) - H6 (medium):
pythonloads or writes Cython-compiled.pyd/.somodules (InvisibleFerret migration) or executes from a cloned-repo path. (T1059.006, T1027) - H7 (low): Outbound to Vercel/GitHub raw content fetching an executable payload from within a developer process tree. (T1105)
Attack chain
| # | Step | Telemetry | Hunt Angle |
|---|---|---|---|
| 1 | Recruitment phishing email links to actor GitHub repo | Mail gateway / proxy / DnsRequest | GitHub clone URLs in email; DNS to actor repos |
| 2 | Victim clones repo, opens in VS Code / Cursor, approves "trust authors" | ProcessRollup2 (git/Code.exe/cursor) | git clone followed by editor launch on fresh path |
| 3 | tasks.json / postinstall runs OS loader (sh / vbs / node) | ProcessRollup2 parent=editor/npm | Editor or npm spawning curl/wscript/powershell (H1,H2) |
| 4 | Loader fetches BeaverTail/OtterCookie from Vercel | NetworkConnectIP4 / DnsRequest | Dev process → external download (H7) |
| 5 | BeaverTail enumerates & steals browser-wallet + credential stores | ProcessRollup2 / file access | Non-browser read of wallet/Login Data dirs (H4) |
| 6 | Exfil + tasking to hardcoded IP HTTP C2 (e.g. :5173) | NetworkConnectIP4 | IP-literal HTTP on odd port from dev tree (H3) |
| 7 | Persistence (Run key / Scheduled Task / git hooks) + InvisibleFerret | AsepValueUpdate / ProcessRollup2 | Run-key/task to node/python in Temp (H5,H6) |
Consolidated IOC Table
Provenance: every atomic IOC below traces to a cited Tier-1 source (see §18). Package indicators are point-in-time and high-churn — use as hunt/pivot seeds, not standing blocks, and expire on schedule.
| Type | Value | Conf | Action | Context / Source |
|---|---|---|---|---|
| IPv4 + port | 23.137.105[.]75:5173 | medium | hunt | BeaverTail/InvisibleFerret C2 — THN Jun 2026 |
| GitHub account | stardev0914 | medium | pivot | Actor repo/payload delivery — Trend/Silent Push |
| Staging host | *.vercel.app (actor-controlled subdomains) | low | enrich | Payload fetch stage — Void Dokkaebi reporting |
| npm pkg | dev-log-core, logger-base, logkitx, pino-debugger, debug-fmt, debug-glitz | medium | hunt | 1,700-package campaign — THN Apr 2026 |
| npm pkg | redeem-onchain-sdk, nicegui, period-newline, terminal-logger-utils, terminal-logger-pack, js-logger-pack, node-env-resolve, sleek-pretty | medium | hunt | npm package factory — THN Jun 2026 |
| PyPI pkg | logutilkit, apachelicense, fluxhttp, license-utils-kit | medium | hunt | 1,700-package campaign — THN Apr 2026 |
| Go pkg | github[.]com/golangorg/formstash, github[.]com/aokisasakidev/mit-license-pkg | medium | hunt | Go ecosystem — THN Apr 2026 |
| Rust / Packagist | logtrace (crate); golangorg/logkit, roberts/leads (Packagist) | medium | hunt | Cross-ecosystem — THN Apr/Jun 2026 |
| Loader names | XORIndex, HexEval (npm loaders) | medium | enrich | Loads BeaverTail — Infosecurity-Mag |
| Malware family | BeaverTail, OtterCookie, InvisibleFerret | high | enrich | Named payloads — all Tier-1 sources |
| Behavior tokens | tasks.json runOn:folderOpen, postinstall, .githooks/pre-commit | high | hunt | Execution/persistence — THN + MSFT |
Affected Surface & Telemetry Matrix
| Surface | Required Telemetry | Priority | Gap Risk |
|---|---|---|---|
| Developer workstations (Win/macOS/Linux) | ProcessRollup2 / SyntheticProcessRollup2 | critical | low — core sensor |
| Outbound C2 / payload fetch | NetworkConnectIP4, DnsRequest | high | low |
| Browser wallet / credential stores | ProcessRollup2 cmdline + file-access context | high | med — file-read granularity varies |
| Persistence (Run key / Sched task) | AsepValueUpdate, RegGenericValueUpdate, ProcessRollup2 (schtasks) | medium | low |
| macOS/Linux git-hook persistence | ProcessRollup2 (sh/bash spawned by git) | medium | med — non-Win EDR coverage |
| Email delivery | Mail gateway / proxy logs (non-Falcon) | medium | high — out-of-band |
ATT&CK Mapping
| Tactic | Technique | Observed Behavior | Query / Control |
|---|---|---|---|
| Initial Access | T1566.002 Phishing: Link | Recruitment email links to actor GitHub repo | Native §7 / Hardening §12 |
| Initial Access | T1195.002 Supply Chain: Software Dependencies | Malicious npm/PyPI/Go/Rust packages | Q2 / IOA |
| Execution | T1204.002 User Execution: Malicious File | Repo opened in VS Code/Cursor; trust prompt approved | Q1 |
| Execution | T1059.001/.003/.006/.007 Command & Scripting | powershell / cmd / python / vbscript loaders | Q1, Q2, Q6 |
| Command & Control | T1071.001 Application Layer: Web | HTTP to hardcoded IP endpoint :5173 | Q3 |
| Ingress Tool Transfer | T1105 | Loader fetches payload from Vercel/GitHub | Q3, Q7 |
| Credential Access | T1555.003 Credentials from Web Browsers | Read of Login Data / browser stores | Q4 |
| Credential Access | T1539 Steal Web Session Cookie | Browser cookie/session theft | Q4 |
| Collection | T1115 Clipboard Data | Clipboard wallet-address monitoring | IOA / §7 |
| Persistence | T1547.001 Run Keys | Run-key to node/python/wscript in Temp | Q5 |
| Persistence | T1053.005 Scheduled Task | Scheduled task launching loader | Q5 |
| Exfiltration | T1041 Exfil Over C2 Channel | Wallets/creds sent to IP C2 | Q3 |
Native Audit-Log Hunts (non-CQL)
- Mail gateway / proxy: Hunt inbound mail with body links to
github.com/<new-account>clone URLs paired with recruiter/assessment language ("coding assessment", "take-home", "interview task"). Flag first-seen GitHub orgs. - npm/registry telemetry: If you run an internal registry or Artifactory/Verdaccio, alert on installs of the §4 package names and on first-seen packages with
postinstallscripts that perform network I/O. - VS Code / Cursor config: On managed dev fleets, audit for repos containing
.vscode/tasks.jsonwith"runOptions": {"runOn": "folderOpen"}and for.githooks/pre-commitshipped inside cloned repos. - macOS Unified Log / Linux auditd: Hunt
osascript/curl/bash -cchildren ofnode,Code Helper, orcursor; and gitcore.hooksPathchanges. - Endpoint DNS: Review resolutions to freshly-registered
*.vercel.appsubdomains from developer hosts immediately preceding executable writes.
CrowdStrike LogScale CQL Hunt Queries
Looks for: VS Code / Cursor / node / npm spawning curl, wget, powershell, wscript, cscript, mshta, bash or osascript — the repo-open task-execution step. FP: legitimate build scripts and npm installs that shell out; tune with the C2/raw-IP pivot (Q3) and exclude known internal build hosts.
// HUNT: Editor/pkg-mgr spawns download interpreter (Contagious Interview repo-open) // MITRE: T1204.002, T1059 | CONF: high FP: med COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: build tooling, legitimate npm postinstall, CI agents // TUNING: exclude known build/CI hosts; corroborate with Q3 raw-IP C2 #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName=/^(Code\.exe|Code - Insiders\.exe|cursor\.exe|cursor|Code Helper.*|node|node\.exe|npm|npm\.cmd|yarn|pnpm)$/i | FileName=/^(curl|wget|powershell|pwsh|wscript|cscript|mshta|bitsadmin|certutil|bash|sh|osascript|python3?)(\.exe)?$/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine], limit=200)
Looks for: node/npm command lines invoking install lifecycle hooks combined with download verbs — the malicious-package install step. FP: legitimate packages with native postinstall builds; tune to user-profile / repo paths and pair with outbound-IP correlation.
// HUNT: npm/node lifecycle hook with inline network fetch (malicious dependency) // MITRE: T1195.002, T1059.007 | CONF: high FP: med COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: native-module builds (node-gyp) that download toolchains // TUNING: scope to user-writable repo/Temp paths; exclude vetted internal pkgs #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | CommandLine=/(postinstall|preinstall|prepare)/i | CommandLine=/(curl\s|wget\s|Invoke-WebRequest|iwr\s|http:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|fetch\(|child_process)/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine], limit=200)
Looks for: outbound connections to public IP literals on non-standard ports (e.g. 5173) initiated by a developer-tool process — BeaverTail/InvisibleFerret exfil/tasking, including the documented 23.137.105.75:5173. FP: local dev servers (Vite default 5173) on RFC1918 — query already excludes private ranges; tune to retain only public destinations.
// HUNT: developer process beaconing to public IP literal on odd port (C2 exfil) // MITRE: T1071.001, T1041, T1105 | CONF: high FP: med COST: med // REQUIRES: NetworkConnectIP4 (+ ProcessRollup2 for tree context) // FALSE POSITIVES: local Vite dev server (5173) on private IPs — excluded below // TUNING: confirm RemoteAddressIP4 is public; pin RemotePort to observed C2 ports #event_simpleName=NetworkConnectIP4 | ContextBaseFileName=/^(node|node\.exe|npm|curl|curl\.exe|powershell\.exe|pwsh\.exe|python3?|Code\.exe|cursor|wscript\.exe)$/i | RemoteAddressIP4!=/^(10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|127\.|169\.254\.)/ | in(field="RemotePort", values=[5173, 1224, 3000, 8080, 8000, 5000]) | groupBy([aid, ComputerName, ContextBaseFileName, RemoteAddressIP4, RemotePort], function=count()) | sort(_count, order=desc)
Looks for: command lines referencing browser wallet-extension IDs / Login Data / Local Extension Settings from non-browser processes — BeaverTail wallet & credential theft. FP: backup/security tooling and password managers reading the same paths; exclude known-good readers by ImageFileName.
// HUNT: wallet-extension / browser credential store access by non-browser proc // MITRE: T1555.003, T1539 | CONF: med FP: med COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: backup agents, password managers, browser self-access // TUNING: exclude vetted backup/PWM ImageFileNames; require dev-tool parent #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | CommandLine=/(Local Extension Settings|Login Data|nkbihfbeogaeaoehlefnkodbefgpgknn|bfnaelmomeimhlpmgjnjophhpkkoljpa|Solflare|metamask|Wallets|keystore)/i | ImageFileName!=/\\(chrome\.exe|msedge\.exe|firefox\.exe|brave\.exe|opera\.exe)$/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine], limit=200)
Looks for: Run-key/ASEP writes referencing node/python/wscript/curl out of a user-profile or Temp path. FP: legitimate dev tools that self-register at login; tune to Temp/Downloads/cloned-repo paths only.
// HUNT: Run-key persistence pointing at interpreter in user/Temp path // MITRE: T1547.001, T1053.005 | CONF: med FP: med COST: low // REQUIRES: AsepValueUpdate / RegGenericValueUpdate // FALSE POSITIVES: dev tools registering legitimate startup entries // TUNING: restrict RegStringValue to Temp/Downloads/AppData repo paths #event_simpleName=/AsepValueUpdate|RegGenericValueUpdate/ | RegObjectName=/\\Software\\Microsoft\\Windows\\CurrentVersion\\Run/i | RegStringValue=/(node|python|pythonw|wscript|cscript|curl|powershell).*(\\Temp\\|\\Downloads\\|\\AppData\\)/i | table([@timestamp, aid, ComputerName, RegObjectName, RegValueName, RegStringValue], limit=200)
Looks for: python invoked against scripts under Temp/Downloads/AppData with download or base64 verbs — InvisibleFerret loader behavior. FP: developers legitimately run python from working dirs; this is a high-FP triage hunt — always pair with Q3/Q4 hits before escalating.
// HUNT: python from user/Temp path with network/encoding verbs (InvisibleFerret) // MITRE: T1059.006, T1027, T1105 | CONF: med FP: high COST: low // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: normal developer python usage from project dirs // TUNING: only triage when correlated with a Q3 C2 hit on the same aid #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ImageFileName=/(python3?|pythonw?\.exe)$/i | CommandLine=/(\\Temp\\|\\Downloads\\|\\AppData\\|\/tmp\/).*(urllib|requests|base64|socket|http:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine], limit=200)
Looks for: a host that runs git clone and, within the same window, an editor or interpreter spawning a download utility — stitches the lure-to-execution chain per host. FP: normal clone-then-build workflows; rank by hosts where the download target is a public IP literal.
// HUNT: git clone then editor/interpreter download on same host (lure chain) // MITRE: T1204.002, T1105 | CONF: med FP: med COST: med // REQUIRES: ProcessRollup2 / SyntheticProcessRollup2 // FALSE POSITIVES: legitimate clone-and-build developer workflows // TUNING: prioritize hosts whose download verb targets a public IP literal #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | case { CommandLine=/git\s+clone/i | marker := "clone"; ParentBaseFileName=/^(Code\.exe|cursor|node|npm)$/i AND ImageFileName=/(curl|wget|powershell\.exe|wscript\.exe|bash|osascript)/i | marker := "exec"; * | marker := "other"; } | marker!="other" | groupBy([aid, ComputerName], function=collect([marker, CommandLine, ImageFileName], limit=20)) | CommandLine=/clone/ OR marker="exec"
Field note: All fields above are standard Falcon data-model fields. RemotePort values and the C2 port list reflect observed/likely Contagious Interview tasking ports — confirm port pins against your tenant's own dev traffic baseline before promoting Q3 to an alert.
CrowdStrike Custom IOA Recommendations
| IOA Name | Pattern (process) | Action | Exclusions |
|---|---|---|---|
| CI — Editor spawns downloader | Parent Code.exe|cursor|node|npm → child curl|wscript|powershell|osascript | Detect (Critical) on dev OU; monitor elsewhere | CI/build agents, known internal build hosts |
| CI — postinstall network fetch | CmdLine contains postinstall + (curl|iwr|IP literal) | Detect (High) | Vetted native-module packages (node-gyp) |
| CI — IP-literal C2 from dev tool | node/curl/python → public IPv4 HTTP on 5173/odd port | Detect (High) | Private-range Vite/dev servers (already excluded) |
| CI — wallet store read by non-browser | Non-browser proc CmdLine references wallet ext IDs / Login Data | Detect (Medium) | Approved backup / password-manager binaries |
Promote Q1, Q2 and Q3 to Custom IOAs scoped to a developer host group first (lower FP blast radius), validate for one detection cycle, then widen. Q4–Q7 remain Investigate-only hunts pending tenant baselining.
Machine-Readable IOC Appendix
Grouped quick-copy blocks. Atomic indicators are point-in-time (cited §18); replace REPLACE_WITH_* placeholders only with hashes you confirm from a real source — do not fabricate.
type,value,action,severity,expiration,description,tags ipv4,23.137.105.75,detect,high,2026-09-25,Contagious Interview BeaverTail/InvisibleFerret C2,campaign:ContagiousInterview sha256,REPLACE_WITH_CONFIRMED_HASH,prevent,critical,2026-12-25,BeaverTail loader payload,campaign:ContagiousInterview domain,REPLACE_WITH_CONFIRMED_VERCEL_SUBDOMAIN,detect,medium,2026-07-25,Actor payload staging,campaign:ContagiousInterview
# Parent-spawns-downloader (editor/npm => curl|wscript|powershell|osascript) # npm/node lifecycle: postinstall|preinstall + (curl|iwr|IP-literal) # Public IPv4 HTTP from node/curl/python on odd port (e.g. 5173) # Non-browser read of: Login Data, Local Extension Settings, wallet ext IDs # VS Code tasks.json runOn:folderOpen ; .githooks/pre-commit in cloned repo
# npm dev-log-core logger-base logkitx pino-debugger debug-fmt debug-glitz redeem-onchain-sdk nicegui period-newline terminal-logger-utils terminal-logger-pack js-logger-pack node-env-resolve sleek-pretty # PyPI logutilkit apachelicense fluxhttp license-utils-kit # Go github.com/golangorg/formstash github.com/aokisasakidev/mit-license-pkg # Rust crate: logtrace | Packagist: golangorg/logkit roberts/leads # Malware: BeaverTail OtterCookie InvisibleFerret | Loaders: XORIndex HexEval
# VS Code: enable Workspace Trust (security.workspace.trust.enabled=true) # Block tasks.json auto-run: security.workspace.trust.untrustedFiles=open # npm: set ignore-scripts=true on dev images for untrusted clones # Audit git config core.hooksPath on developer endpoints # Egress: alert dev hosts => public IPv4-literal HTTP on non-standard ports
Detection Validation Gates
| Gate | Check | Pass criteria |
|---|---|---|
| Telemetry ready | ProcessRollup2 + NetworkConnectIP4 flowing from developer OU hosts | >= 95% dev endpoints reporting in last 24h |
| Benign baseline | Run Q1/Q2 over 7d; catalog legitimate editor-spawns-downloader on build/CI hosts | Known-good parents enumerated & excluded |
| Positive test | In a lab VM, open a benign repo whose tasks.json runs curl http://<test-ip>:5173 | Q1 and Q3 both fire on the test host |
| Wallet-read test | Run a script reading a dummy Login Data path from non-browser | Q4 fires; approved readers excluded |
| Promotion | Q1/Q2/Q3 false-positive rate over one cycle on dev OU | < 1 FP/host/week before IOA promotion |
Hardening — Tiered & Deployable
- Enforce VS Code Workspace Trust (
security.workspace.trust.enabled) and disable auto-task execution on untrusted folders — directly defeats thetasks.json runOn:folderOpenvector. (M1038 Execution Prevention; vendor guidance) - Set
ignore-scripts=truein the developer-image npm config so cloned/untrusted packages cannot runpostinstallautomatically. (M1042 Disable/Remove Feature) - Block public IPv4-literal HTTP on non-standard ports from developer egress (proxy/firewall) — strips the BeaverTail C2 channel. (M1037 Filter Network Traffic)
- Block §4 packages in the internal registry / dependency firewall as point-in-time hunt seeds. (M1042)
- User-awareness for recruitment lures targeting developers / crypto staff: never run interview "assessment" repos on a corporate endpoint. (M1017 User Training)
- App control / WDAC on dev fleet to constrain interpreters (wscript, mshta, powershell) launched by editors. (M1038)
- Browser-wallet-extension governance: inventory and, where policy allows, restrict crypto-wallet extensions on corporate browsers. (M1042)
- Isolate developer build/test work in disposable VMs or remote dev containers, segmented from credential/wallet stores. (M1030 Network Segmentation)
- Dependency-provenance enforcement (lockfile pinning, signature/SLSA checks, registry allow-listing) across all five ecosystems. (M1016 Supply Chain)
Deployable Playbooks
1 · VS Code Workspace Trust + disable folder-open tasks (managed settings.json)
// Deploy to %APPDATA%\Code\User\settings.json (Win) or
// ~/Library/Application Support/Code/User/settings.json (macOS) via MDM/GPO
{
"security.workspace.trust.enabled": true,
"security.workspace.trust.startupPrompt": "always",
"security.workspace.trust.untrustedFiles": "open",
"task.allowAutomaticTasks": "off"
}
2 · Harden npm on developer images (disable lifecycle scripts for untrusted installs)
# System/user npmrc — block auto-run of pre/postinstall scripts npm config set ignore-scripts true --global # Verify npm config get ignore-scripts # expect: true # For vetted internal builds, run install explicitly with scripts enabled: # npm ci --foreground-scripts --ignore-scripts=false (in CI only)
3 · Audit git hooksPath across the dev fleet (PowerShell)
$paths = git config --show-origin --get-all core.hooksPath 2>$null
if ($paths) { Write-Warning "Non-default git hooksPath set:"; $paths }
else { Write-Output "core.hooksPath: default (no override)" }
# Flag any repo shipping its own .githooks/ + core.hooksPath override
4 · Egress block — dev hosts to public IP-literal HTTP on odd ports (firewall pseudo-rule)
# Proxy/NGFW: from DEV_WORKSTATION_GROUP
# deny dst=PUBLIC and dst-port in {5173,1224,3000,8000,8080} and app=http/raw
# log+alert matches; allowlist legitimate SaaS dev endpoints by FQDN only
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Network-contain the host in Falcon; preserve process tree & netconn timeline | SOC L2 | aid, ComputerName, Q1/Q3 hits |
| Eradicate | Kill loader/payload; remove Run-key/Sched-task/git-hook persistence; delete cloned repo + fetched payloads | IR | Q5 reg entries, file paths |
| Credential reset | Assume browser creds + crypto wallets compromised: rotate all browser-stored passwords, session tokens, and move funds from any wallet that touched the host | IR + user | Q4 wallet-read hits |
| Hunt-wider | Pivot C2 IP across fleet; sweep dev OU with Q1–Q3; check internal registry for §4 packages | Threat Hunt | Q3 groupBy, registry logs |
| Recover | Reimage host; restore from clean backup; re-enable after Workspace-Trust/npm hardening applied | IT | Rebuild ticket |
Detection Coverage Map
| Technique | Behavior | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1204.002 / T1059 | Editor spawns downloader | Q1, Q7 | Yes | Good |
| T1195.002 | Malicious package postinstall | Q2 | Yes | Good |
| T1071.001 / T1041 / T1105 | IP-literal C2 / payload fetch | Q3, Q7 | Yes | Good |
| T1555.003 / T1539 | Browser wallet / credential theft | Q4 | Yes | Partial |
| T1547.001 / T1053.005 | Run-key / Sched-task persistence | Q5 | No | Partial |
| T1059.006 / T1027 | InvisibleFerret python/.pyd stage | Q6 | No | Partial |
| T1115 | Clipboard wallet-address swap | — | IOA only | GAP |
| T1566.002 | Recruitment phishing link delivery | — (mail GW) | No | GAP — out of band |
| macOS/Linux git-hook persistence | git core.hooksPath abuse | §7 / §13.3 | No | Partial |
Known gaps: clipboard-hijack and email-delivery detection sit outside Falcon endpoint telemetry — cover via browser/clipboard EDR signals and the mail-gateway hunt in §7. Persistence and InvisibleFerret hunts (Q5/Q6) are present but unpromoted pending tenant baselining (see §11 gates). Validation: do not promote any query to a blocking IOA until its §11 gate passes.
Hunt Summary Ticket
TITLE......: Hunt — Contagious Interview (BeaverTail / OtterCookie) on developer endpoints
SEVERITY...: HIGH (active DPRK nation-state supply-chain + social engineering)
SCOPE......: Developer / engineering workstations (Win, macOS, Linux); crypto/finance staff
HYPOTHESIS.: Recruitment-phish => actor GitHub repo opened in VS Code/Cursor => tasks.json/
postinstall loader => BeaverTail/OtterCookie => wallet+cred theft => IP C2 exfil
QUERIES....: Q1 editor-spawns-downloader | Q2 npm postinstall netfetch | Q3 IP-literal C2
Q4 wallet/cred read | Q5 run-key persist | Q6 InvisibleFerret python | Q7 chain
DO FIRST...: Run Q1 + Q3 over 7d on dev OU; triage any host where both fire on same aid
FINDINGS...: <pending hunt execution>
GAPS.......: Clipboard hijack (T1115) + email delivery = out-of-band; Q5/Q6 unpromoted
ACTIONS....: Enforce VS Code Workspace Trust; npm ignore-scripts; block IP-literal egress;
rotate browser creds + move wallet funds for any confirmed host
OWNER......: HuntPack / Threat Hunt
VERSION....: v0.1 · 2026-06-25
Changelog
table() calls now carry an explicit row limit: the default is 200 and truncation is silent, so a capped result was indistinguishable from a complete one. Atomic-IOC sweeps (filters over 5+ hashes or C2 IPs) use limit=max so a wide infection is never silently under-scoped; behavioural hunts use limit=200, where exceeding the cap indicates the query needs tuning. Where present, event names that do not exist in the Falcon data model were corrected (e.g. ServiceInstalled is a Sysmon concept, not a Falcon event; ElfFileWritten is ELFFileWritten) — such queries could never return a row. No detection logic, fields, or IOCs changed.References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| 1 | THN — North Korean hackers turning (Jun 2026) | Chain, C2 IP, persistence, Overlord, impact figures | 2026-06-25 |
| 1 | THN — 1,700 malicious packages (Apr 2026) | npm/PyPI/Go/Rust/Packagist package IOCs; loader chain | 2026-06-25 |
| 1 | Infosecurity-Magazine — Contagious Interview | XORIndex/HexEval, attribution, payload staging | 2026-06-25 |
| 2 | Microsoft Security Blog (Mar 2026) | VS Code trust-prompt / repo-open task execution | 2026-06-25 |
| 2 | Trend Micro — Void Dokkaebi | stardev0914 account, Vercel staging, OtterCookie | 2026-06-25 |
| 2 | DarkReading — npm package factory | Cross-reference (403 at fetch; corroborated via THN) | 2026-06-25 |