THREAT-C2-VSCodeTunnelGitHubDeviceCodeC2 Sumo Logic CSE · Sumo

Detect Command and Control via VS Code Remote Tunnel GitHub Device Code Authentication in Sumo Logic CSE

Visual Studio Code ships a built-in remote tunnel feature (`code tunnel`) that lets a developer reach a machine from any browser via a vscode.dev URL, authenticated through a GitHub or Microsoft account device-code flow: the CLI prints a short-lived code and a github.com/login/device URL, the operator approves it from any browser on any device, and the tunnel then rides through Microsoft's own relay infrastructure. Because code.exe and code-tunnel.exe are signed Microsoft binaries frequently present on developer endpoints, this channel is close to invisible to allowlisting and to network egress controls that trust Microsoft-owned domains. Operation Digital Eye, reported by SentinelOne in December 2024, showed an intrusion set abusing exactly this feature against Southern European IT service providers: a `code tunnel` session was launched headlessly (no VS Code GUI, `--accept-server-license-terms` supplied non-interactively) from a binary that had been placed outside the standard VS Code install directory, and the resulting tunnel was used as an interactive command shell rather than for its intended remote-development purpose. This detection differs from the base T1219.001 record — which covers IDE tunneling generically across VS Code, JetBrains Gateway, and DevTunnel — by encoding the specific three-arm shape of this abuse pattern: (1) the tunnel CLI executed with device-code or license-acceptance flags consistent with unattended/headless use, (2) egress from that same binary to the Microsoft tunnel relay or vscode.dev, and (3) the binary itself running from a folder that is not a standard VS Code installation, which is the single strongest discriminator because a portable or renamed CLI dropped into Temp, ProgramData, or a user profile root has no legitimate reason to exist there. The rule requires at least two of these three arms to agree on the same host before alerting, because any one arm alone — a developer's own tunnel session, a build agent staging a portable CLI, or a proxy log entry for vscode.dev — is common and benign in isolation.

MITRE ATT&CK

Tactic
Command and Control

Sumo Detection Query

Sumo Logic CSE (Sumo)
sql
_sourceCategory=endpoint/sysmon EventID=1
| parse "Image=*," as image nodrop
| parse "CommandLine=*," as cmdline nodrop
| where (image matches "*code.exe" or image matches "*code-tunnel.exe" or image matches "*code-insiders.exe")
| where (cmdline matches "*tunnel*accept-server-license-terms*" or cmdline matches "*tunnel user login*" or cmdline matches "*serve-web*")
| eval is_standard_path = if (image matches "*Program Files\\Microsoft VS Code*" or image matches "*AppData\\Local\\Programs\\Microsoft VS Code*", 1, 0)
| count as CliEvents, sum(1-is_standard_path) as NonStandardPathEvents by host
| where CliEvents >= 1
high severity low confidence

Sumo Logic query over Sysmon Event ID 1 process-creation logs, matching VS Code CLI binaries invoked with a tunnel-bootstrap command line and separately flagging whether the binary ran from a non-standard install path. Because Sumo's parse operator extracts fields from raw Sysmon XML/text, field names and delimiters should be validated against the actual log-forwarder format in use before deployment. Correlate with a network-log source (e.g. a proxy or NGFW category) for tunnels.api.visualstudio.com/vscode.dev egress to reproduce the relay-egress arm.

Data Sources

Sysmon Event ID 1 (Process Creation) forwarded to Sumo LogicSumo Logic Cloud SIEMProxy/NGFW logs (companion relay-egress arm)

Required Tables

_sourceCategory=endpoint/sysmon

False Positives & Tuning

  • Developers running an approved VS Code Remote Tunnels session
  • Build agents staging a portable VS Code CLI outside the standard install path as part of pipeline tooling
  • Parsing mismatches from Sysmon log-forwarder format differences producing incomplete field extraction

Other platforms for THREAT-C2-VSCodeTunnelGitHubDeviceCodeC2


Testing Methodology

Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1VS Code CLI Headless Tunnel Bootstrap (Windows, CLI Arm)

    Expected signal: Sysmon Event ID 1 / MDE DeviceProcessEvents: a process creation record for code.exe or code-tunnel.exe with a command line containing 'tunnel' and '--accept-server-license-terms', launched from the standard VS Code install path.

  2. Test 2VS Code CLI Execution from Non-Standard Install Path (Windows, Path Arm)

    Expected signal: Sysmon Event ID 1 / MDE DeviceProcessEvents: a process creation record for code-tunnel.exe running from %TEMP%, with a command line containing 'tunnel', and PowerShell as the parent process.

  3. Test 3VS Code CLI Tunnel Bootstrap via Portable Linux Binary (Linux, Cross-Platform Coverage)

    Expected signal: Process telemetry (auditd or MDE for Linux) for the 'code' CLI binary executing from /tmp with a command line containing 'tunnel' and '--accept-server-license-terms'. Network telemetry: outbound TLS connections to tunnels.api.visualstudio.com during device-code registration.


