Multiple Credential Access by VS Code or Cursor IDE Descendant


Description

Detects a process descended from Visual Studio Code or Cursor opening at least two distinct sensitive files, such as browser credential stores, cloud tokens, or SSH and package-manager secrets. Malicious IDE extensions use the editor process tree to harvest more than one credential store in a single session.

Query · esql

FROM logs-endpoint.events.process-*, logs-endpoint.events.file-*
  METADATA _id, _index, _version

// Normalize sensitive filenames for case-insensitive matching.
| EVAL sensitive_name = TO_LOWER(file.name)

// Identify processes launched by VS Code or Cursor.
| EVAL is_editor_spawn =
    event.dataset == "endpoint.events.process"
    AND process.parent.name IN (
      "Code",
      "Code.exe",
      "Code Helper",
      "Code Helper (Plugin)",
      "code",
      "Cursor",
      "Cursor.exe",
      "Cursor Helper",
      "Cursor Helper (Plugin)",
      "cursor"
    )

// Identify sensitive file-open events.
| EVAL is_sensitive_read =
    event.dataset == "endpoint.events.file"
    AND MV_CONTAINS(event.action, "open")
    AND sensitive_name IN (
      "logins.json", "login data", "local state",
      "signons.sqlite", "cookies", "cookies.sqlite",
      "cookies.binarycookies", "token",
      "credentials.db", "credentials", "credentials.toml",
      "access_tokens.db", "accesstokens.json",
      "azureprofile.json", "rdcman.settings",
      "keepass.config.xml", ".npmrc", "auth.json", ".vault-token",
      "application_default_credentials.json"
    )

// Retain editor-associated process events and sensitive file opens.
| WHERE host.id IS NOT NULL
    AND (is_editor_spawn OR is_sensitive_read)

// Capture editor parent entity IDs as ancestry anchors.
| EVAL editor_anchor_id = CASE(
    is_editor_spawn, process.parent.entity_id, NULL
  )

// Share editor ancestor IDs within each host.
| INLINE STATS
    editor_anchor_ids = VALUES(editor_anchor_id)
  BY host.id

// Retain sensitive file opens with valid process identity and ancestry.
| WHERE is_sensitive_read
    AND process.Ext.ancestry IS NOT NULL
    AND process.entity_id IS NOT NULL

// Match file-opening processes to VS Code or Cursor ancestors.
| EVAL editor_ancestor_ids = MV_INTERSECTION(
    editor_anchor_ids, process.Ext.ancestry
  )
| WHERE editor_ancestor_ids IS NOT NULL

// Require at least 2 distinct sensitive filenames per process.
| INLINE STATS
    unique_sensitive_files = COUNT_DISTINCT(sensitive_name),
    Esql.file_names = VALUES(sensitive_name)
  BY host.id, process.entity_id

| WHERE unique_sensitive_files >= 2

// Preserve event-level investigation context.
| KEEP
    @timestamp,
    host.id, host.name, host.os.type,
    user.name,
    event.action, event.outcome,
    file.name, Esql.file_names, file.path,
    unique_sensitive_files,
    process.entity_id, process.name, process.executable,
    editor_ancestor_ids,
    _id, _index, _version

Investigation fields

Pivot points the source recommends for triage.

  • host.name
  • host.os.type
  • user.name
  • process.name
  • process.executable
  • file.name
  • file.path
  • Esql.file_names

Implementation guide

This rule is designed for data generated by Elastic Defend, which provides native endpoint detection and response, along with event enrichments designed to work with our detection rules.

Setup instructions: https://ela.st/install-elastic-defend

Analyst notes

Investigating Multiple Credential Access by VS Code or Cursor IDE Descendant

A process whose ancestry includes Visual Studio Code or Cursor opened at least two different sensitive files. The files include browser stores (logins.json, Login Data, Cookies), cloud and token files, and SSH or package-manager secrets. One such file can be normal for a language server or CLI. Two or more in the same process is credential harvesting behavior associated with a malicious extension or a prompt-injected agent.

Possible investigation steps

  • Review Esql.file_names and file.path for which stores were opened and whether the paths sit under a browser profile, .aws, .ssh, .azure, or an npm/vault config directory.
  • Review process.executable, process.command_line, and process.parent.command_line for extensionHost or a path under .vscode/extensions or .cursor/extensions.
  • Identify the extension id from that path and compare it with the installed extension.
  • On the same host and user, look for a preceding VSIX install, a suspicious shell spawned by Code.exe or Cursor, and outbound connections from the same process.

