// question

On a Linux system, you suspect someone erased their command history by linking the history file to /dev/null. Investigate process events and determine which utility was executed to achieve this.

Reference KQL answer and model-by-model comparison.

23d348f3-cc5c-4ba9-bd0a-ae09069f0914
question id
17/23
models correct
1
valid answer
74%
success rate
// reference answer

Human-validated correct answer for this question.

ln
// model comparison
ModelAnswerKQL queryStatusTriesTimeCost
gemini-2.5-flash-preview-04-17
ln
DeviceProcessEvents
| where ProcessCommandLine has "ln"
| where ProcessCommandLine has "/dev/null"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine
pass18.75s0.0026
gpt-35-turbo
no answer
no query generatedfail50.00s0.0111
gpt-4-turbo-2024-04-09
ln
DeviceProcessEvents
| where InitiatingProcessCommandLine has 'ln' and (InitiatingProcessCommandLine has '/dev/null' and InitiatingProcessCommandLine has '.bash_history')
| union (DeviceProcessEvents
| where ProcessCommandLine has 'ln' and (ProcessCommandLine has '/dev/null' and ProcessCommandLine has '.bash_history'))
| project TimeGenerated, DeviceName, FileName, FolderPath, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine
pass432.74s0.1963
gpt-4.1
ln
DeviceProcessEvents
| where ProcessCommandLine has "/dev/null" and ProcessCommandLine has ".bash_history"
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessCommandLine
pass15.45s0.0119
gpt-4.1-finetuned
no answer
no query generatedfail50.00s0.0491
gpt-4.1-mini
ln
DeviceProcessEvents
| where DeviceName endswith ".linux" or FolderPath has_any ("/bin", "/usr/bin", "/usr/local/bin")
| where ProcessCommandLine contains "/dev/null"
| where FileName in~ ("ln", "bash", "sh", "cat", "rm", "unlink")
| project TimeGenerated, DeviceName, InitiatingProcessAccountName, FileName, ProcessCommandLine
| order by TimeGenerated desc
pass325.12s0.0068
gpt-4.1-nano
no answer
no query generatedfail50.00s0.0011
gpt-4o
dash
DeviceProcessEvents
| where InitiatingProcessCommandLine contains "ln" and InitiatingProcessCommandLine contains "/dev/null"
| project TimeGenerated, DeviceName, InitiatingProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessFileName, InitiatingProcessId
fail13.13s0.0146
gpt-5-high
ln
let LinuxDevices = DeviceInfo
| where OSPlatform =~ "Linux"
| project DeviceId, DeviceName;
let HistIndicators = dynamic(["bash_history","zsh_history","ash_history","ksh_history","sh_history","fish_history",".history","HISTFILE"]);
DeviceProcessEvents
| where ActionType == "ProcessCreated"
| where DeviceId in (LinuxDevices | project DeviceId)
| where ProcessCommandLine has "/dev/null"
| where ProcessCommandLine has_any (HistIndicators)
// Ensure the created process is ln/link or an applet invocation that runs ln
| extend isLn = case(
    tolower(FileName) in ("ln","link"), true,
    tolower(FileName) in ("busybox","toybox","coreutils") and ProcessCommandLine matches regex @"\bln\b", true,
    false)
| where isLn
// Identify which utility actually executed
| extend Utility = case(
    tolower(FileName) == "ln", "ln",
    tolower(FileName) == "link", "link",
    tolower(FileName) in ("busybox","toybox","coreutils") and ProcessCommandLine matches regex @"\bln\b", strcat(tolower(FileName), " ln"),
    FileName)
// Infer link type
| extend LinkType = iif(ProcessCommandLine matches regex @"\s-[^\s]*s", "symlink", "hard_or_unspecified")
// Extract the first history-like token referenced in the command
| extend HistoryTokens = extract_all(@"(\S*history\S*)", ProcessCommandLine)
| extend HistoryPath = iif(array_length(HistoryTokens) > 0, tostring(HistoryTokens[0]), "")
| project TimeGenerated, DeviceName, DeviceId, AccountName, Utility, LinkType, HistoryPath,
          FileName, FolderPath, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessParentFileName, ReportId
