Common supported CEL macros
Sophos XDR supports Google's CEL macros for evaluating and manipulating data. Some macros are built in, while others are specific to Sophos XDR. This page describes commonly used macros.
You can also use CEL Explorer, which provides context-aware autocomplete for CEL macros. For details, see CEL Explorer.
In these examples, the following data structure is used:
{
"plant": {
"type": "tree",
"name": "white oak",
"uses": [
"lumber",
"firewood",
"furniture"
],
"traits": {
"produces_fruit": true,
"genus": "Quercus",
"height": 100,
"extinct": false,
"related_to": [
{
"name": "chestnut",
"genus": "Castanea"
},
{
"name": "beech",
"genus": "Fagus"
}
]
},
"locations": [
"usa",
"europe",
"new york",
"New York",
"new york ",
"usa",
"worldwide",
"eu"
]
}
}
Macros for working with lists and maps
The following CEL macros are formulated for working with lists and maps.
Example map data structure:
{
"name": "apple",
"count": 5
}
In the map example, the labels on the left are referred to as keys. Each pairing of name: value is referred to as an element.
Maps can be nested, such as:
{
"name": "apple",
"color": "red",
"count": 5,
"properties": {
"color": "red"
}
}
Accessing the value of the color key in the properties map is referred to as a path and uses . notation: properties.color
Example list data structure:
[
"apple",
"banana",
"orange"
]
In the list example, each value in the list is referred to as an element.
all()
Iterates on a list or map and validates that a condition is true for all elements in the list.
The following expression returns false because one of the elements in the uses list does not start with the character f:
plant.uses.all(x, x.startsWith('f'))
Output: false
append()
Adds elements to an existing list.
The following expression adds the value "barrels" to the list of uses:
append(plant.uses, 'barrels')
Output: ["lumber","firewood","furniture","barrels"]
collect()
Returns a list of map values which match the provided path argument.
The following expression returns the values for the name key in the related_to list:
plant.traits.related_to.collect('name')
Output: ["chestnut","beech"]
contains()
Returns true if any element in the list matches the provided argument (case-sensitive). An optional second argument of true causes the match to occur by ignoring the case.
The following expression returns false because the uses list does not have an element that matches the value Nit (case sensitive):
plant.uses.contains('Nit')
Output: false
The following expression returns true because the uses list does have an element that matches the value nit while ignoring case:
plant.uses.contains('nit', true)
Output: true
count()
Returns a count of the list elements that match the provided string argument.
The following expression returns a count for the name key in the related_to list:
count(plant.traits.related_to, 'name')
Output: 2
exists()
Iterates on a list or map and validates that a condition is true for at least one of the elements.
The following expression returns true because lumber is in the uses list:
plant.uses.exists(x, x == 'lumber')
Output: true
The following expression returns false because an element with the name apple is not found in the related_to list:
plant.traits.related_to.exists(x, x.name == 'apple')
Output: false
exists_one()
Iterates on a list or map and validates that a condition is true for exactly one of the elements.
The following expression returns false because more than one element in the uses list starts with the character f:
plant.uses.exists_one(x, x.startsWith('f'))
Output: false
filter()
Iterates on a list and returns the elements that match the provided criteria.
The following expression filters the uses list and return a new list with the values "firewood" and "furniture" because both values start with the character f:
plant.uses.filter(x, x.startsWith('f'))
Output: ["firewood","furniture"]
flatten()
Returns a list where all nested lists are combined into a single, top-level list.
The following expression combines the nested lists into a single non-nested list:
flatten([["a","b","c"],"x","y","z","a"])
Output: ["a","b","c","x","y","z","a"]
format()
Returns the string representation of the timestamp using the provided format. For a list of supported formats, see Supported Formats.
The following expression returns just the year, month, and day from the current timestamp:
now().format('dateonly')
Output: 2024-05-01
groupBy()
Returns a list of map elements grouped by one or more paths and a corresponding count of the grouping. The first argument to the macro is the list to group. The second argument to the macro is a list of paths to group by. The third optional argument sorts the list by ascending (asc) or descending (desc) order (default is ascending).
The following expression evaluates the related_to list and groups the elements by the genus value in descending order:
groupBy(plant.traits.related_to, ['genus'], `desc`)
Output:
[
{
"genus": "Fagus",
"count": 1
},
{
"genus": "Castanea",
"count": 1
}
]
has()
Validates that a key exists/is defined/has a non-null value. This macro also supports checking a map for one or more paths. An optional third argument exists for the separator in the paths.
The following expression returns true because the variable/path plant.traits.produces_fruit exists:
has(plant, 'traits.produces_fruit')
Output: true
join()
Combines the elements of a list into a string using the provided (optional) separator. The default separator is a comma character.
The following expression combines the elements of the uses list into a string separated by a comma character:
join(plant.uses)
Output: "lumber, firewood, furniture"
keys()
Returns a list of top level keys from a map.
The following expression returns a list consisting of the keys from the traits map:
keys(plant.traits)
Output: ["produces_fruit","genus","height","extinct","related_to"]
map()
Iterates on a list and transforms the elements as defined.
The following expression transforms each element by making it uppercase and returns a new list with the values "LUMBER", "FIREWOOD", and "FURNITURE":
plant.uses.map(x, x.toUpper())
Output: ["LUMBER","FIREWOOD","FURNITURE"]
merge()
Adds elements to an existing map.
The following expression adds a new element to the existing traits map:
merge(plant.traits, {'leaf_type':'simple'})
Output:
"traits": {
"produces_fruit": yes,
"genus": "Quercus",
"height": 100,
"extinct": false,
"related_to": [
{
"name": "chestnut",
"genus": "Castanea"
},
{
"name": "beech",
"genus": "Fagus"
}
],
"leaf_type": "simple"
}
sort()
Returns a copy of the provided list sorted in ascending order. The sort order may be reversed to descending by specifying the value desc as the second argument.
The following expression sorts the uses list in ascending order:
sort(plant.uses)
Output: ["firewood","furniture","lumber"]
The following expression sorts the uses list in descending order:
sort(plant.uses, 'desc')
Output: ["lumber","furniture","firewood"]
toLower()
Returns a copy of the list with all elements converted to lowercase.
The following expression returns the existing elements in the locations list with all lowercase values:
toLower(plant.locations)
Output: ["usa","europe","new york","new york","new york ","usa","worldwide","eu"]
toUpper()
Returns a copy of the list with all elements converted to uppercase.
The following expression returns the existing elements in the uses list with all uppercase values:
toUpper(plant.uses)
Output: ["LUMBER","FIREWOOD","FURNITURE"]
trim()
Returns a copy of the list with leading and trailing whitespace characters removed from all the elements in the list.
The following expression returns the existing elements in the locations list with the leading and trailing whitespace characters removed.
trim(plant.locations)
Output: ["usa","europe","new york","New York","new york","usa","worldwide","eu"]
unique()
Returns a copy of the list with any duplicate elements removed. Note only duplicate elements that are exactly the same (case-sensitive) are removed.
The following expression returns the locations list with the duplicate elements removed:
unique(plant.locations)
Output: ["usa","europe","new york","New York","new york ","worldwide","eu"]
Macros for working with strings
The following CEL macros are formulated for working with strings.
contains()
Returns true if a string matches the provided substring. An optional list of substrings can be provided as the first argument. An optional second argument can be provided to ignore case when matching (default is false).
The following expression returns true because the string test contains the substring est:
'test'.contains('est')
Output: true
The following expression returns true because the string test contains the substring est:
'test'.contains(['est','no'])
Output: true
The following expression returns true because the string test contains the substring EST when the optional ignore case value is set:
'test'.contains(['EST'], true)
Output: true
decodeBase64()
Returns a decoded base64 input string.
The following expression returns a string after decoding it using base64:
decodeBase64('dGVzdD0xMjM=')
Output: "test=123"
decodeJSON()
Returns a json object after decoding the input string.
The following expression returns a json object representing the input string (encoded json):
decodeJSON('{"a":["b","c"]}')
Output:
{
"a": [
"b",
"c"
]
}
encodeBase64()
Returns an encoded string input as a base64 string.
The following expression returns a base64 encoded string:
encodeBase64('test=123')
Output: "dGVzdD0xMjM="
encodeJSON()
Returns an encoded string input as a json string.
The following expression returns the plant.uses list as a json encoded string:
encodeJSON(plant.uses)
Output: "['lumber','firewood','furniture']"
generateString()
Returns a random generated string having the length of the first argument and using the characters/alphabet from the second argument.
The following expression returns a random string that is 5 characters long and only contains the characters a, b, c, 1, 2, or 3:
generateString(5, 'abc123')
Output: 31a3c
ipInNetwork()
Returns true if the first argument IP address is in one or more of the second argument IP network ranges. The second argument is represented as a list of networks in CIDR notation.
The following expression returns true because the string is in the provided network range:
ipInNetwork('192.168.0.1',['192.168.0.0/16'])
Output: true
isDomain()
Returns true if the provided string argument represents a valid domain.
The following expression returns true because the string is a valid domain:
isDomain('secureworks.com')
Output: true
isEmail()
Returns true if the provided string argument represents a valid email address.
The following expression returns true because the string is a valid email address:
isEmail('noreply@secureworks.com')
Output: true
isIP()
Returns true if the provided string argument represents a valid IPv4 address.
The following expression returns true because the string is a valid IP:
isIP('192.168.0.1')
Output: true
isPrivateIP()
Returns true if the provided string argument represents a private (RFC-1918), link-local, or loopback IPv4 address.
The following expression returns false because the string represents a public IP address:
isPrivateIP('8.8.8.8')
Output: false
isURL()
Returns true if the provided string argument represents a valid Uniform Resource Locator (URL).
The following expression returns true because the string is a valid URL:
isURL('https://www.secureworks.com')
Output: true
isUUID()
Returns true if the provided string argument represents a valid Universally Unique Identifier (UUID).
The following expression returns true because the string is a valid UUID:
isUUID('ce53ce61-0745-4b9b-ad16-568a022b6002')
Output: true
matchGroup()
Returns an array of strings from the provided regex capture group(s).
The following expression returns a list representing the capture groups from the regex:
'red,blue:green'.matchGroup('(blue).(green)'
Output:["blue:green","blue","green"]
md5sum()
Returns the computed md5 digest for the provided string.
The following expression computes the md5 digest for the string input using the toHex() macro to return the common string representation:
md5sum('test').toHex()
Output: "098f6bcd4621d373cade4e832627b4f6"
parseURL()
Returns the provided URL string as a URL map structure.
The following expression returns a URL map structure representing the URL string:
parseURL('https://www.example.com')
Output:
{
"ForceQuery": false
"Fragment": ""
"Host": "www.example.com"
"OmitHost": false
"Opaque": ""
"Path": ""
"RawFragment": ""
"RawPath": ""
"RawQuery": ""
"Scheme": "https"
"User": {
"Password": ""
"Username": ""
}
}
sha1sum()
Returns the computed sha1 digest for the provided string.
The following expression computes the sha1 digest for the string input using the toHex() macro to return the common string representation:
sha1sum('test').toHex()
Output: "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"
sha256sum()
Returns the computed sha256 digest for the provided string.
The following expression computes the sha256 digest for the string input using the toHex() macro to return the common string representation:
sha256sum('test').toHex()
Output: "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
sha512sum()
Returns the computed sha512 digest for the provided string.
The following expression computes the sha512 digest for the string input using the toHex() macro to return the common string representation:
sha512sum('test').toHex()
Output: "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"
toHTML()
Returns the provided string as HTML.
The following expression converts the string in markdown to HTML:
'## This is an H2'.toHTML()
Output: "<h2>This is an H2</h2>"
toLower()
Returns a copy of the string with all characters converted to lowercase.
The following expression converts the string Test to test:
'Test'.toLower()
Output: "test"
toString()
Returns the provided value of any data type as a string.
The following expression returns a string representation of the provided list:
toString([1,2,3])
Output: "[1,2,3]"
toTable()
Returns a string representing the provided data as a text/markdown table. This macro requires four parameters:
- A list of data where each row in the table is represented by a list
- A list of headers for the table
- A list of footers for the table
- Boolean to output as markdown or not
The following expression generates a text table with two columns and two rows with no headers or footers:
toTable([['row1_column1','row2_column2'],['row2_column1','row2_column2']],[],[],false)
Output:
+--------------+--------------+
| row1_column1 | row2_column2 |
| row2_column1 | row2_column2 |
+--------------+--------------+
toTimestamp()
Returns a timestamp from a date and time string.
The following expression returns a timestamp from the date string 2000/01/01:
'2000/01/01'.toTimestamp()
Output: 2000-01-01T00:00:00Z
toTitle()
Returns a copy of the string with the first letter of each word converted to uppercase.
The following expression converts the string new york to New York:
'new york'.toTitle()
Output: "New York"
toUpper()
Returns a copy of the string with all characters converted to uppercase.
The following expression converts the string test to TEST:
'test'.toUpper()
Output: "TEST"
toURLQuery()
Returns a copy of the string with all URL special characters converted to escape sequences.
The following expression returns the string param=1 as an escaped URL string:
'param=1'.toURLQuery()
Output: "param%3D1"
userInDomain()
Returns true if the provided username argument is in one or more of the provided domains.
The following expression returns true because the username is a part of the provided domains:
userInDomain('noreply@secureworks.com', ['secureworks.com','secureworks.net'])
Output: true
Macros for working with detection data
The following CEL macros are formulated for working with detection data.
alertAttackTechniqueIds()
Parses a detection and returns the attack technique IDs value.
The following expression returns a list of attack technique IDs found in the detection:
alertAttackTechniqueIds(inputs)
Output: ['T1059', 'T1057']
alertConfidence()
Parses a detection and returns the confidence value.
The following expression returns the confidence value from the detection:
alertConfidence(inputs)
Output: 0.1
alertCreatedAtNanos()
Parses a detection and returns the nanoseconds value of the time the detection was created.
The following expression returns the nanoseconds from the detection creation time:
alertCreatedAtNanos(inputs)
Output: 1678899090
alertCreatedAtSeconds()
Parses a detection and returns the created_at value as a measure of seconds from epoch.
The following expression returns the time (in seconds) the detection was created:
alertCreatedAtSeconds(inputs)
Output: 1697207995554
alertDescription()
Parses a detection and returns the description value.
The following expression returns the detection description:
alertDescription(inputs)
Output: "This is a sample Taegis Watchlist Detection"
alertDestinationIPs()
Parses a detection and returns a unique list of IP address values from the detection entities field where the entity is labeled destinationIPAddress (case-insensitive).
The following expression returns a list of destination IPs found in the detection:
alertDestinationIPs(inputs)
Output: ['192.168.0.1', '192.168.0.2']
alertDetectorId()
Parses a detection and returns the detector ID value.
The following expression returns the detector ID found in the detection:
alertDetectorId(inputs)
Output: "267658fe-65f1-4145-8753-d45fbf9ed6d3"
alertDetectorName()
Parses a detection and returns the detector name value.
The following expression returns the detector name found in the detection:
alertDetectorName(inputs)
Output: "Taegis Watchlist"
alertDomains()
Parses a detection and returns a unique list of domain name values from the detection entities field where the entity is labeled ipdomain, topprivateipdomain, domainname, authdomainname, sourceauthdomainname, or targetauthdomainname (case-insensitive).
The following expression returns a list of domains found in the detection:
alertDomains(inputs)
Output: ['example.com', 'a.example.com']
alertEnrichment()
Parses a detection and its enrichment data and returns the first value matching the path provided.
The following expression returns the enrichment data associated with the rareprogram_rare_ip.programs path:
alertEnrichment(inputs, 'rare_program_rare_ip.programs')
Output: ['foo.exe', 'bar.exe']
alertEntities()
Parses a detection and returns the entities value.
The following expression returns a list of entities found in the detection:
alertEntities(inputs)
Output: ['hostname:abc', 'sensorId:12345', 'fileName:c:\windows\syswow64\cmd.exe']
alertEntity()
Parses a detection and returns the entity values which match the provided entity name (ignores case).
The following expression returns a list of entity values that have the username entity name in the detection:
alertEntity(inputs, 'username')
Output: ['bob','sally']
alertEventIds()
Parses a detection and returns a list of event ID values.
The following expression returns a list of event IDs found in the detection:
alertEventIds(inputs)
Output: ['29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae', 'aef81a33-fe5c-43fe-b589-c5ff8c3cce1c']
alertGroupKey()
Parses a detection and returns the group_key value.
The following expression returns the group key found in the detection:
alertGroupKey(inputs)
Output: "12345:app:event-filter:80c0809b-153f-4b81-bb7c-52fcb83c7127"
alertHostnames()
Parses a detection and returns a unique list of values from the detection entities field where the entity is labeled hostname, sourcehostname, desthostname, workstationname, or computername (case-insensitive).
The following expression returns a list of hostnames found in the detection:
alertHostnames(inputs)
Output: ['sample_hostname', 'another_sample_hostname']
alertId()
Parses a detection and returns the ID or UUID.
The following expression returns the ID from the detection:
alertId(inputs)
Output: "alert://priv:endpoint-redcloak:12345:1678899090095:29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae"
alertInvestigationIds()
Parses a detection and returns a list of case IDs associated with the detection.
The following expression returns a list of case IDs associated with the detection:
alertInvestigationIds(inputs)
Output: ['29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae', 'aef81a33-fe5c-43fe-b589-c5ff8c3cce1c']
alertIPs()
Parses a detection and returns a unique list of IP address values from the detection entities field where the entity is labeled ipAddress (case-insensitive).
The following expression returns a list of IP addresses found in the detection:
alertIPs(inputs)
Output: ['192.168.0.1', '192.168.0.2']
alertMitreAttackInfo()
Parses a detection and returns a list of mitre_attack_info values.
The following expression returns a list of mitre_attack_info values found in the detection:
alertMitreAttackInfo(inputs)
Output:
[
{
"description": "Adversaries may attempt...",
"platform": [
"Linux",
"macOS"
],
"tactics": [
"discovery"
],
"technique": "Process Discovery",
"technique_id": "T1057",
"type": "Enterprise ATT&CK"
}
]
alertObservationIds()
Parses a detection and returns a list of observation ID values.
The following expression returns a list of observation IDs found in the detection:
alertObservationIds(inputs)
Output: ['29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae', 'aef81a33-fe5c-43fe-b589-c5ff8c3cce1c']
alertReferences()
Parses a detection and returns a list of references associated with the detection.
The following expression returns a list of references from the detection:
alertReferences(inputs)
Output:
[
{
"description": "External Alert Ref",
"url": https://example.com/alert/29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae"
}
]
alertResolution()
Parses a detection and returns the resolution value.
The following expression returns the resolution value from the detection:
alertResolution(inputs)
Output: "open"
alertResolutionReason()
Parses a detection and returns the resolution reason value.
The following expression returns the resolution reason value from the detection:
alertResolutionReason(inputs)
Output: "Valid activity for this user."
alertRuleId()
Parses a detection and returns the rule ID.
The following expression returns the rule ID found in the detection:
alertRuleId(inputs)
Output: "267658fe-65f1-4145-8753-d45fbf9ed6d3"
alertSensorIds()
Parses a detection and returns a list of sensor ID values.
The following expression returns a list of sensor ID values found in the detection:
alertSensorIds(inputs)
Output: ['12345', '1234-12345-123']
alertSensorTypes()
Parses a detection and returns a list of unique sensor type values (in uppercase).
The following expression returns a list of sensor types found in the detection:
alertSensorTypes(inputs)
Output: ['ENDPOINT_REDCLOAK', 'ENDPOINT_TAEGIS']
alertSeverity()
Parses a detection and returns the severity value.
The following expression returns the severity of the detection expressed as a floating point number:
alertSeverity(inputs)
Output: 0.8
alertSeverityNice()
Parses a detection and returns the human-friendly severity value as a word (Informational, Low, Medium, High, Critical).
The following expression returns the severity of the detection as a string:
alertSeverityNice(inputs)
Output: "High"
alertSourceIPs()
Parses a detection and returns a unique list of IP address values from the detection entities field where the entity is labeled sourceIPAddress (case-insensitive).
The following expression returns a list of source IPs found in the detection:
alertSourceIPs(inputs)
Output: ['192.168.0.1', '192.168.0.2']
alertStatus()
Parses a detection and returns the status value.
The following expression returns the status value from the detection:
alertStatus(inputs)
Output: "open"
alertTags()
Parses a detection and returns a list of tags.
The following expression returns a list of tags found in the detection:
alertTags(inputs)
Output: ['alertRule:29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae', 'compactor:handler']
alertTenantId()
Parses a detection and returns the tenant ID.
The following expression returns the tenant ID from the detection:
alertTenantId(inputs)
Output: "12345"
alertThirdPartyDetail()
Parses a detection and third party detail data and returns the first value matching the path provided.
The following expression returns the third party detail data associated with the userStates.0.aadUserId path:
alertThirdPartyDetail(inputs, 'userStates.0.aadUserId')
Output: ['F86DBD0D-6571-44A0-BAE1-43B83CF430AD']
alertTitle()
Parses a detection and returns the title value.
The following expression returns the detection title:
alertTitle(inputs)
Output: "Taegis Watchlist Alert"
alertUpdatedAtNanos()
Parses a detection and returns the nanoseconds value of the time the detection was modified.
The following expression returns the nanoseconds from the detection modified time:
alertUpdatedAtNanos(inputs)
Output: 1678899090
alertUpdatedAtSeconds()
Parses a detection and returns the updated_at value as a measure of seconds from epoch.
The following expression returns the time (in seconds) the detection was modified:
alertUpdatedAtSeconds(inputs)
Output: 1697207995554
alertUsernames()
Parses a detection and returns a unique list of lowercase username values from the detection entities field where the entity is labeled username (case-insensitive).
The following expression returns a list of usernames found in the detection:
alertUsernames(inputs)
Output: ['sample_user', 'another_sample_user']
Macros for working with other data
The following CEL macros are formulated for working with various other forms of data (assets, cases, entities, and time).
entityValue()
Parses an entity record and returns a list of values for the provided entity property.
The following expression returns a list of the values associated with user_name property:
entityValue(inputs, 'user_name')
Output: ["john"]
entityValues()
Parses an entity record and returns a list of values associated with the entity.
The following expression returns a list of the values associated with an entity:
entityValues(inputs)
Output: ["example.com", "john@example.com", "john"]
investigationFieldChanged()
Parses a case record and returns true if the provided field was modified.
The following expression returns true if the case priority was modified:
investigationFieldChanged(inputs, 'priority')
Output: true
investigationPriority()
Parses a case record and returns the priority of the case as a word (Low, Medium, High, Critical).
The following expression returns the case priority as a word:
investigationPriority(inputs)
Output: "High"
now()
Returns the current local time as a timestamp.
The following expression returns the current time of the local system (in this example, UTC - 5):
now()
Output: "2023-10-13T14:06:47.059012-05:00"
nowUnixMilli()
Returns the current time as the number of seconds since epoch.
The following expression returns the number of milliseconds since epoch of the local system as an integer:
nowUnixMilli()
Output: 1697224173109
random()
Returns a random value between 0 and .99 (inclusive).
The following expression returns a random float (double) between 0 and .99:
random()
Output: 0.2