Detect Command and Control via VS Code Remote Tunnel GitHub Device Code Authentication in Google Chronicle
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
YARA-L Detection Query
rule vscode_tunnel_github_device_code_c2 {
meta:
author = "Detection Engineering"
description = "Detects VS Code remote tunnel CLI bootstrap combined with a binary running outside the standard install path, consistent with Operation Digital Eye's C2 abuse of IDE tunneling."
mitre_attack_tactic = "Command and Control"
mitre_attack_technique = "T1219.001"
severity = "HIGH"
priority = "HIGH"
events:
$e.metadata.event_type = "PROCESS_LAUNCH"
$e.target.process.file.full_path = /(?i)\\(code|code-tunnel|code-insiders)\.exe$/
$e.target.process.command_line = /(?i)tunnel.*(accept-server-license-terms|user login|serve-web)/
not $e.target.process.file.full_path = /(?i)Program Files\\Microsoft VS Code/
not $e.target.process.file.full_path = /(?i)AppData\\Local\\Programs\\Microsoft VS Code/
$host = $e.principal.hostname
match:
$host over 1h
outcome:
$tunnel_events = count($e.metadata.id)
condition:
$e and $tunnel_events >= 1
} Chronicle YARA-L 2.0 rule matching PROCESS_LAUNCH UDM events for a VS Code CLI binary invoked with a tunnel-bootstrap command line whose file path falls outside both standard VS Code install directories — combining the CLI-execution and non-standard-path arms into a single high-fidelity rule, since Chronicle UDM process events carry the full path natively. Pair with a NETWORK_CONNECTION or DNS rule for tunnels.api.visualstudio.com/vscode.dev to additionally corroborate the relay-egress arm when network telemetry is available.
Data Sources
Required Tables
False Positives & Tuning
- Developers or build hosts with a portable VS Code CLI staged outside the standard install path as part of an approved workflow
- CI/CD pipelines invoking a cached VS Code CLI binary from a workspace directory
- Endpoint agents that report a non-standard install path due to symlinks or per-user portable installs
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.
- 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.
- 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.
- 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.
References (8)
- https://attack.mitre.org/techniques/T1219/001/
- https://attack.mitre.org/tactics/TA0011/
- https://www.sentinelone.com/labs/operation-digital-eye-chinese-apt-compromises-southern-european-it-service-provider/
- https://code.visualstudio.com/docs/remote/tunnels
- https://code.visualstudio.com/docs/editor/command-line
- https://github.com/redcanaryco/atomic-red-team/tree/master/atomics/T1219.001
- https://learn.microsoft.com/en-us/defender-xdr/advanced-hunting-devicenetworkevents-table
- https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon
Response Playbook
Triage
- 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.
- 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.
- 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?
- 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.
- 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.
- 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
- 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.
- 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.
- 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.
- 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.
- 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
- 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.
- 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.
- 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.
- 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).
- 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.
// 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 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.
// 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 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
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
$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
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.
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
$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
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.
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
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
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.