| order by TimeGenerated desc
pass3227.43s0.1533
gpt-5-mini-high
ln
let history_files = dynamic([".bash_history", ".zsh_history", ".sh_history", ".history", "fish_history"]);
let cmd_indicators = dynamic(["/dev/null", "ln -s", "ln -f", "ln ", "unlink ", "rm ", "cat ", "tee ", "dd ", "> /dev/null", ">/dev/null", "history -c", "shred", "truncate"]);

// Process executions that reference /dev/null or history files
let proc_hits = DeviceProcessEvents
| where isnotempty(ProcessCommandLine)
| where ProcessCommandLine has_any(cmd_indicators) or ProcessCommandLine has_any(history_files)
| project TimeGenerated, DeviceName, DeviceId, AccountName, AccountDomain = AccountDomain, ProcessId, InitiatingProcessId, InitiatingProcessFileName, InitiatingProcessCommandLine, ProcessCommandLine, ProcessCreationTime;

// File events touching common history filenames (helps catch symlink/hardlink actions)
let file_hits = DeviceFileEvents
| where FileName in (".bash_history", ".zsh_history", ".sh_history", ".history", "fish_history")
| where ActionType has_any ("Deleted", "Created", "Modified", "HardlinkCreated", "SymlinkCreated", "Renamed", "Overwritten", "Overwrite") or tostring(AdditionalFields) has "/dev/null"
| project TimeGenerated, DeviceName, DeviceId, AccountName = RequestAccountName, AccountDomain = RequestAccountDomain, ProcessId = InitiatingProcessId, InitiatingProcessId = InitiatingProcessParentId, InitiatingProcessFileName, InitiatingProcessCommandLine, ProcessCommandLine = InitiatingProcessCommandLine, ProcessCreationTime = InitiatingProcessCreationTime, FileName, FolderPath, ActionType, AdditionalFields;

union isfuzzy=true proc_hits, file_hits
| extend matched_devnull = (tolower(tostring(ProcessCommandLine)) contains "/dev/null") or (tostring(AdditionalFields) contains "/dev/null")
| extend suspect_utility = case(
    tolower(ProcessCommandLine) contains " ln" or tolower(ProcessCommandLine) startswith "ln", "ln",
    tolower(ProcessCommandLine) contains "unlink", "unlink",
    tolower(ProcessCommandLine) contains " rm" or tolower(ProcessCommandLine) startswith "rm", "rm",
    tolower(ProcessCommandLine) contains " cat" or tolower(ProcessCommandLine) startswith "cat", "cat",
    tolower(ProcessCommandLine) contains " tee" or tolower(ProcessCommandLine) startswith "tee", "tee",
    tolower(ProcessCommandLine) contains " dd" or tolower(ProcessCommandLine) startswith "dd", "dd",
    tolower(ProcessCommandLine) contains "shred", "shred",
    tolower(ProcessCommandLine) contains "truncate", "truncate",
    tolower(ProcessCommandLine) contains "history -c", "history -c",
    "other")
| where matched_devnull or suspect_utility != "other"
| summarize
    event_count = count(),
    first_time = min(TimeGenerated),
    last_time = max(TimeGenerated),
    sample_commands = make_set(tostring(ProcessCommandLine), 10),
    sample_initiators = make_set(tostring(InitiatingProcessFileName), 10),
    sample_additional = make_set(tostring(AdditionalFields), 5),
    actions = make_set(tostring(ActionType), 5)
    by DeviceName, DeviceId, AccountName, AccountDomain, FileName, suspect_utility
| order by last_time desc