Response Playbook

Triage

  1. Establish which arms fired for the device. CLI execution plus a non-standard install path is close to conclusive on its own — a portable or relocated VS Code CLI has essentially no legitimate reason to exist outside Program Files or the per-user Programs directory.
  2. Identify the parent process of the tunnel CLI. An interactive shell or Explorer parent launched by the logged-on user suggests a developer's own session; a service, scheduled task, or an unsigned/unusual parent suggests the binary was placed and triggered by another payload.
  3. Check whether the account has a documented reason to use VS Code Remote Tunnels — is this a developer machine with an active IDE license, or a server/domain controller with no development purpose?
  4. Review the full command line for the device-code login flow (`tunnel user login`) versus a pre-authenticated headless session (`--accept-server-license-terms` with no login prompt) — the latter indicates the tunnel was likely pre-provisioned by the operator rather than approved interactively at the time of the incident.
  5. Correlate the timestamp of tunnel bootstrap with any preceding delivery activity on the host (phishing attachment execution, ingress tool transfer, or exploitation of an internet-facing service), since Operation Digital Eye deployed the tunnel CLI as a post-exploitation step rather than a first-stage payload.
  6. Pull subsequent process and file telemetry for the host after the tunnel session starts, looking for interactive command execution, credential access, or file staging consistent with the tunnel being used as a remote shell rather than for code editing.

Containment

  1. Isolate the endpoint via EDR network isolation and terminate the tunnel CLI process; a relay-domain block alone will not sever the channel since it rides on Microsoft's own trusted infrastructure.
  2. Revoke the GitHub or Microsoft account device-code session tied to the tunnel from the identity provider's active-sessions console, so the operator's browser-side access is cut even if the endpoint process survives.
  3. If the binary was found in a non-standard path, preserve and then remove it, and check for a persistence mechanism (scheduled task, Run key, or service) configured to relaunch the tunnel at startup, which Operation Digital Eye used to survive reboots.
  4. Where the organisation has no legitimate developer-tunnel use case for the affected host population, block `code tunnel` execution via WDAC/AppLocker command-line rules or an EDR execution policy, since blocking the code.exe binary itself would also break unrelated legitimate VS Code use.
  5. Rotate any credentials the account used during the tunnel session window, since an interactive shell delivered over the tunnel can capture keystrokes or clipboard content for other applications on the host.

Evidence Collection

  1. Full Sysmon/MDE process history for the tunnel binary and its parent and child processes across the entire suspected activity window, not only the alerting window, since the CLI may have bootstrapped hours before the scored threshold was reached.
  2. The VS Code CLI's local tunnel state directory (`%USERPROFILE%\.vscode\cli\serve-web` or `%USERPROFILE%\.vscode-tunnel` depending on version), which retains the tunnel name, the authenticated account, and session history.
  3. DeviceNetworkEvents/Sysmon Event ID 3 records for all connections from the tunnel binary, and any corroborating proxy or firewall logs for vscode.dev and the tunnels.api.visualstudio.com relay.
  4. Prefetch, ShimCache, Amcache, and file-creation telemetry for the tunnel binary itself, to establish whether it arrived via a full VS Code installer (benign) or was dropped or copied in isolation (consistent with adversary staging).
  5. Any scheduled task, Run key, or service configured to relaunch the tunnel CLI, which indicates the operator intended persistent rather than one-off access.

Escalation Criteria

  • !The tunnel CLI is running from a non-standard install path with no accompanying full VS Code installation on the host — this matches the Operation Digital Eye pattern directly and should go straight to incident response.
  • !The tunnel was launched by a service, scheduled task, or unsigned parent process rather than an interactively logged-on developer.
  • !The host is a server, domain controller, or jump host with no legitimate development purpose.
  • !Interactive command execution, credential access, or file staging is observed on the host in the same window as the tunnel session, indicating the channel is being used as a remote shell.
  • !The same tunnel-abuse pattern appears on multiple hosts in a short window, indicating tooling deployed by an operator rather than independent developer choice.

Investigation Guide

Related Techniques

Forensic Artifacts

  • >VS Code CLI tunnel state directory (`%USERPROFILE%\.vscode\cli\serve-web` or `%USERPROFILE%\.vscode-tunnel`), retaining tunnel name, authenticated identity, and session timestamps
  • >Process command line for the tunnel CLI, which records whether the session used an interactive device-code login or a pre-authenticated headless bootstrap
  • >Sysmon Event ID 1 / MDE DeviceProcessEvents for the CLI binary's FolderPath, the single strongest indicator when it falls outside the two standard VS Code install directories
  • >Sysmon Event ID 3 / MDE DeviceNetworkEvents for outbound sessions to tunnels.api.visualstudio.com and vscode.dev from the CLI process
  • >Sysmon Event ID 22 DNS query records for the tunnel relay domain, useful when network-layer connection logging is unavailable
  • >Scheduled task, Run key, or service artifacts referencing the tunnel binary, indicating an operator-configured persistence mechanism

