← All hunts high TLP:CLEAR Part 2 of 2

Node.js Backdoor and Lateral Movement

An intruder is using a portable Node.js runtime and an obfuscated implant staged in LocalAppData to move laterally via WinRM after initial social engineering via Microsoft Teams.

Based on research by Microsoft 2026-09-24 12 steps · 5 queries T1041 T1059.001 T1071 T1090.003 T1218.011 T1555 T1566.003

Brief

The IT Support Trap

The adversary exploits trust through Microsoft Teams to establish a foothold in the enterprise. In "Impersonating IT support: how threat actors turn a remote session into enterprise-wide access" (https://www.microsoft.com/en-us/security/blog/2026/09/02/impersonating-it-support-threat-actors-turn-remote-session-into-enterprise-wide-access/), Microsoft describes a campaign where actors masquerade as help desk personnel to gain remote access. Once a victim grants permission, the attacker deploys a Node.js-based backdoor to maintain a presence and expand throughout the network. This move bypasses many email-based security layers, making post-access hunting essential.

How the Hunt Flows

The hunt begins by identifying the staging of the runtime. The first phase scans process activity for Node.js or renamed binaries executing from the AppData folder. While legitimate developers use Node.js, a portable instance running from a non-technical user profile indicates highly suspicious staging activity.

Next, the hunt gathers persistence evidence in parallel. It checks registry Run keys that point to executable code in LocalAppData and searches for rare files with extensions like .tmp or .cfg in user profiles. This phase establishes a baseline of anomalous file activity on the suspected hosts and identifies the potential loader files used by the implant.

An analyst then triages these signals to confirm beachheads. Once identified, the hunt pivots to investigate the interactive stage of the attack. It filters outbound network connections on port 5985 to find WinRM-based lateral movement originating from the compromised machines. This identifies where the attacker is attempting to move next.

Finally, the hunt examines follow-on reconnaissance and execution. It looks for Active Directory discovery commands and rundll32.exe activity specifically associated with the flagged users and hosts. Narrowing the scope to these specific entities helps distinguish malicious operator activity from routine administrative tasks.

Blind Spots

This hunt relies on process and network telemetry. If the environment lacks outbound socket data, the hunt cannot track the attacker's movement from the beachhead to sensitive internal servers. Additionally, Node.js implants often run tasking in memory or delete temporary files immediately after execution. These ephemeral artifacts can hide the exact nature of the data stolen or the specific commands run during the operator's session.

In this series

Steps

  1. Scope Node.js in LocalAppData

    Query · scoping

    Identify hosts running Node.js or renamed copies from a user-writable path, indicating the staging of a portable runtime.

    reads hb_process_activitysql
    SELECT device_hostname, user_name, process_name, process_path, process_cmd_line, time FROM hb_process_activity WHERE (LOWER(process_path) LIKE '%\\appdata\\local\\%' OR LOWER(process_cmd_line) LIKE '%\\appdata\\local\\%') AND (LOWER(process_name) LIKE '%node%' OR LOWER(process_file_description) LIKE '%node.js%') AND time >= datetime('now', '-{{lookback_days}} days')

    What a hit looks like. Hosts where Node.js is running from a user profile directory. Silence suggests no portable Node.js runtime has been launched in this way.

  2. Run key persistence in LocalAppData

    Query · detection candidate

    Identify any registry Run key pointing to executable code within LocalAppData, which is a common persistence method for this implant.

    reads hb_registry_activitysql
    SELECT device_hostname, reg_target, reg_value_data, time FROM hb_registry_activity WHERE LOWER(reg_target) LIKE '%\\currentversion\\run%' AND LOWER(reg_value_data) LIKE '%\\appdata\\local\\%' AND time >= datetime('now', '-{{lookback_days}} days')

    What a hit looks like. A Run key pointing to a binary or script in a user's LocalAppData. This is a durable signal of staging.

  3. Rare files with non-standard extensions

    Query · baseline

    Find rare files in user profiles matching the report's extension list to identify encrypted payloads using a suffix-based filter.

    reads hb_file_activitysql
    SELECT device_hostname, file_path, file_name, COUNT(DISTINCT device_hostname) AS hosts, MIN(time) AS first_seen FROM hb_file_activity WHERE LOWER(file_path) LIKE '%\\appdata\\local\\%' AND instr(',' || '{{nonstandard_extensions}}' || ',', ',' || substr(LOWER(file_name), instr(LOWER(file_name), '.')) || ',') > 0 AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY file_path, file_name HAVING hosts <= 5

    What a hit looks like. Files with extensions like .tmp or .cfg appearing in a LocalAppData folder that are rare across the fleet. Silence proves these specific extensions were not used.

  4. Triage early implant staging

    Agent triage

    Consolidate process, registry, and file evidence to confirm which hosts and users are acting as beachheads.

  5. WinRM lateral movement (Port 5985)

    Query · triage

    Identify outbound WinRM connections from the beachheads. Populate scope_hosts with results from triage-early-footprint to automate the pivot.

    reads hb_network_connectionsql
    SELECT device_hostname, dst_endpoint_ip, dst_endpoint_port, time FROM hb_network_connection WHERE dst_endpoint_port = 5985 AND direction = 'outbound' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')

    What a hit looks like. Outbound connections to 5985 originating from a suspected beachhead host. This indicates an attempt to move laterally.

  6. Discovery and rundll32 payloads

    Query · enrichment

    Detect Active Directory discovery and follow-on rundll32 execution, scoped to the specific users and hosts flagged in the early triage to minimize administrative noise.

    reads hb_process_activitysql
    SELECT device_hostname, user_name, process_name, process_cmd_line, time FROM hb_process_activity WHERE (LOWER(process_cmd_line) LIKE '%adsi%' OR LOWER(process_cmd_line) LIKE '%get-ad%' OR LOWER(process_name) LIKE '%rundll32.exe%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND ('{{scope_users}}' = '' OR instr(',' || '{{scope_users}}' || ',', ',' || user_name || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')

    What a hit looks like. Process command lines performing domain enumeration or rundll32 loading actor-supplied DLLs, scoped to the flagged beachhead.

  7. Triage enterprise intrusion

    Agent triage

    Confirm the multi-stage breach by weighing the early staging evidence against the lateral movement results.

  8. Route based on breach scope

    Decision

    Direct response actions based on the confirmed scope of the lateral movement.

  9. Isolate compromised host

    Response action

    Sever the attacker's interactive session and stop further lateral movement.

  10. Analyze intrusion depth

    Analyst task

    Identify what the operator accessed after moving laterally.

  11. Hunt close-out

    Analyst task

    Document findings and negative results.

Coverage

Scenario coverage

StageCoveredHow, or why not
Node.js Implant Staging and Persistence
T1059.001
Yes scope-node-in-localappdata, run-key-localappdata, nonstandard-file-prevalence
C2 Communication and Reconnaissance
T1071 · T1041 · T1555
Yes recon-and-rundll32
Lateral Movement via WinRM
T1059.001
Yes winrm-lateral-movement
Follow-on Payload Execution
T1218.011
Yes recon-and-rundll32
IT Support Impersonation via Teams
T1566.003
Out of scope Belongs to another part of the 'Impersonating IT support: how threat actors turn a remote session into enterprise-wide access' series.
Remote Session and MSI Delivery
T1059.001
Out of scope Belongs to another part of the 'Impersonating IT support: how threat actors turn a remote session into enterprise-wide access' series.

Blind spots

  • Needs hb_network_connection with destination port. Without outbound socket data, the hunt cannot track the movement from the beachhead to sensitive identity servers. It would answer Did the actor pivot to other hosts via WinRM?.
  • Needs hb_file_activity with content capture. Node.js implants often execute tasking in memory or temporary files that are immediately deleted, hiding the specific data stolen or tools used. It would answer What specific tasks did the C2 provide?.

Parameters & data

Parameters

ParameterTypeDefaultWhat it is
lookback_daysnumber14Days of history to examine.
nonstandard_extensionslist[string].tmp, .ini, .dat, .bin, .cfgExtensions used for encrypted implants and loaders.
scope_hostslist[host]—Hostnames flagged in the early triage stage to narrow follow-on queries.
scope_userslist[string]—Usernames flagged in the early triage stage to narrow follow-on queries.

Telemetry

SourceCategoryTelemetry
Endpoint telemetry (hb_ surfaces)endpointendpoint
Network telemetrynetworknetwork

Source

Download hunt.md Definition (JSON) An open hunt.md file; it runs anywhere that reads the format.
---
analysis: 'A single rule for the EdgeUpdate key is easily bypassed by changing a string.
  This hunt identifies the structural behavior of the attack: staging a runtime in
  a user path, rare persistence mechanisms, and follow-on lateral movement that crosses
  network and process boundaries.'
blind_spots:
- id: no-network-telemetry
  question: Did the actor pivot to other hosts via WinRM?
  requires: hb_network_connection with destination port
  risk: Without outbound socket data, the hunt cannot track the movement from the
    beachhead to sensitive identity servers.
  stage: lateral-movement-winrm
- id: ephemeral-js-implants
  question: What specific tasks did the C2 provide?
  requires: hb_file_activity with content capture
  risk: Node.js implants often execute tasking in memory or temporary files that are
    immediately deleted, hiding the specific data stolen or tools used.
  stage: nodejs-implant-persistence
coverage:
- stage: nodejs-implant-persistence
  status: covered
  steps:
  - scope-node-in-localappdata
  - run-key-localappdata
  - nonstandard-file-prevalence
- stage: c2-recon-and-tasking
  status: covered
  steps:
  - recon-and-rundll32
- stage: lateral-movement-winrm
  status: covered
  steps:
  - winrm-lateral-movement
- stage: execution-rundll32-dlls
  status: covered
  steps:
  - recon-and-rundll32
- reason: 'Belongs to another part of the ''Impersonating IT support: how threat actors
    turn a remote session into enterprise-wide access'' series.'
  stage: initial-access-teams-vishing
  status: out_of_scope
- reason: 'Belongs to another part of the ''Impersonating IT support: how threat actors
    turn a remote session into enterprise-wide access'' series.'
  stage: remote-session-msi-delivery
  status: out_of_scope
guardrails:
  claims: no_unsupported
  evidence: citation_required
  missing_data: not_benign
  telemetry: untrusted
hunt:
  applicability: campaign-specific
  handoff: promote-to-detection
  justification: The abuse of Microsoft Teams for social engineering bypasses many
    email-based controls; detecting the resulting Node.js implant and subsequent WinRM
    pivoting is critical to preventing enterprise-wide compromise.
  methodology: model-assisted
  trigger: intel-report
hypothesis: An intruder is using a portable Node.js runtime and an obfuscated implant
  staged in LocalAppData to move laterally via WinRM after initial social engineering
  via Microsoft Teams.
labels:
- hunt
- attack.t1059.001
- attack.t1071
- attack.t1041
- attack.t1555
- attack.t1218.011
- attack.t1090.003
- attack.t1566.003
name: Node.js Backdoor and Lateral Movement
parameters:
  lookback_days:
    default: '14'
    description: Days of history to examine.
    type: number
  nonstandard_extensions:
    default:
    - .tmp
    - .ini
    - .dat
    - .bin
    - .cfg
    description: Extensions used for encrypted implants and loaders.
    type: list[string]
  scope_hosts:
    default: []
    description: Hostnames flagged in the early triage stage to narrow follow-on queries.
    type: list[host]
  scope_users:
    default: []
    description: Usernames flagged in the early triage stage to narrow follow-on queries.
    type: list[string]
provenance:
  authors:
  - name: Huntbase hunt generation
    org: huntbase.io
  generated:
    by: huntbase-hunt-generation
    from: https://www.microsoft.com/en-us/security/blog/2026/09/02/impersonating-it-support-threat-actors-turn-remote-session-into-enterprise-wide-access/
    gates:
    - dry-run
    - lint
    model: hb_google/gemini-3-flash-preview
rationale: Focus on workstations without developer roles first, as Node.js in LocalAppData
  is highly anomalous there. Use the early triage hosts and users to populate the
  scoping parameters for follow-on queries.
references:
- name: "Microsoft Security Blog \u2014 Impersonating IT support: how threat actors\
    \ turn a remote session into enterprise-wide access"
  url: https://www.microsoft.com/en-us/security/blog/2026/09/02/impersonating-it-support-threat-actors-turn-remote-session-into-enterprise-wide-access/
related:
- hunt: initial-access-teams-vishing
  reason: That hunt covers the Teams ingress and social engineering; this hunt focuses
    on the post-access technical footprint.
  relation: out-of-scope-alternative
- hunt: it-support-impersonation-remote-access
  relation: follows
scenario:
  stages:
  - name: IT Support Impersonation via Teams
    observables:
    - Microsoft Teams external tenant collaboration
    - Accept/Block prompts in Teams
    - Quick Assist connection code usage
    - 'Lures: ''Microsoft Security Update'', ''Spam Filter Update'', ''Account Verification'''
    - Vishing (voice phishing) used to layer trust
    slug: initial-access-teams-vishing
    tactic: initial-access
    techniques:
    - T1566.003
  - name: Remote Session and MSI Delivery
    observables:
    - Quick Assist or remote support tool process tree
    - PowerShell downloading MSI from cloud storage
    - msiexec.exe /qn (silent installation)
    - 'MSI filenames: ''devfix.msi'', ''Hotfix.msi'''
    slug: remote-session-msi-delivery
    tactic: execution
    techniques:
    - T1059.001
  - name: Node.js Implant Staging and Persistence
    observables:
    - Portable Node.js runtime downloaded from official distribution
    - Files staged in LocalAppData randomly named directories
    - 'Nonstandard file extensions: .tmp, .ini, .dat, .bin, .cfg'
    - HKCU Run key 'EdgeUpdate'
    - Startup folder shortcut 'EdgeUpdate.lnk'
    - Renamed Node.js binaries with original metadata 'node.exe'
    slug: nodejs-implant-persistence
    tactic: persistence
    techniques:
    - T1059.001
  - name: C2 Communication and Reconnaissance
    observables:
    - Randomized HTTPS long-polling to C2 server
    - Discovery of antivirus products and virtualization
    - ADSI (Active Directory Service Interfaces) queries
    - Screen captures encoded in Base64 and saved to temporary files
    - Host hardware and locale enumeration
    slug: c2-recon-and-tasking
    tactic: command-and-control
    techniques:
    - T1071
    - T1041
    - T1555
  - name: Lateral Movement via WinRM
    observables:
    - WinRM connections over TCP port 5985
    - Pivoting toward Domain Controllers and Certificate Authorities
    - Native Windows Remote Management execution
    slug: lateral-movement-winrm
    tactic: lateral-movement
    techniques:
    - T1059.001
  - name: Follow-on Payload Execution
    observables:
    - rundll32.exe loading threat actor-supplied DLLs
    - Short-lived cmd.exe and PowerShell child processes of Node.js
    slug: execution-rundll32-dlls
    tactic: defense-evasion
    techniques:
    - T1218.011
  summary: A human-operated campaign impersonates IT support via Microsoft Teams to
    trick users into granting remote access through tools like Quick Assist. Once
    access is established, the attackers deploy a persistent Node.js-based implant
    to perform extensive reconnaissance and move laterally via WinRM toward high-value
    infrastructure like domain controllers.
series:
  index: 2
  slug: impersonating-it-support-how-threat-actors-turn-a-remote-session-into-enterprise-wide-access
  title: 'Impersonating IT support: how threat actors turn a remote session into enterprise-wide
    access'
  total: 2
severity: high
targets:
  analyst:
    name: Tier-2 analyst
    role: analyst
  endpoint:
    category: endpoint
    name: Endpoint telemetry (hb_ surfaces)
    telemetry:
    - endpoint
  hunter:
    agent: true
    name: Hunt agent
  network:
    category: network
    name: Network telemetry
    telemetry:
    - network
tlp: clear
type: investigation
---


# Node.js Backdoor and Lateral Movement

This hunt targets the technical footprint of a human-operated intrusion campaign that deploys a Node.js-based implant. It identifies the staging of a portable Node.js runtime, non-standard file extensions for loaders, and per-user registry persistence. The hunt then pivots to investigate follow-on reconnaissance and lateral movement over WinRM (port 5985), focusing on activity originating from the compromised beachhead. By examining the process tree and network connections together, the hunt distinguishes legitimate administrative work from malicious interactive tasking.

## scope-node-in-localappdata
<!-- Scope Node.js in LocalAppData -->
Identify hosts running Node.js or renamed copies from a user-writable path, indicating the staging of a portable runtime.

```sqlite target=endpoint role=scoping params=(lookback_days=lookback_days)
~~~yaml
expected: Hosts where Node.js is running from a user profile directory. Silence suggests
  no portable Node.js runtime has been launched in this way.
reads:
- device_hostname
- user_name
- process_name
- process_path
- process_cmd_line
- time
silence: not_evidence_of_absence
source: hb_process_activity
verified: dry-run
verified_at: '2026-09-24'
~~~
SELECT device_hostname, user_name, process_name, process_path, process_cmd_line, time FROM hb_process_activity WHERE (LOWER(process_path) LIKE '%\\appdata\\local\\%' OR LOWER(process_cmd_line) LIKE '%\\appdata\\local\\%') AND (LOWER(process_name) LIKE '%node%' OR LOWER(process_file_description) LIKE '%node.js%') AND time >= datetime('now', '-{{lookback_days}} days')
```

## staging-and-persistence
<!-- Analyze staging and persistence -->
parallel:
- → run-key-localappdata
- → nonstandard-file-prevalence
join: → triage-early-footprint

## run-key-localappdata
<!-- Run key persistence in LocalAppData -->
Identify any registry Run key pointing to executable code within LocalAppData, which is a common persistence method for this implant.

```sqlite target=endpoint role=detection-candidate params=(lookback_days=lookback_days)
~~~yaml
expected: A Run key pointing to a binary or script in a user's LocalAppData. This
  is a durable signal of staging.
reads:
- device_hostname
- reg_target
- reg_value_data
- time
silence: not_evidence_of_absence
source: hb_registry_activity
verified: dry-run
verified_at: '2026-09-24'
~~~
SELECT device_hostname, reg_target, reg_value_data, time FROM hb_registry_activity WHERE LOWER(reg_target) LIKE '%\\currentversion\\run%' AND LOWER(reg_value_data) LIKE '%\\appdata\\local\\%' AND time >= datetime('now', '-{{lookback_days}} days')
```

## nonstandard-file-prevalence
<!-- Rare files with non-standard extensions -->
Find rare files in user profiles matching the report's extension list to identify encrypted payloads using a suffix-based filter.

```sqlite target=endpoint role=baseline params=(lookback_days=lookback_days, nonstandard_extensions=nonstandard_extensions)
~~~yaml
baseline:
  compare: first_seen
  window: '{{lookback_days}}d'
expected: Files with extensions like .tmp or .cfg appearing in a LocalAppData folder
  that are rare across the fleet. Silence proves these specific extensions were not
  used.
prevalence:
  by: device_hostname
  key:
  - file_name
  rare_below: 5
reads:
- device_hostname
- file_path
- file_name
- time
silence: evidence_of_absence
source: hb_file_activity
verified: dry-run
verified_at: '2026-09-24'
~~~
SELECT device_hostname, file_path, file_name, COUNT(DISTINCT device_hostname) AS hosts, MIN(time) AS first_seen FROM hb_file_activity WHERE LOWER(file_path) LIKE '%\\appdata\\local\\%' AND instr(',' || '{{nonstandard_extensions}}' || ',', ',' || substr(LOWER(file_name), instr(LOWER(file_name), '.')) || ',') > 0 AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY file_path, file_name HAVING hosts <= 5
```

## triage-early-footprint
<!-- Triage early implant staging -->
```agent target=hunter
cite: required
context:
- scope-node-in-localappdata
- run-key-localappdata
- nonstandard-file-prevalence
max_iterations: 4
objective: Determine which hosts show evidence of a Node.js implant staging, citing
  the process location, Run key values, and clusters of rare staging files.
success_criteria: A per-host verdict of malicious | suspicious | benign, naming the
  user and host.
tools:
- endpoint
- network
```

## follow-on-activity
<!-- Investigate follow-on intrusion -->
parallel:
- → winrm-lateral-movement
- → recon-and-rundll32
join: → triage-intrusion-scope

## winrm-lateral-movement
<!-- WinRM lateral movement (Port 5985) -->
Identify outbound WinRM connections from the beachheads. Populate scope_hosts with results from triage-early-footprint to automate the pivot.

```sqlite target=network role=triage params=(lookback_days=lookback_days, scope_hosts=scope_hosts)
~~~yaml
expected: Outbound connections to 5985 originating from a suspected beachhead host.
  This indicates an attempt to move laterally.
reads:
- device_hostname
- dst_endpoint_ip
- dst_endpoint_port
- time
silence: not_evidence_of_absence
source: hb_network_connection
verified: dry-run
verified_at: '2026-09-24'
~~~
SELECT device_hostname, dst_endpoint_ip, dst_endpoint_port, time FROM hb_network_connection WHERE dst_endpoint_port = 5985 AND direction = 'outbound' AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```

## recon-and-rundll32
<!-- Discovery and rundll32 payloads -->
Detect Active Directory discovery and follow-on rundll32 execution, scoped to the specific users and hosts flagged in the early triage to minimize administrative noise.

```sqlite target=endpoint role=enrichment params=(lookback_days=lookback_days, scope_hosts=scope_hosts, scope_users=scope_users)
~~~yaml
expected: Process command lines performing domain enumeration or rundll32 loading
  actor-supplied DLLs, scoped to the flagged beachhead.
reads:
- device_hostname
- user_name
- process_name
- process_cmd_line
- time
silence: not_evidence_of_absence
source: hb_process_activity
verified: dry-run
verified_at: '2026-09-24'
~~~
SELECT device_hostname, user_name, process_name, process_cmd_line, time FROM hb_process_activity WHERE (LOWER(process_cmd_line) LIKE '%adsi%' OR LOWER(process_cmd_line) LIKE '%get-ad%' OR LOWER(process_name) LIKE '%rundll32.exe%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND ('{{scope_users}}' = '' OR instr(',' || '{{scope_users}}' || ',', ',' || user_name || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```

## triage-intrusion-scope
<!-- Triage enterprise intrusion -->
```agent target=hunter
cite: required
context:
- triage-early-footprint
- winrm-lateral-movement
- recon-and-rundll32
max_iterations: 4
objective: 'Weigh the evidence from both phases: does the host with the Node.js implant
  also show WinRM lateral movement or AD discovery? Determine the full scope of the
  intrusion.'
success_criteria: A final verdict naming beachheads, lateral targets, and users involved.
tools:
- endpoint
- network
```

## route-response
<!-- Route based on breach scope -->
if~: "the triage-intrusion-scope verdict is malicious for at least one host, indicating confirmed lateral movement or AD discovery" (confidence: high, judge=hunter)
then: → isolate-host
indeterminate: → analyst-review
unavailable: → analyst-review (blind_spot: no-network-telemetry)
else: → close-out

## isolate-host
<!-- Isolate compromised host -->
```action target=endpoint
~~~yaml
approval: required
~~~
Isolate the hosts identified as compromised beachheads. Revoke credentials for associated users and begin forensic collection of the LocalAppData artifacts.
```
→ analyst-review

## analyst-review
<!-- Analyze intrusion depth -->
```manual target=analyst
Examine the targets of the WinRM connections for follow-on payloads. Review the Teams chat history of affected users to identify the attacker's ingress method and the external tenant involved.
```
→ end

## close-out
<!-- Hunt close-out -->
```manual target=analyst
Record that no evidence of the Node.js implant or associated WinRM pivoting was found. Archive the instances of legitimate Node.js usage observed in profile paths for future tuning.
```
→ end

Run it

Take this hunt into your environment.

Open it in Huntbase to run every step against your own connections, with Scout weighing the evidence and your analysts in command. Or take the open hunt.md file anywhere that reads the format.

Machine-drafted by huntbase-hunt-generation using hb_google/gemini-3-flash-preview, gated by dry-run, lint, then reviewed by a person.