False positive analysis

  • Developer tools launched by the IDE can read known_hosts and .npmrc, or both credentials and credentials.toml, during normal git, npm, or cloud CLI use. Confirm the process path and the two file names before excluding.
  • Scope exceptions to a known process executable, extension path, host, or user. Do not broadly exclude Code.exe or Cursor.

Response and remediation

  • If the access is unapproved, stop the process, uninstall the extension, and disconnect the host from networks that hold the exposed accounts.
  • Rotate the credentials in the opened files, including browser passwords, cloud tokens, SSH keys, and npm or vault tokens.
  • Hunt Esql.file_names and the process executable on other hosts.
Raw source Multiple Credential Access by VS Code or Cursor IDE Descendant · Elastic TOML
Esc
Published by elastic/detection-rules ↗, licensed under Elastic License 2.0 ↗. Reproduced here unmodified.
[metadata]
creation_date = "2026/10/26"
integration = ["endpoint"]
maturity = "production"
min_stack_comments = "ES|QL inline stats became generally available in 9.3.0 and MV_INTERSECTION is in preview since 9.3."
min_stack_version = "9.3.0"
updated_date = "2026/10/26"

[rule]
author = ["Elastic"]
description = """
Detects a process descended from Visual Studio Code or Cursor opening at least two distinct sensitive files, such as
browser credential stores, cloud tokens, or SSH and package-manager secrets. Malicious IDE extensions use the editor
process tree to harvest more than one credential store in a single session.
"""
from = "now-9m"
language = "esql"
license = "Elastic License v2"
name = "Multiple Credential Access by VS Code or Cursor IDE Descendant"
note = """## Triage and analysis

### Investigating Multiple Credential Access by VS Code or Cursor IDE Descendant

A process whose ancestry includes Visual Studio Code or Cursor opened at least two different sensitive files. The files
include browser stores (`logins.json`, `Login Data`, `Cookies`), cloud and token files, and SSH or package-manager
secrets. One such file can be normal for a language server or CLI. Two or more in the same process is credential
harvesting behavior associated with a malicious extension or a prompt-injected agent.

### Possible investigation steps

- Review `Esql.file_names` and `file.path` for which stores were opened and whether the paths sit under a browser profile, `.aws`, `.ssh`, `.azure`, or an npm/vault config directory.
- Review `process.executable`, `process.command_line`, and `process.parent.command_line` for `extensionHost` or a path under `.vscode/extensions` or `.cursor/extensions`.
- Identify the extension id from that path and compare it with the installed extension.
- On the same host and user, look for a preceding VSIX install, a suspicious shell spawned by `Code.exe` or `Cursor`, and outbound connections from the same process.

### False positive analysis

- Developer tools launched by the IDE can read `known_hosts` and `.npmrc`, or both `credentials` and `credentials.toml`, during normal git, npm, or cloud CLI use. Confirm the process path and the two file names before excluding.
- Scope exceptions to a known process executable, extension path, host, or user. Do not broadly exclude `Code.exe` or `Cursor`.

### Response and remediation

- If the access is unapproved, stop the process, uninstall the extension, and disconnect the host from networks that hold the exposed accounts.
- Rotate the credentials in the opened files, including browser passwords, cloud tokens, SSH keys, and npm or vault tokens.
- Hunt `Esql.file_names` and the process executable on other hosts.
"""
references = [
    "https://www.mdsec.co.uk/2021/01/macos-post-exploitation-shenanigans-with-vscode-extensions/",
]
risk_score = 73
rule_id = "f27228e4-0792-4681-a965-9937ed6e4e00"
setup = """## Setup

This rule is designed for data generated by [Elastic Defend](https://www.elastic.co/security/endpoint-security), which provides native endpoint detection and response, along with event enrichments designed to work with our detection rules.

Setup instructions: https://ela.st/install-elastic-defend
"""
severity = "high"
tags = [
    "Domain: Endpoint",
    "OS: Windows",
    "OS: macOS",
    "Use Case: Threat Detection",
    "Tactic: Credential Access",
    "Tactic: Persistence",
    "Data Source: Elastic Defend",
    "Resources: Investigation Guide",
    "Rule Type: ES|QL",
    "Platform: Windows",
    "Platform: macOS",
]
timestamp_override = "event.ingested"
type = "esql"