Tuning Guidance

Build the authorised-tunnel-user allowlist first, before enabling this in alerting mode. Run the fleet-wide tunnel-bootstrap hunt over 30 days, identify every developer and host with a documented reason to use VS Code Remote Tunnels, and exclude those DeviceIds (not accounts or subnets, since the justification attaches to the host's role). After that exclusion, the non-standard-path arm is by far the highest-value signal and should be weighted accordingly — consider alerting on that arm alone once corroborated by any CLI execution, rather than waiting for the full two-of-three score, since a relocated VS Code CLI binary has essentially no legitimate reason to exist. Do not rely on domain or IP blocking of tunnels.api.visualstudio.com or vscode.dev as a primary control: both are Microsoft-owned and widely allowlisted by default in most secure-web-gateway and proxy configurations, which is precisely why this channel is attractive to an operator in the first place. If your environment has no legitimate developer-tunnel use case at all, the most durable control is an application-control command-line rule blocking the `tunnel` subcommand for the VS Code CLI family, since blocking the code.exe binary itself would also break ordinary, unrelated VS Code editing use.


Hunting Queries

Fleet-wide baseline hunt for any VS Code tunnel invocation over 30 days, independent of install path or network signal. Because tunnel usage is a legitimate but comparatively uncommon developer feature, the output is normally small enough to review host by host and is the correct starting point for building an allowlist of developers and hosts with a documented business reason to use it before enabling this detection in alerting mode.

Hunting — KQL
kql
// Hunt: any VS Code CLI tunnel bootstrap fleet-wide, regardless of install path or network activity
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName in~ ("code.exe", "code-tunnel.exe", "code-insiders.exe", "code", "code-tunnel")
| where ProcessCommandLine has "tunnel"
| summarize ExecutionCount = count(), Paths = make_set(FolderPath, 10), Parents = make_set(InitiatingProcessFileName, 10), Users = make_set(AccountName, 10), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceName, FileName
| sort by FirstSeen asc
Hunting — SPL
spl
index=windows sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
| eval ImageLower=lower(coalesce(Image, "unknown"))
| eval CmdLower=lower(coalesce(CommandLine, ""))
| where (like(ImageLower, "%\\code.exe") OR like(ImageLower, "%\\code-tunnel.exe") OR like(ImageLower, "%\\code-insiders.exe")) AND like(CmdLower, "%tunnel%")
| stats count as ExecutionCount, values(ImageLower) as Paths, values(ParentImage) as Parents, values(User) as Users, min(_time) as FirstSeen, max(_time) as LastSeen by ComputerName
| eval FirstSeen=strftime(FirstSeen, "%Y-%m-%d %H:%M:%S"), LastSeen=strftime(LastSeen, "%Y-%m-%d %H:%M:%S")
| sort FirstSeen

Hunts specifically for the strongest single indicator in this technique — a VS Code CLI binary executing outside both standard install directories. A legitimate VS Code installation almost never runs its CLI from anywhere else, so this hunt typically returns a very small, high-signal result set and is the fastest way to find the Operation Digital Eye pattern independent of the two-arm scoring threshold used in the alerting rule.

Hunting — KQL
kql
// Hunt: VS Code CLI binaries running from a non-standard install path anywhere in the fleet
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName in~ ("code.exe", "code-tunnel.exe", "code-insiders.exe", "code", "code-tunnel")
| where not(FolderPath has_any (@"Program Files\Microsoft VS Code", @"AppData\Local\Programs\Microsoft VS Code"))
| summarize ExecutionCount = count(), Paths = make_set(FolderPath, 10), Parents = make_set(InitiatingProcessFileName, 10), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by DeviceName, FileName
| sort by FirstSeen asc
Hunting — SPL
spl
index=windows sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
| eval ImageLower=lower(coalesce(Image, "unknown"))
| where (like(ImageLower, "%\\code.exe") OR like(ImageLower, "%\\code-tunnel.exe") OR like(ImageLower, "%\\code-insiders.exe")) AND NOT (like(ImageLower, "%program files\\microsoft vs code%") OR like(ImageLower, "%appdata\\local\\programs\\microsoft vs code%"))
| stats count as ExecutionCount, values(ImageLower) as Paths, values(ParentImage) as Parents, min(_time) as FirstSeen, max(_time) as LastSeen by ComputerName
| eval FirstSeen=strftime(FirstSeen, "%Y-%m-%d %H:%M:%S"), LastSeen=strftime(LastSeen, "%Y-%m-%d %H:%M:%S")
| sort FirstSeen

Atomic Red Team Tests

Test 1 VS Code CLI Headless Tunnel Bootstrap (Windows, CLI Arm)
windows

Invokes the VS Code CLI tunnel subcommand with non-interactive license acceptance, the same flag pattern observed in Operation Digital Eye, from a standard install path. This exercises the CLI-execution arm on its own and should not, by itself, cross the two-arm alert threshold — pair it with the non-standard-path test below on the same host to validate the full alert path.

Command

powershell
$code = Get-Command code -ErrorAction SilentlyContinue
if (-not $code) { Write-Host 'VS Code CLI not installed on this host; skipping'; exit 0 }
Start-Process -FilePath $code.Source -ArgumentList 'tunnel', '--accept-server-license-terms', '--name', 'atomic-t1219-001-test' -NoNewWindow
Start-Sleep -Seconds 20
Write-Host 'VS Code tunnel CLI bootstrap test complete'

Cleanup

powershell
Get-Process -Name code-tunnel -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
Get-Process -Name code -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like '*tunnel*' } | Stop-Process -Force -ErrorAction SilentlyContinue

