🌙

Subscribe to the Taegis™ XDR Documentation RSS Feed at .

Learn more about RSS readers or RSS browser extensions.

Common Expression Language Macros


The Secureworks® Taegis™ XDR Automations platform can use Google’s Common Expression Language (CEL) to enable embedding logic and data manipulation within connectors, playbook inputs, playbook triggers, and templates.

XDR supports a number of Google’s Common Expression Language (CEL) macros that allow you to manipulate and evaluate data. Many macros are built-in, but some have been custom built to address common problems.

In these examples, the following data structure is used:

{
    "plant": {
        "type": "tree",
        "name": "white oak",
        "uses": [
            "lumber",
            "firewood",
            "furniture"
        ],
        "traits": {
            "produces_fruit": yes,
            "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 will return 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 will add 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 will return 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 will cause the match to occur by ignoring the case.

The following expression will return 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 will return 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 will return 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 will return true because lumber is in the uses list:

plant.uses.exists(x, x == 'lumber')

Output: true

The following expression will return 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 will return 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 will filter 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 will combine 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. See the Supported Formats for a list of 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 will sort the list by ascending (asc) or descending (desc) order (default is ascending).

The following expression will evaluate the related_to list and group the elements by the genus value in descending order:

groupBy(plant.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 will return 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 will combine 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 will return 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 will transform each element by making it uppercase and return 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 will add 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 will sort the uses list in ascending order:

sort(plant.uses)

Output: ["firewood","furniture","lumber"]

The following expression will sort 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 will return the existing elements in the locations list with all lowercase values:

toUpper(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 will return 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 will return 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 will return 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 will return 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 will return 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 will return a base64 encoded string:

encodeBase64('test=123')

Output: "dGVzdD0xMjM="

encodeJSON()

Returns an encoded string input as a json string.

The following expression will return 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 will return 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 will return 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 will compute 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 will return 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 will compute 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 will compute 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 will compute 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 will convert 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 will return 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:

The following expression will generate 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 Alerts Data

The following CEL Macros are formulated for working with alerts data.

alertAttackTechniqueIds()

Parses an alert record and returns the attack technique IDs value.

The following expression will return a list of attack technique IDs found in the alert:

alertAttackTechniqueIds(inputs)

Output: ['T1059', 'T1057']

alertConfidence()

Parses an alert record and returns the confidence value.

The following expression will return the confidence value from the alert:

alertConfidence(inputs)

Output: 0.1

alertCreatedAtNanos()

Parses an alert record and returns the nanoseconds value of the time the alert was created.

The following expression will return the nanoseconds from the alert creation time:

alertCreatedAtNanos(inputs)

Output: 1678899090

alertCreatedAtSeconds()

Parses an alert record and returns the created_at value as a measure of seconds from epoch.

The following expression will return the time (in seconds) the alert was created:

alertCreatedAtSeconds(inputs)

Output: 1697207995554

alertDescription()

Parses an alert record and returns the description value.

The following expression will return the alert description:

alertDescription(inputs)

Output: "This is a sample Taegis Watchlist Alert"

alertDestinationIPs()

Parses an alert record and returns a unique list of IP address values from the alert entities field where the entity is labeled destinationIPAddress (case-insensitive).

The following expression will return a list of destination IPs found in the alert:

alertDestinationIPs(inputs)

Output: ['192.168.0.1', '192.168.0.2']

alertDetectorId()

Parses an alert record and returns the detector ID value.

The following expression will return the detector ID found in the alert:

alertDetectorId(inputs)

Output: "267658fe-65f1-4145-8753-d45fbf9ed6d3"

alertDetectorName()

Parses an alert record and returns the detector name value.

The following expression will return the detector name found in the alert:

alertDetectorName(inputs)

Output: "Taegis Watchlist"

alertDomains()

Parses an alert record and returns a unique list of domain name values from the alert entities field where the entity is labeled ipdomain, topprivateipdomain, domainname, authdomainname, sourceauthdomainname, or targetauthdomainname (case-insensitive).

The following expression will return a list of domains found in the alert:

alertDomains(inputs)

Output: ['example.com', 'a.example.com']

alertEnrichment()

Parses an alert record and the enrichment data and returns the first value matching the path provided.

The following expression will return 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 an alert record and returns the entities value.

The following expression will return a list of entities found in the alert:

alertEntities(inputs)

Output: ['hostname:abc', 'sensorId:12345', 'fileName:c:\windows\syswow64\cmd.exe']

alertEntity()

Parses an alert record and returns the entity values which match the provided entity name (ignores case).

The following expression will return a list of entity values that have the username entity name in the alert:

alertEntity(inputs, 'username')

Output: ['bob','sally']

alertEventIds()

Parses an alert record and returns a list of event ID values.

The following expression will return a list of event IDs found in the alert:

alertEventIds(inputs)

Output: ['29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae', 'aef81a33-fe5c-43fe-b589-c5ff8c3cce1c']

alertGroupKey()

Parses an alert record and returns the group_key value.

The following expression will return the group key found in the alert:

alertGroupKey(inputs)

Output: "12345:app:event-filter:80c0809b-153f-4b81-bb7c-52fcb83c7127"

alertHostnames()

Parses an alert record and returns a unique list of values from the alert entities field where the entity is labeled hostname, sourcehostname, desthostname, workstationname, or computername (case-insensitive).

The following expression will return a list of hostnames found in the alert:

alertHostnames(inputs)

Output: ['sample_hostname', 'another_sample_hostname']

alertId()

Parses an alert record and returns the ID or UUID.

The following expression will return the ID from the alert:

alertId(inputs)

Output: "alert://priv:endpoint-redcloak:12345:1678899090095:29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae"

alertInvestigationIds()

Parses an alert record and returns a list of investigation IDs associated with the alert.

The following expression will return a list of investigation IDs associated with the alert:

alertInvestigationIds(inputs)

Output: ['29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae', 'aef81a33-fe5c-43fe-b589-c5ff8c3cce1c']

alertIPs()

Parses an alert record and returns a unique list of IP address values from the alert entities field where the entity is labeled ipAddress (case-insensitive).

The following expression will return a list of IP addresses found in the alert:

alertIPs(inputs)

Output: ['192.168.0.1', '192.168.0.2']

alertMitreAttackInfo()

Parses an alert record and returns a list of mitre_attack_info values.

The following expression will return a list of mitre_attack_info values found in the alert:

alertMitreAttackInfo(inputs)

Output:

[
    {
        "description": "Adversaries may attempt...",
        "platform": [
            "Linux",
            "macOS"
        ],
        "tactics": [
            "discovery"
        ],
        "technique": "Process Discovery",
        "technique_id": "T1057",
        "type": "Enterprise ATT&CK"
    }
]

alertObservationIds()

Parses an alert record and returns a list of observation ID values.

The following expression will return a list of observation IDs found in the alert:

alertObservationIds(inputs)

Output: ['29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae', 'aef81a33-fe5c-43fe-b589-c5ff8c3cce1c']

alertReferences()

Parses an alert record and returns a list of references associated with the alert.

The following expression will return a list of references from the alert:

alertReferences(inputs)

Output:

[
    {
        "description": "External Alert Ref",
        "url": https://example.com/alert/29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae"
    }
]

alertResolution()

Parses an alert record and returns the resolution value.

The following expression will return the resolution value from the alert:

alertResolution(inputs)

Output: "open"

alertResolutionReason()

Parses an alert record and returns the resolution reason value.

The following expression will return the resolution reason value from the alert:

alertResolutionReason(inputs)

Output: "Valid activity for this user."

alertRuleId()

Parses an alert record and returns the rule ID.

The following expression will return the rule ID found in the alert:

alertRuleId(inputs)

Output: "267658fe-65f1-4145-8753-d45fbf9ed6d3"

alertSensorIds()

Parses an alert record and returns a list of sensor ID values.

The following expression will return a list of sensor ID values found in the alert:

alertSensorIds(inputs)

Output: ['12345', '1234-12345-123']

alertSensorTypes()

Parses an alert record and returns a list of unique sensor type values (in uppercase).

The following expression will return a list of sensor types found in the alert:

alertSensorTypes(inputs)

Output: ['ENDPOINT_REDCLOAK', 'ENDPOINT_TAEGIS']

alertSeverity()

Parses an alert record and returns the severity value.

The following expression will return the severity of the alert expressed as a floating point number:

alertSeverity(inputs)

Output: 0.8

alertSeverityNice()

Parses an alert record and returns the human-friendly severity value as a word (Informational, Low, Medium, High, Critical).

The following expression will return the severity of the alert as a string:

alertSeverityNice(inputs)

Output: "High"

alertSourceIPs()

Parses an alert record and returns a unique list of IP address values from the alert entities field where the entity is labeled sourceIPAddress (case-insensitive).

The following expression will return a list of source IPs found in the alert:

alertSourceIPs(inputs)

Output: ['192.168.0.1', '192.168.0.2']

alertStatus()

Parses an alert record and returns the status value.

The following expression will return the status value from the alert:

alertStatus(inputs)

Output: "open"

alertTags()

Parses an alert record and returns a list of tags.

The following expression will return a list of tags found in the alert:

alertTags(inputs)

Output: ['alertRule:29ab783f-d3b5-4d4e-8025-9d36f4e1d2ae', 'compactor:handler']

alertTenantId()

Parses an alert record and returns the tenant ID.

The following expression will return the tenant ID from the alert:

alertTenantId(inputs)

Output: "12345"

alertThirdPartyDetail()

Parses an alert record and the third party detail data and returns the first value matching the path provided.

The following expression will return 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 an alert record and returns the title value.

The following expression will return the alert title:

alertTitle(inputs)

Output: "Taegis Watchlist Alert"

alertUpdatedAtNanos()

Parses an alert record and returns the nanoseconds value of the time the alert was modified.

The following expression will return the nanoseconds from the alert modified time:

alertUpdatedAtNanos(inputs)

Output: 1678899090

alertUpdatedAtSeconds()

Parses an alert record and returns the updated_at value as a measure of seconds from epoch.

The following expression will return the time (in seconds) the alert was modified:

alertUpdatedAtSeconds(inputs)

Output: 1697207995554

alertUsernames()

Parses an alert record and returns a unique list of lowercase username values from the alert entities field where the entity is labeled username (case-insensitive).

The following expression will return a list of usernames found in the alert:

alertUsernames(inputs)

Output: ['sample_user', 'another_sample_user']

Macros for Working with Various Other Data

The following CEL Macros are formulated for working with various other forms of data (assets, investigations, entities, and time).

entityValue()

Parses an entity record and returns a list of values for the provided entity property.

The following expression will return 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 will return a list of the values associated with an entity:

entityValues(inputs)

Output: ["example.com", "john@example.com", "john"]

investigationFieldChanged()

Parses an investigation record and returns true if the provided field was modified.

The following expression will return true if the investigation priority was modified:

investigationFieldChanged(inputs, 'priority')

Output: true

investigationPriority()

Parses an investigation record and returns the priority of the investigation as a word (Low, Medium, High, Critical).

The following expression will return the investigation priority as a word:

investigationPriority(inputs)

Output: "High"

now()

Returns the current local time as a timestamp.

The following expression will return 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 will return 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 will return a random float (double) between 0 and .99:

random()

Output: 0.2

 

On this page: