Search & Query Language
Search & Query Language
nano uses nPL (nano Pipe Language), a piped query language for searching and analyzing security logs. If you've used other piped query languages, the syntax will be familiar.
Also available: Quick Reference, all 45 commands at a glance.
Understanding Fields
nano organizes log data into different field types, each optimized for specific search patterns. For detailed information, see Field Types & Search Performance.
Quick Overview:
- Metadata Fields (
timestamp,source_type,id) - Fastest, always present - Normalized Fields (
src_ip,user,process_name) - Fast, heavily indexed - UDM Fields (525+ extended fields) - Good performance, broad coverage
- Enriched Fields (
enriched_src_country,enriched_dest_asn) - Fast, pre-computed - Prevalence Fields (
prevalence_min,prevalence_process_hash, etc.) - Ingest-time rarity scores (0-100) - Extension Fields (additional UDM fields) - Stored as dynamic JSON, searchable by name
Query Basics
Simple Search
Search for keywords across all fields:
failed loginThis searches for events containing both "failed" and "login" in any field.
Field Filters
Search specific fields using field=value syntax:
user=admin
status=401
src_ip=192.168.1.100Comparison Operators
| Operator | Description | Example |
|---|---|---|
= | Equal | status=200 |
!= | Not equal | status!=200 |
> | Greater than | bytes>1000 |
< | Less than | response_time<100 |
>= | Greater or equal | severity>=5 |
<= | Less or equal | port<=1024 |
Logical Operators
Combine conditions with AND, OR, NOT:
# AND (implicit or explicit)
user=admin status=401
user=admin AND status=401
# OR
status=401 OR status=403
# NOT
user=admin NOT status=200
# Grouping with parentheses
(status=401 OR status=403) AND user=adminWildcards
Use * for wildcard matching:
# Wildcard in field value
user=admin*
filename=*.exe
# Wildcard keyword search
*malware*Regex Matching
Use /pattern/ for regex searches:
# Regex on specific field
filename=/cmd\.exe$/
# Case-insensitive regex
user=/admin|root/i
# Complex patterns
CommandLine=/powershell.*-enc.*[A-Za-z0-9+\/=]{50,}/Advanced Search Operators
IN Lists
Check if a field matches any value in a list:
status IN (200, 201, 204)
user IN ("admin", "root", "administrator")
EventID NOT IN (4624, 4625)IOC Observable Matching
Match an indicator of compromise across all observable columns at once with the ioc term — you never specify which field the indicator might appear in:
ioc=1.2.3.4
ioc=5fb90fd28458d16e61a7d6e08ae2ed28cb9b35e535aa6bc0e5a487f7c7702288
ioc in [1.2.3.4, evil.com, paste.ee/r/k9Xz]ioc resolves the value against every observable field — IPs, file/process hashes, URLs and domains, DNS, identity, and detection context (CVE, rule, signature) — and is profile-aware (UDM or OCSF columns). It works on its own (ioc=1.2.3.4 | stats count by src_host) and pairs with | retro for a full-history, prevalence-weighted sweep. See retro for all forms, including threat-feed and lookup-table sources.
String Operators
| Operator | Description | Example |
|---|---|---|
LIKE | Pattern matching with % wildcard | filename LIKE "%.exe" |
NOT LIKE | Negated pattern match | path NOT LIKE "%temp%" |
CONTAINS | Substring match | message CONTAINS "error" |
STARTSWITH | Prefix match | domain STARTSWITH "evil" |
ENDSWITH | Suffix match | filename ENDSWITH ".ps1" |
# Examples
filename LIKE "%.dll"
CommandLine CONTAINS "powershell"
domain STARTSWITH "malicious"
path ENDSWITH "\\system32"Function Filters
Use functions in search filters:
# Length check
len(password) < 8
# Lowercase comparison
lower(user) = "admin"
# Time-based filtering
timestamp > now() - INTERVAL 1 HOUR
age(timestamp) < 3600Piped Commands
Transform and analyze results using piped commands with |. Results flow from left to right:
<search expression> | <command> | <command> | ...Aggregation & Statistics
- stats - Aggregate data with functions like count, sum, avg
- chart - Create aggregated visualizations (alias for stats)
- timechart - Time-based aggregations with automatic bucketing
- streamstats - Calculate running statistics per event
- eventstats - Add aggregate statistics to all events
- bin - Bucket timestamps or numeric values
Filtering & Selection
- where - Filter results based on conditions
- head - Return first N results
- tail - Return last N results
- dedup - Remove duplicate events
- top - Find most common values
- rare - Find least common values
- sample - Return random sample of events
Field Operations
- eval - Create calculated fields
- rename - Rename fields
- fields - Include or exclude specific fields
- table - Display specific fields
- rex - Extract fields using regex
- spath - Extract fields from JSON/XML
- fillnull - Replace null values
- mvexpand - Expand multi-value fields
Ordering & Formatting
Enrichment & Analysis
- lookup - Enrich with lookup table data
- inputlookup - Fetch data from external URLs for enrichment
- prevalence - Filter or enrich based on artifact prevalence
- resolve_identity - Resolve IP addresses to hostnames/users from EDR data
- risk - Assign risk scores for risk-based alerting
- anomaly - Detect statistical outliers
- asset - Asset investigation view with identity resolution
- cloud - Cloud investigation view with faceted summaries
- baseline - What's new for a host/user/IP vs. its own recent history
- retro - Full-history, prevalence-weighted IOC sweep (pairs with the
iocsearch term)
Pattern Detection
- transaction - Group related events
- sequence - Detect ordered event patterns
- funnel - Analyze conversion through sequential steps
- tree - Visualize hierarchical relationships (process trees, web flows)
Observability
Command-pages that open a curated observability surface directly from the search bar. Each must be the final command in the pipeline; they open a built page (no log scan) rather than returning rows.
- services - Services overview with RED metrics (rate, errors, duration)
- service - Single-service detail page (RED, endpoints, latency)
- trace - Distributed-trace waterfall by trace id
- metric - Metric time-series explorer
Subsearch Operations
- append - Append subsearch results
- join - Join with subsearch on common fields
- format - Format subsearch results as string
- return - Return field values from subsearch
Prevalence Filtering
nano provides two ways to filter by prevalence: ingest-time fields (recommended) and query-time enrichment.
Ingest-Time Prevalence Fields (Recommended)
Prevalence scores are computed at ingest time and stored directly in the logs. This is the fastest approach:
# Filter events with rare process hashes (score 0-25)
sourcetype = "sysmon" AND prevalence_process_hash <= 25
# Find events with any rare artifact
prevalence_min <= 10
# Combine with other filters
dest_ip != "" AND prevalence_dest_ip <= 15 | table timestamp, dest_ip, prevalence_dest_ipIngest-time prevalence fields:
| Field | Description |
|---|---|
prevalence_file_hash | Score (0-100) for file_hash. Lower = rarer. |
prevalence_process_hash | Score (0-100) for process_hash. Lower = rarer. |
prevalence_dest_domain | Score (0-100) for dest_host. Lower = rarer. |
prevalence_dest_ip | Score (0-100) for dest_ip (public IPs only). |
prevalence_min | Minimum of all scores (for filtering any rare artifact). |
Score interpretation: 0-10 = Very rare, 11-25 = Rare, 26-50 = Uncommon, 51-100 = Common, 255 = N/A
Prevalence Slider
The search interface includes a Prevalence Filter slider next to the search button:
- Drag left for rare artifacts only, right to show all
- Quick presets: Very Rare (≤10), Rare (≤25), Uncommon (≤50), All
- Automatically injects
| where prevalence_min <= Xbefore any pipeline commands, so it filters events prior to anystats/table/timechartaggregation - Value is saved in URL for shareable links
Query-Time Prevalence Enrichment
For dynamic prevalence lookups (slower but always current), use the prevalence command:
# Filter rare hashes (seen on < 5 hosts)
* | prevalence hash_prevalence < 5 window=24h
# Filter rare domains
* | prevalence domain_prevalence < 3 window=7dTime Ranges
Time Picker
Use the UI time picker to set the search time range:
- Last 15 minutes
- Last hour
- Last 24 hours
- Last 7 days
- Last 30 days
- Custom range
Time Filters in Query
Filter by time in your query:
# Relative time
timestamp > now() - INTERVAL 1 HOUR
timestamp > date_sub(now(), INTERVAL 24 HOURS)
# Absolute time
timestamp > "2025-01-01 00:00:00"
timestamp BETWEEN "2025-01-01" AND "2025-01-31"
# Age-based
age(timestamp) < 3600 # Less than 1 hour oldQuery Examples
Security Use Cases
# Brute force detection
event_type=login status=failure
| bin span=5m
| stats count() as attempts by time_bucket, src_ip
| where attempts > 10
# Rare process execution
EventID=1
| prevalence hash_prevalence < 5 window=24h
| table timestamp, Image, CommandLine, hash_prevalence
# Suspicious PowerShell
Image=*powershell.exe CommandLine=*-enc*
| eval cmd_entropy = entropy(CommandLine)
| where cmd_entropy > 4.5
# Privilege escalation
EventID IN (4672, 4673, 4674)
| stats count() by user, PrivilegeList
| where count > 5
# Data exfiltration
bytes > 10485760 dest_port IN (80, 443)
| stats sum(bytes) as total_bytes by src_ip, dest_ip
| where total_bytes > 104857600 # 100 MBLog Analysis
# Error rate by service
level=error
| bin span=5m
| stats count() by time_bucket, service
# Slow requests
response_time > 1000
| stats avg(response_time), max(response_time), count() by endpoint
# Top talkers
* | stats sum(bytes) as total_bytes by src_ip
| sort -total_bytes
| head 10
# Failed authentication sources
status IN (401, 403)
| stats count() as failures by src_ip, user_agent
| where failures > 5Threat Hunting
# Beaconing detection
* | bin span=1m
| stats count() as connections by time_bucket, src_ip, dest_ip
| stats stdev(connections) as conn_stdev by src_ip, dest_ip
| where conn_stdev < 0.5 # Consistent timing = beaconing
# Lateral movement
EventID=4624 LogonType=3
| stats dc(dest_host) as unique_targets by src_ip
| where unique_targets > 10
# Suspicious domains
* | eval domain = extract_domain(url)
| eval domain_entropy = entropy(domain)
| where domain_entropy > 4.0
| prevalence domain_prevalence < 3
# Command and control
* | where is_public_ip(dest_ip)
| stats sum(bytes) as total, count() as connections by dest_ip
| where connections > 100 AND total < 10000 # Many small connectionsSearch Queuing
When the system is busy, your search may be queued rather than running immediately. This happens when the global or per-user concurrency limits are reached.
While queued, you'll see your position in the queue and an estimated wait time. You can cancel a queued search at any time using the cancel button.
The Active Searches panel (above the results area) shows all your current and recent searches, including queued, running, completed, and failed jobs. Click a completed search to reload its results.
Detection rules always run immediately and are never affected by search queuing.
For configuration details, see Search Concurrency.
Performance Tips
- Filter early: apply time and field filters before piped commands
- Use specific fields:
src_host="github.com"is much faster than baregithub.com - Limit results: use
headto limit large result sets - Avoid wildcards at start:
*adminis slower thanadmin* - Use stats wisely: group by low-cardinality fields when possible
- Time-based partitioning: always specify a time range
- Aggregate:
statsis more efficient than returning raw events - Sample for testing: use
samplewhen developing queries
For a deep dive on all of these, see Search Best Practices.
Keyboard Shortcuts
Ctrl+Enter: Execute queryCtrl+K: Open command paletteCtrl+Space: AutocompleteCtrl+/: Comment/uncommentCtrl+F: Find in results
Next Steps
- Quick Reference: all 45 commands at a glance
- Search Best Practices: write faster, more efficient queries
- Field Types & Search Performance: understand field categories and optimization
- Detection Rules: create threat detections
- Dashboards: visualize your data