| name | logscale-security-queries |
| description | Develop, optimize, and troubleshoot CrowdStrike LogScale (Humio) security detection queries using CQL syntax. Use when writing LogScale queries, building security detections, creating threat hunting rules, fixing CQL syntax errors, working with CrowdStrike EDR/Falcon security monitoring, or building behavioral rules with the correlate() function. Handles case statements, risk categorization, multi-event correlation, investigation playbooks, and actionable security outputs. |
CrowdStrike LogScale Security Query Development
Expert assistance for developing security detection queries and hunting rules in CrowdStrike LogScale (formerly Humio) using CQL syntax.
When to Use This Skill
Use this skill when you need to:
- Write or optimize LogScale/CQL security queries
- Build threat detection rules with risk categorization
- Fix CQL syntax errors (especially case statements)
- Create investigation playbooks and hunting queries
- Develop queries for AWS CloudTrail, Entra ID, or CrowdStrike EDR
- Generate actionable security outputs with user context and remediation steps
- Build behavioral rules using
correlate() for multi-event detection
- Create attack chain detections that span multiple events over time
Quick Start
Basic Query Structure
// 1. Filter relevant events
#event_simpleName=<EventType>
| <field_filters>
// 2. Categorize risk
| case {
<critical_condition> | _RiskLevel := "Critical" ;
<high_condition> | _RiskLevel := "High" ;
* | _RiskLevel := "Low" ;
}
// 3. Enrich with context
| match(file="entraidusers.csv", field=UserPrincipalName, include=[DisplayName])
// 4. Generate output
| table([_RiskLevel, DisplayName, <key_fields>])
Critical Case Statement Rules
// ALWAYS use test() for numeric comparisons
| case {
test(FailedLogins > 5) | _Severity := "Critical" ; // โ
CORRECT
FailedLogins > 5 | _Severity := "Critical" ; // โ WRONG
}
// AND/OR ARE supported in case branches (contrary to earlier docs)
// โ
WORKS in case statements:
| case {
DetectionTier = "RAPID" AND UniqueIPs > 1 | RiskScore := 90;
UniqueIPs > 1 AND UniqueDevices > 1 | AttackPattern := "Distributed";
* | RiskScore := 50;
}
// Use composite keys when AND/OR is cleaner for string matching
| _Key := format("%s-%s", field=[Type, Location])
| case {
_Key="Admin-External" | _Risk := "High" ;
* | _Risk := "Low" ;
}
// ALWAYS include default branch
| case {
Status="Active" | _Label := "Active" ;
* | _Label := "Unknown" ; // โ
Required
}
Behavioral Rules with correlate()
For multi-event pattern detection (attack chains, behavioral sequences):
correlate(
FailedLogin: {
event.outcome="failure" event.action=/UserLogon/
},
SuccessLogin: {
event.outcome="success" event.action=/UserLogon/
| user.email <=> FailedLogin.user.email
},
sequence=true,
within=30m,
globalConstraints=[user.email]
)
Key correlate() concepts:
- Named queries:
FailedLogin: { ... } - each event pattern has a name
- Link operator
<=>: Correlates fields between queries
sequence=true: Events must occur in order
within=30m: Time window constraint
globalConstraints: Fields all events must share
See correlate-function.md for complete syntax reference.
Core Principles
1. Actionable Over Raw
- Include display names, risk scores, and specific actions
- Provide categorized outputs, not just event dumps
- Add business context and investigation IDs
2. Syntax Precision
- Use
test() for all comparisons (>, <, >=, <=, !=)
- Use
:= for assignments in case statements
- End each case branch with
; semicolon
- Never nest case statements
3. Maintainability
- Use functions over hardcoded exclusions
- Implement dynamic classification (service account detectors)
- Keep queries focused and well-commented
4. Risk-Based Categorization
- Implement severity levels (Critical, High, Medium, Low)
- Assign risk scores and action priorities
- Provide specific remediation recommendations
Common Tasks
Fix Case Statement Errors
See case-statements.md for:
- 12 distinct case statement patterns
- Complete syntax rules and limitations
- Common errors with before/after fixes
- Debug methodology and testing checklist
Build Detection Query
See query-patterns.md for:
- Failed login monitoring and multi-tier severity scoring
- Privilege escalation detection with session correlation
- Statistical baseline anomaly detection (defineTable)
- Array operations for complex data extraction
- Geographic risk with privilege multipliers
- DNS entropy analysis for tunneling detection
- Temporal gating for duplicate alert prevention
- Data exfiltration indicators
Troubleshoot Syntax Errors
See troubleshooting.md for:
- Comprehensive error catalog
- Emergency fix templates
- When to use test() reference table
- Step-by-step debugging process
Create Investigation Playbook
See investigation-playbooks.md for:
- 5-phase investigation methodology
- Structured hunting approaches
- Timeline analysis techniques
- Root cause identification
View Examples
See examples.md for 11 production examples:
- Statistical anomaly detection with defineTable() baselines
- Event sequencing with selfJoinFilter()
- Aggregation with threshold detection
- Volume-based exfiltration detection
- Multi-platform pattern matching with case statements
- Named capture groups for data extraction
- Session hijacking with string formatting
- Simple aggregation with service account filtering (GitHub)
- Statistical baseline with defineTable() (AWS KMS)
- Multi-tier severity with temporal gating (EntraID brute force)
- Geographic risk with privilege multipliers (international sign-in)
Key Syntax References
Case Statement Structure
| case {
condition1 | field1 := value1 | field2 := value2 ;
test(comparison) | field := value ;
Field=/regex/ | field := value ;
* | field := default ; // Always required
}
When to Use test()
- Greater/less than:
test(Field > 5)
- Not equal:
test(Field != "value")
- Field comparison:
test(Field1 > Field2)
- Simple equality:
Field="value" (no test() needed)
- Regex:
Field=/pattern/ (no test() needed)
AND/OR in Case Statements
AND/OR works in case branches for combining conditions:
| case {
DetectionTier = "RAPID" AND UniqueIPs > 1 | RiskScore := 90;
entra.privilege_category="global_admin" AND CountryRisk!="low" | CompositeRiskScore := CompositeRiskScore + 25;
* | RiskScore := 50;
}
Composite keys are still useful for complex string-based matching:
| _Key := format("%s-%s-%s", field=[Protocol, Port, DestIP])
| case {
_Key="tcp-22-0.0.0.0/0" | _Risk := "Critical" ;
_Key=/tcp-(80|443)-.*/ | _Risk := "Low" ;
}
Key Functions Quick Reference
| Function | Purpose | Example |
|---|
test() | Numeric comparison in case/filter | test(count > 5) |
if() | Conditional value assignment | if(field, then=a, else=b) |
format() | String formatting | format("%s-%s", field=[a, b]) |
coalesce() | First non-null value | coalesce([f1, f2], as=out) |
defineTable() | Historical baseline | defineTable(query={...}, start=7d) |
match() | CSV/table lookup | match(file="x.csv", field=f) |
selfJoinFilter() | Multi-event correlation | selfJoinFilter(field=[aid], where=[...]) |
session() | Time-bounded grouping | session([...], maxpause=15m) |
array:contains() | Array membership | array:contains(array="f[]", value="v") |
objectArray:eval() | Iterate object arrays | objectArray:eval("arr[]", ...) |
shannonEntropy() | String randomness | shannonEntropy(DomainName) |
ipLocation() | GeoIP enrichment | ipLocation(source.ip) |
asn() | ASN organization lookup | asn(source.ip) |
cidr() | Subnet membership | cidr(ip, subnet=["10.0.0.0/8"]) |
now() | Current time (epoch ms) | _current := now() |
formatDuration() | Duration display | formatDuration(ms, from=ms, precision=2) |
See reference.md for complete syntax documentation.
Link Operator for correlate()
// The <=> operator links fields between named queries
correlate(
Query1: { filter1 },
Query2: {
filter2
| fieldA <=> Query1.fieldB // Links fieldA to Query1.fieldB
},
within=1h
)
// Output fields are prefixed: Query1.fieldB, Query2.fieldA
Supporting Files
Workflow
- Define objective - What threat/behavior are you detecting?
- Start with basic filter - Get relevant events with simple filters
- Add categorization - Implement risk-based logic with case statements
- Enrich context - Add user data, geo, timeline using joins/lookups
- Generate output - Create actionable format with display names and actions
- Validate query - Use the CLI validator before deployment
- Test and refine - Validate against historical data, adjust false positives
Query Validation (AI-Assisted Detection Engineering)
When creating or modifying detection templates, always validate queries before committing:
Validate Query CLI Command
python scripts/resource_deploy.py validate-query --template <path/to/detection.yaml>
python scripts/resource_deploy.py validate-query --query '#Vendor="sase" | count()'
python scripts/resource_deploy.py validate-query --file /tmp/query.txt
Output
VALID (exit code 0) - Query syntax is correct
INVALID: <message> (exit code 1) - Query has syntax errors
AI Workflow for Detection Development
- Write the detection template with
search.filter query
- Run validation:
python scripts/resource_deploy.py validate-query --template <path>
- If INVALID, review the query for common CQL issues:
- Case statement syntax (missing
test(), missing default branch *)
- Incorrect use of
if() function (use case statements instead)
- AND/OR operators in case conditions (use composite keys)
- Comparison operators without
test() wrapper
- Fix and re-validate until
VALID
- Run full plan:
python scripts/resource_deploy.py plan --resources=detection
Common Validation Failures
| Error Pattern | Likely Cause | Fix |
|---|
NotAFunctionArgumentOperator | Using = in function args like count(x, where=field="value") | Use case statement to create flag field, then sum() |
UnrecognizedNamedArgumentNoSuggestions | Wrong if() syntax | Use case statement instead of if() |
ArraysNotSupportedHere | Positional args in if() | Use named params: if(condition, then=x, else=y) |
| Generic syntax error | Case statement issues | Check for test(), default branch, no AND/OR |
Unknown error with groupBy | Named assignment := in function list | Use as= for count/sum/min/max, use original field name for collect() |
Unknown error with collect | Using as= or := with collect() | collect() doesn't support naming - use original field name after groupBy |
Debugging "Unknown Error"
When you get INVALID: Syntax error: Unknown error, isolate the problem:
git stash && python scripts/resource_deploy.py validate-query --template <path>
git stash pop
python scripts/resource_deploy.py validate-query --query '#Vendor="aws" | groupBy([x], function=[count()])'
See troubleshooting.md for the full debugging methodology.
Platform Limitations
Case Statements
- โ No nested case statements (use sequential case blocks instead)
- โ No comparisons (>, <, !=) without test() wrapper
- โ Cannot use field created in same case branch
- โ No
:= assignment in groupBy function list
- โ
collect() doesn't support as= parameter - use original field name
- โ
AND/OR conditions work in case branches (e.g.,
field1="a" AND field2 > 5)
- โ
Use sequential case statements for complex multi-step logic
- โ
Wrap numeric comparisons in test()
- โ
Create fields first, use in next case statement
- โ
Always include default branch (
*)
- โ
Use
as= for count/sum/min/max in groupBy
correlate() Function
- โ Cannot use after aggregators (groupBy, count, etc.)
- โ Cannot appear inside function parameters
- โ Cannot use aggregate functions within queries
- โ Only
<=> operator for linking (no other comparisons)
- โ Results must fit within memory quota
- โ
Supports 3+ queries (unlike join which only supports 2)
- โ
Use
globalConstraints to simplify links
- โ
Use
sequence=true only when event order matters
Requirements
This skill works with:
- CrowdStrike LogScale / Humio
- CQL (CrowdStrike Query Language)
- CSV lookup files (entraidusers.csv, entraidgroups.csv)
- Custom functions (aws_service_account_detector, etc.)
Need Help?