// When you run this query, expand the sample_commands and sample_initiators to inspect the exact command lines and parent processes. Look specifically for commands such as: ln -s /dev/null ~/.bash_history, ln -f /dev/null ~/.bash_history, unlink ~/.bash_history, rm ~/.bash_history, or cat /dev/null > ~/.bash_history. If you find a matching entry, the suspect_utility column will indicate which utility was likely used.
pass127.62s0.0095
gpt-5-mini-low
ln
// Look for processes and file events that reference /dev/null or known shell history files
let procMatches = DeviceProcessEvents
| where ProcessCommandLine has "/dev/null"
    or ProcessCommandLine has ".bash_history"
    or ProcessCommandLine has ".zsh_history"
    or ProcessCommandLine has ".history"
    or ProcessCommandLine contains " ln "
    or ProcessCommandLine startswith "ln "
    or ProcessCommandLine contains "unlink"
    or ProcessCommandLine contains "tee "
    or ProcessCommandLine contains "cat /dev/null"
    or ProcessCommandLine contains "cp /dev/null"
    or ProcessCommandLine contains "truncate"
    or ProcessCommandLine contains ">/dev/null"
    or ProcessCommandLine contains "> /dev/null"
| project TimeGenerated, Type = "Process", DeviceName, AccountName, Executable = FileName, ProcessId, CommandLine = ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine;

let fileMatches = DeviceFileEvents
| where FileName has ".bash_history"
    or FileName has ".zsh_history"
    or FileName has ".history"
    or FolderPath has "/dev"
    or PreviousFileName has ".bash_history"
    or PreviousFolderPath has "/dev"
    or InitiatingProcessCommandLine has "/dev/null"
| project TimeGenerated, Type = "File", DeviceName, AccountName = RequestAccountName, Executable = FileName, ProcessId = InitiatingProcessId, CommandLine = InitiatingProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine;

union procMatches, fileMatches
| extend Utility = iif(isnotempty(Executable), tostring(split(Executable, "/")[-1]), tostring(split(trim(@"\n\r\t " ,CommandLine), " ")[0]))
// Aggregate and show most common utilities that touched history or referenced /dev/null
| summarize Count = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), AnyExampleCommand = any(CommandLine), AnyInitiatingProcess = any(InitiatingProcessFileName) by Utility
| order by Count desc, LastSeen desc
| project Utility, Count, FirstSeen, LastSeen, AnyExampleCommand, AnyInitiatingProcess
pass135.75s0.0087
gpt-5-mini-medium
no answer
no query generatedfail50.00s0.0213
gpt-5-nano-high
no answer
no query generatedfail50.00s0.0078
gpt-5-nano-medium
ln
let StartTime = ago(7d);

let processHits = DeviceProcessEvents
| where TimeGenerated >= StartTime
| where InitiatingProcessCommandLine contains "ln" or ProcessCommandLine contains "ln"
| where (InitiatingProcessCommandLine contains "/dev/null" or ProcessCommandLine contains "/dev/null")
| where (InitiatingProcessCommandLine contains ".bash_history" or InitiatingProcessCommandLine contains "bash_history" or InitiatingProcessCommandLine contains "history")
| project TimeGenerated, DeviceName, DeviceId, InitiatingProcessAccountName, InitiatingProcessAccountDomain, InitiatingProcessCommandLine, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessId, InitiatingProcessSessionId;

let fileHits = DeviceFileEvents
| where TimeGenerated >= StartTime
| where ActionType in ("Link","Create","Symlink","Relink","HardLink","SymbolicLink")
| where (FileName endswith ".bash_history" or FileName contains "bash_history")
| where (InitiatingProcessCommandLine contains "ln" or InitiatingProcessCommandLine contains "/dev/null")
| project TimeGenerated, DeviceName, DeviceId, InitiatingProcessAccountName, InitiatingProcessCommandLine, FileName, FolderPath, InitiatingProcessId;