Expected Telemetry

Sysmon Event ID 1 / MDE DeviceProcessEvents: a process creation record for code.exe or code-tunnel.exe with a command line containing 'tunnel' and '--accept-server-license-terms', launched from the standard VS Code install path.

Expected Detection

The TunnelCliExecution arm sets to true, giving TunnelScore = 1 in isolation from a standard path. This is below the TunnelScore >= 2 alert threshold by design; validate this test against the fleet-wide tunnel-bootstrap hunting query rather than the alert rule unless paired with a second arm.

Test 2 VS Code CLI Execution from Non-Standard Install Path (Windows, Path Arm)
windows

Copies the VS Code CLI binary (or, where VS Code is not installed, a benign signed system binary renamed to code-tunnel.exe for isolated arm testing) into a Temp directory and executes it with a tunnel-style command line, without generating network traffic. This exercises the non-standard-path arm — the discriminator that caught Operation Digital Eye — safely on a corporate endpoint.

Command

powershell
$code = Get-Command code -ErrorAction SilentlyContinue
$dest = "$env:TEMP\code-tunnel.exe"
if ($code) { Copy-Item -Path $code.Source -Destination $dest -Force } else { Copy-Item -Path "$env:SystemRoot\System32\whoami.exe" -Destination $dest -Force }
Start-Process -FilePath $dest -ArgumentList 'tunnel', '--help' -NoNewWindow -Wait
Write-Host 'Non-standard-path tunnel CLI test complete'

Cleanup

powershell
Remove-Item -Path "$env:TEMP\code-tunnel.exe" -Force -ErrorAction SilentlyContinue

Expected Telemetry

Sysmon Event ID 1 / MDE DeviceProcessEvents: a process creation record for code-tunnel.exe running from %TEMP%, with a command line containing 'tunnel', and PowerShell as the parent process.

Expected Detection

The NonStandardTunnelPath arm sets to true, giving TunnelScore = 1 in isolation. Run this in the same 24-hour window as the CLI bootstrap test above on the same host so the two arms agree and the detection crosses the TunnelScore >= 2 threshold and fires.

Test 3 VS Code CLI Tunnel Bootstrap via Portable Linux Binary (Linux, Cross-Platform Coverage)
linux

Downloads the official portable VS Code CLI for Linux to a non-standard directory and runs it with the tunnel license-acceptance flag, reproducing the same abuse pattern on a Linux endpoint. Run only on an authorised lab host with outbound access to code.visualstudio.com.

Command

bash
mkdir -p /tmp/atomic-t1219-001 && cd /tmp/atomic-t1219-001
curl -sL 'https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64' --output vscode_cli.tar.gz
tar -xzf vscode_cli.tar.gz
timeout 20 ./code tunnel --accept-server-license-terms --name atomic-t1219-001-test || true
echo 'Linux VS Code tunnel CLI bootstrap test complete'

Cleanup

bash
pkill -f 'atomic-t1219-001-test' 2>/dev/null; rm -rf /tmp/atomic-t1219-001

Expected Telemetry

Process telemetry (auditd or MDE for Linux) for the 'code' CLI binary executing from /tmp with a command line containing 'tunnel' and '--accept-server-license-terms'. Network telemetry: outbound TLS connections to tunnels.api.visualstudio.com during device-code registration.

Expected Detection

Both the TunnelCliExecution arm and the NonStandardTunnelPath arm should fire for the host (the binary runs from /tmp, well outside any standard install location), giving TunnelScore >= 2 and triggering the alert without needing a corroborating relay-egress signal.

Related Detections