query = '''
FROM logs-endpoint.events.process-*, logs-endpoint.events.file-*
  METADATA _id, _index, _version

// Normalize sensitive filenames for case-insensitive matching.
| EVAL sensitive_name = TO_LOWER(file.name)

// Identify processes launched by VS Code or Cursor.
| EVAL is_editor_spawn =
    event.dataset == "endpoint.events.process"
    AND process.parent.name IN (
      "Code",
      "Code.exe",
      "Code Helper",
      "Code Helper (Plugin)",
      "code",
      "Cursor",
      "Cursor.exe",
      "Cursor Helper",
      "Cursor Helper (Plugin)",
      "cursor"
    )

// Identify sensitive file-open events.
| EVAL is_sensitive_read =
    event.dataset == "endpoint.events.file"
    AND MV_CONTAINS(event.action, "open")
    AND sensitive_name IN (
      "logins.json", "login data", "local state",
      "signons.sqlite", "cookies", "cookies.sqlite",
      "cookies.binarycookies", "token",
      "credentials.db", "credentials", "credentials.toml",
      "access_tokens.db", "accesstokens.json",
      "azureprofile.json", "rdcman.settings",
      "keepass.config.xml", ".npmrc", "auth.json", ".vault-token",
      "application_default_credentials.json"
    )

// Retain editor-associated process events and sensitive file opens.
| WHERE host.id IS NOT NULL
    AND (is_editor_spawn OR is_sensitive_read)

// Capture editor parent entity IDs as ancestry anchors.
| EVAL editor_anchor_id = CASE(
    is_editor_spawn, process.parent.entity_id, NULL
  )

// Share editor ancestor IDs within each host.
| INLINE STATS
    editor_anchor_ids = VALUES(editor_anchor_id)
  BY host.id

// Retain sensitive file opens with valid process identity and ancestry.
| WHERE is_sensitive_read
    AND process.Ext.ancestry IS NOT NULL
    AND process.entity_id IS NOT NULL

// Match file-opening processes to VS Code or Cursor ancestors.
| EVAL editor_ancestor_ids = MV_INTERSECTION(
    editor_anchor_ids, process.Ext.ancestry
  )
| WHERE editor_ancestor_ids IS NOT NULL

// Require at least 2 distinct sensitive filenames per process.
| INLINE STATS
    unique_sensitive_files = COUNT_DISTINCT(sensitive_name),
    Esql.file_names = VALUES(sensitive_name)
  BY host.id, process.entity_id

| WHERE unique_sensitive_files >= 2

// Preserve event-level investigation context.
| KEEP
    @timestamp,
    host.id, host.name, host.os.type,
    user.name,
    event.action, event.outcome,
    file.name, Esql.file_names, file.path,
    unique_sensitive_files,
    process.entity_id, process.name, process.executable,
    editor_ancestor_ids,
    _id, _index, _version
'''

[rule.investigation_fields]
field_names = [
    "host.name",
    "host.os.type",
    "user.name",
    "process.name",
    "process.executable",
    "file.name",
    "file.path",
	"Esql.file_names"
]

[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1552"
name = "Unsecured Credentials"
reference = "https://attack.mitre.org/techniques/T1552/"

[[rule.threat.technique.subtechnique]]
id = "T1552.001"
name = "Credentials In Files"
reference = "https://attack.mitre.org/techniques/T1552/001/"

[[rule.threat.technique]]
id = "T1555"
name = "Credentials from Password Stores"
reference = "https://attack.mitre.org/techniques/T1555/"

[[rule.threat.technique.subtechnique]]
id = "T1555.003"
name = "Credentials from Web Browsers"
reference = "https://attack.mitre.org/techniques/T1555/003/"

[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"

[[rule.threat]]
framework = "MITRE ATT&CK"

[[rule.threat.technique]]
id = "T1176"
name = "Software Extensions"
reference = "https://attack.mitre.org/techniques/T1176/"

[[rule.threat.technique.subtechnique]]
id = "T1176.002"
name = "IDE Extensions"
reference = "https://attack.mitre.org/techniques/T1176/002/"

[rule.threat.tactic]
id = "TA0003"
name = "Persistence"
reference = "https://attack.mitre.org/tactics/TA0003/"

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.