processHits
| union fileHits
| sort by TimeGenerated desc
| take 250
pass130.87s0.0022
grok-3-beta
ln
DeviceProcessEvents
| where TimeGenerated > ago(30d)
| where ProcessCommandLine contains 'ln' and ProcessCommandLine contains '/dev/null'
| join kind=inner (
    DeviceInfo
    | where OSPlatform == 'Linux'
) on DeviceId
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, ProcessCommandLine
| order by Timestamp desc
pass111.24s0.0228
grok-3-mini-beta
ln
DeviceInfo
| where OSPlatform == "Linux"
| project DeviceId, DeviceName
| join kind=inner (DeviceProcessEvents) on DeviceId
| where ProcessCommandLine contains "ln" and ProcessCommandLine contains "/dev/null" and (ProcessCommandLine contains ".bash_history" or ProcessCommandLine contains ".history")
| project TimeGenerated, DeviceName, ProcessCommandLine, InitiatingProcessFileName, AccountName
| order by TimeGenerated desc
| limit 100  // Adjust limit as needed based on data volume and timerange
pass112.70s0.0019
o1-high
ln
DeviceProcessEvents
| where ActionType == "ProcessCreated"
| where isnotempty(ProcessCommandLine)
| join kind=inner (
    DeviceInfo
    | where OSPlatform =~ "Linux"
) on DeviceId
| where ProcessCommandLine has "/dev/null" and (ProcessCommandLine has "bash_history" or ProcessCommandLine has "history")
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessCommandLine, FolderPath
| order by TimeGenerated desc
pass158.43s0.3118
o1-low
ln
DeviceInfo
| where OSPlatform == "Linux"
| distinct DeviceId
| join kind=inner (
    DeviceProcessEvents
    | where ActionType == "ProcessCreated" // Focus on new processes
    | where ProcessCommandLine has "bash_history"
    | where ProcessCommandLine has "/dev/null"
) on DeviceId
| project TimeGenerated, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessCommandLine
| order by TimeGenerated desc
pass131.51s0.2340
o3-high
ln
// 1. Get Linux devices only
let LinuxDevices = DeviceInfo
| where OSPlatform startswith "Linux"
| summarize arg_max(TimeGenerated, DeviceName) by DeviceId;

// 2. Look for history-erasure via symbolic link to /dev/null
LinuxDevices
| join kind=inner (
    DeviceProcessEvents
    | where FileName =~ "ln"               // "ln" utility (case-insensitive)
    | where ProcessCommandLine has "/dev/null"
    | where ProcessCommandLine has_any (".bash_history", ".zsh_history", "history")
) on DeviceId

// 3. Show the interesting details
| project TimeGenerated,
          DeviceName,
          AccountName,
          FileName,
          ProcessCommandLine,
          InitiatingProcessFileName,
          InitiatingProcessCommandLine,
          FolderPath,
          SHA256
| sort by TimeGenerated desc
pass134.85s0.0221
o3-mini-high
ln
DeviceProcessEvents
| where ProcessCommandLine contains "/dev/null"
| where ProcessCommandLine has ".bash_history" or ProcessCommandLine has "history"
| project TimeGenerated, DeviceName, ProcessCommandLine, ProcessId
| sort by TimeGenerated desc
pass120.97s0.0120
o3-mini-low
ln
DeviceProcessEvents
| where tolower(ProcessCommandLine) has 'ln' and tolower(ProcessCommandLine) has '/dev/null'
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessCommandLine, ProcessId, InitiatingProcessId
| sort by TimeGenerated desc
pass220.62s0.0149
o4-mini-high
ln
DeviceProcessEvents
| where ProcessCommandLine has "/dev/null" and ProcessCommandLine has ".bash_history"
| where FileName == "ln"
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, FileName, ProcessCommandLine, ProcessId, InitiatingProcessId
pass126.02s0.0108
o4-mini-low
ln
// 1. Identify Linux devices
let LinuxDevices = DeviceInfo
| where OSPlatform has "Linux"
| distinct DeviceId;

// 2. Look for ln invocations that link history files to /dev/null on those devices
DeviceProcessEvents
| where DeviceId in (LinuxDevices)
| where tolower(FileName) == "ln"
| where ProcessCommandLine has "/dev/null"
    and (ProcessCommandLine has "history" or ProcessCommandLine has ".bash_history")
| project TimeGenerated, DeviceName, AccountName, ProcessId, FileName, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine
| sort by TimeGenerated desc
pass378.47s0.0283