Sekoia Operating Language (SOL)
Power and Simplicity in Security Analytics
Sekoia Operating Language (SOL
) is a powerful, pipe-based query language designed specifically for modern security operations. Built with similarities to KQL
(Kusto Query Language) and SQL
, SOL
combines familiar syntax with advanced security-focused capabilities to deliver exceptional performance when analyzing massive security datasets.
Datasources
SOL
provides seamless access to all critical security data within Sekoia SOC platform:
Data Source | Description | Use Cases |
---|---|---|
events |
Security events | Threat hunting, incident investigation, SOC reporting |
eternal_events |
Security events related to alerts or cases | Extract metrics from events related to alerts/cases |
alerts |
Security alerts and detections | SOC monitoring, alert pattern analysis |
cases |
Security incidents and cases | Case management, incident correlation |
intakes |
Data sources | Data source management, volume monitoring |
entities |
Company entities | Entity tracking, detailed reporting |
communities |
Multi-tenant communities (if applicable) | Cross-organization analysis |
Alerts properties
Alert Property | Description |
---|---|
uuid | A unique identifier for the alert. |
short_ID | A concise identifier for quick reference to the alert. |
community_uuid | A unique identifier for the community the alert belongs to. |
entity_uuid | A unique identifier representing the entity associated with the alert. |
entity_name | The name of the entity linked to the alert. |
rule_name | The name assigned to the rule that triggered the alert. |
rule_pattern | The detection pattern of the alert. |
detection_type | The method by which the alert was detected. |
alert_type_category | The category of the alert. |
alert_type_value | The type of the alert. |
status | The current state of the alert (e.g., open, acknowledged, resolved). |
urgency | The level of urgency assigned to the alert. |
created_at | The date and time when the alert was initially created. |
update_at | The date and time when the alert was last updated. |
first_seen_at | The date and time of the first alert occurrence. |
last_seen_at | The date and time of the last alert occurrence. |
time_to_detect | Duration taken to identify the alert from its occurrence in seconds. |
time_to_acknowledge | Time elapsed from detection to official acknowledgment of the alert in seconds. |
time_to_respond | Duration taken to take action after acknowledgment in seconds. |
time_to_resolve | The total time taken to completely resolve the alert in seconds. |
time_to_ingest | The duration from alert generation to its final ingestion into the system in seconds. |
occurrences | The number of alert occurrences |
rule_instance_uuid | A unique identifier for the rule that generated the alert. |
cases | List of cases associated to the alert. |
assets | List of assets associated to the alert. |
threats | List of threats associated the alert. |
Cases properties
Property | Description |
---|---|
uuid | A unique identifier for the case. |
short_id | A concise identifier for quick reference to the case. |
community_uuid | A unique identifier for the community related to the case. |
title | The title or subject line of the case. |
description | A detailed description outlining the case's context or issues. |
priority | The importance level assigned to the case, indicating its urgency. |
created_at | The date and time when the case was created. |
created_by | The user or system that created the case. |
created_by_type | The type of entity that created the case (e.g., user, automated system). |
updated_at | The date and time when the case was last updated. |
updated_by | The user or system that last updated the case. |
updated_by_type | The type of user that last updated the case. |
first_seen_at | The date and time when the case was first detected. |
last_seen_at | The date and time when the case was last observed or updated. |
Entities properties
Property | Description |
---|---|
uuid | A unique identifier for the entity. |
name | The name of the entity. |
alerts_generation | The alert generation mode of the entity. |
description | The description of the entity. |
entity_id | The ID of the entity. |
community_uuid | A unique identifier for the community related to the entity. |
created_at | The date and time when the entity was created. |
updated_at | The date and time when the entity was last updated. |
Intakes properties
Property | Description |
---|---|
uuid | A unique identifier for the intake. |
name | The name of the intake. |
community_uuid | A unique identifier for the community related to the intake. |
entity_uuid | A unique identifier for the entity related to the intake. |
format_uuid | A unique identifier for the format related to the intake. |
intake_key | The intake key of the intake. |
created_at | The date and time when the intake was created. |
created_by | The user or system that created the intake. |
created_by_type | The type of entity that created the intake (e.g., avatar, apikey). |
updated_at | The date and time when the intake was last updated. |
updated_by | The user or system that last updated the intake. |
updated_by_type | The type of user that last updated the intake. |
is_custom_format | Indicate if the intake uses a custom format. |
connector_configuration_uuid | A unique identifier for the connector configuration related to the intake. |
Communities properties
Property | Description |
---|---|
uuid | A unique identifier for the community. |
name | The name of the community. |
description | The description of the community. |
homepage_url | The homepage url of the community. |
picture_mode | The picture mode of the community. |
intake_key | The intake key of the community. |
created_at | The date and time when the community was created. |
created_by | The user or system that created the community. |
created_by_type | The type of entity that created the community (e.g., avatar, apikey). |
updated_at | The date and time when the community was last updated. |
company_size | The size of the company. |
company_security_team_size | The size of the security team. |
company_sector | The sector of the company. |
company_location | The location of the company. |
is_parent | Indicate if the community is a parent community. |
parent_uuid | A unique identifier of the parent community. |
subcommunities | Indicate if the community has subcommunities. |
is_mfa_enforced | Indicate if MFA is enforced at the community level. |
session_timeout | The duration before users are automatically logged after inactivity. |
disable_inactive_avatars | Indicate if users are disabled after 90 days of inactivity. |
disabled | Indicate if the community is disabled. |
Operators
The next sections describe the different operators supported by Sekoia Operating Language (SOL
).
Count rows
Description
Use the count
operator to count the number of rows returned by the statement.
<table name>
| count
Example
Count the number of rows in the events
table
events
| where timestamp > ago(30m)
| count
Select columns
Description
Use the select
operator to define the columns to retrieve from the table. The order of the columns defined in the query will define the order of the columns in the results.
<table name>
| select <column name 1>, <column name 2>
Example
Select the columns host.name and source.ip from the events
table
events
| select host.name, source.ip
| limit 100
Distinct
Description
Use the distinct
operator to list all the unique values of a column.
<table name>
| distinct <column name>
Example
List the unique values of client.ip from the events table
events
| where timestamp > ago(24h)
| distinct client.ip
| limit 100
Where
Description
Use the where
operator to filter rows by a list of conditions. Use parenthesis and keywords and
, or
to define complex conditions.
<table name>
| where <conditions>
Example 1
Filter the query by excluding events older than 5 days
and retrieving only user agent from Mac
events
| where timestamp > ago(5d) and user_agent.device.name == 'Mac'
| limit 100
Example 2
Filter the query by excluding events older than 5 days
and retrieving only user agent from Mac
or Android
events
| where timestamp > ago(5d) and (user_agent.device.name == 'Mac' or user_agent.device.name == 'Android')
| limit 100
Example 3
Same as example 2 but with multiple where
statements
events
| where timestamp > ago(5d)
| where user_agent.device.name == 'Mac' or user_agent.device.name == 'Android'
| limit 100
Sort results
Description
Use the order by
operator to sort rows by a column. The default sort order is descending.
<table name>
| order by <column name> <desc | asc>
Example 1
Order the rows by the timestamp column in ascending order
events
| order by timestamp asc
| limit 100
Example 2
Order alerts by descending urgency and ascending first_seen_at
alerts
| order by urgency desc, first_seen_at asc
| select short_id, rule_name, urgency, first_seen_at
| limit 100
Limit results
Description
Use the limit
operator to retrieve the last n number of rows based on the current sort order.
<table name>
| limit <integer>
Example
Get 1000
events from events
table
events
| limit 1000
Get the Top n rows
Description
Use the top
operator to returns the first n rows sorted by the specified column.
<table name>
| top <integer> by <column name> [asc | desc]
Example
Get the top 5
alerts with the most occurrences from alerts
table in the last 7 days
alerts
| where created_at > ago(7d)
| top 5 by occurrences
Note that the query below is equivalent.
alerts
| order by occurrences desc
| limit 5
Create calculated columns
Description
You can use select
or extend
operators to create calculated columns.
Use select
to specify the columns to display. When using extend
, the calculated column is appended to the end of the table.
<table name>
| select <new column name> = <column name 1> + <column name 2>
Example
Create a calculated column named total that sums the time_to_detect
, time_to_respond
and time_to_resolve
values
alerts
| select total = time_to_detect + time_to_respond + time_to_resolve
| limit 100
Aggregate rows
Description
Use the aggregate
operator to group rows by a column and perform aggregations with a chosen function: count
, sum
, min
, max
, avg
, count_distinct
, make_set
.
<table name>
| aggregate <function> by <column name>
Example 1
Count the number of events per asset in the events
table
events
| aggregate count() by sekoiaio.any_asset.name
| limit 100
Note that you can specify a column name for the aggregation. In the example below, the column name is defined as total
.
events
| aggregate total = count() by sekoiaio.any_asset.name
| limit 100
Example 2
Count the number of events per source.ip and per action.outcome in the events
table
events
| where timestamp >= ago(24h) and event.category == 'authentication'
| aggregate count() by source.ip, action.outcome
Example 3
Sum the values of 'time_to_detect' column in the alerts
table
alerts
| aggregate sum(time_to_detect)
| limit 100
Example 4
Retrieve the minimum value of 'time_to_detect' column in the alerts
table
alerts
| aggregate min(time_to_detect)
| limit 100
Example 5
Retrieve the maximum value of 'time_to_detect' column in the alerts
table
alerts
| aggregate max(time_to_detect)
| limit 100
Example 6
Calculate the average value of 'time_to_detect' column in the alerts
table
alerts
| aggregate avg(time_to_detect)
| limit 100
Example 7
Count unique values of 'source.ip' column in the events
table
events
| aggregate count_distinct(source.ip)
| limit 100
Example 8
Create an array of the set of distinct values of 'source.ip' column in the events
table.
Note that null
values are ignored.
events
| aggregate make_set(source.ip)
| limit 100
Render results in chart
Description
Use the render
operator to display results in a chart to identify more easily anomalies or outliers. Supported charts are:
number
piechart
columnchart
barchart
linechart
<table name>
| aggregate <function> by <column name>
| render <chart_type> with (x=<column name>, y=<column name>, breakdown_by=<column name>, mode=<grouped | stacked>)
Example
Count the number of events per asset in the events table and render it in a bar chart.
events
| aggregate count() by sekoiaio.any_asset.name
| render barchart with (y=sekoiaio.any_asset.name)
| limit 100
Join tables
Description
Use the join
operator to combine data from multiple tables, enriching the data context, filtering more accurately data.
Available join
types are:
- inner join: Returns records that have matching values in both tables (default)
- left join: Returns all records from the left table, and the matched records from the right table
<left table name>
| inner join <right table name> on <left column name> == <right column name>
| select <model object>.<right column name>, <left column name>
When performing join
, the right table is injected into a model
object.
This model
object (similar to a class Object in code development) contains a set properties. Each property
represents a column
of the original table.
Info
By convention, we consider that when using join
, the left part of the statement represents the column of the left table and the right part of the statement represents the column of the right table.
Example 1
Join the tables events and intakes
events
| where timestamp > ago(24h)
| limit 100
| inner join intakes on sekoiaio.intake.uuid == uuid // sekoiaio.intake.uuid belongs to events table and uuid belongs to intakes table
| distinct intake.name
The model
object default name is related to the table name it is originating from. In this case, the model name is intake
since the join was performed on the intakes
table.
Example 2
Join the tables alerts and entities
alerts
| where created_at > ago(24h)
| limit 100
| inner join entities on entity_uuid == uuid // entity_uuid belongs to alerts table and uuid belongs to entities table
| distinct entity.name
The model
object default name is related to the table name it is originating from. In this case, the model name is entity
since the join was performed on the entities
table.
Define model object name
In this example, we define a specific name for the model object with the into operator.
alerts
| where created_at > ago(24h)
| inner join entities on entity_uuid == uuid into my_entity
| select my_entity.name
Lookup
Description
Use the lookup
operator to extend a table. Extends the current table with values looked-up in another table.
Prefer the lookup
operator over join
when the right table is small enough to fit into memory to improve query performance.
Info
The result doesn't repeat columns from the right
table that are the basis for the join operation.
The lookup
operator only supports left join
.
<left table name>
| lookup <right table name> on <left column name> == <right column name>
| aggregate <function> by <column name>
| order by <column name>
Similarly to join
operator, lookup
will inject the right table into a model
object.
Compare
Description
Use the following operators to compare values.
Comparator | Description | Examples |
---|---|---|
== | Equals | 1 == 1 |
!= | Not equals | 1 != 0 |
< | Less | 1 < 10 |
<= | Less or equals | 4 <= 5 |
> | Greater | 20 > 10 |
>= | Greater or equals | 5 >= 4 |
In
Description
Use the in
operator to filter the rows based on a set of case-sensitive strings.
<table name>
| where <column name> in [<value 1>, <value 2>]
Example
Find events where client.ip
equals to theses values: 192.168.0.1, 192.168.0.2.
events
| where client.ip in ['192.168.0.1', '192.168.0.2']
| limit 100
Contains
Description
Use the contains
operator to filter the rows that contains a case-sensitive string.
<table name>
| where <column name> contains <value 1>
Example
Find events where user.full_name
contains the string Admin
.
events
| where user.full_name contains 'Admin'
| limit 100
Starts with
Description
Use the startswith
operator to filter rows that starts with a case-sensitive string.
<table name>
| where <column name> startswith <pattern>
Example
Find events where url.domain
starts with the string api.prod
.
events
| where url.domain startswith 'api.prod'
| limit 100
Ends with
Description
Use the endswith
operator to filter rows that ends with a case-sensitive string.
<table name>
| where <column name> endswith <pattern>
Example
Find events where url.path
ends with the string /admin
.
events
| where url.path endswith '/admin'
| limit 100
Variables
Description
Use the let
operator to define variables.
let <variable name> = <string | integer>;
<table name>
| where <column name> == <variable name>
Example
Count the number of events in the last 24 hours.
let StartTime = ago(24h);
let EndTime = now();
events
| where event.created > StartTime and event.created <= EndTime
| count
Comments
Description
Use //
to add comments in the query.
Example
// Comment the filtering condition
<table name>
//| where <column name> = <variable name>
| limit 100
Functions
Datetime: now()
Description
Returns the current UTC time, optionally offset by a given timespan.
Example
let time = now();
let time_earlier = now(-2d);
Datetime: ago()
Description
Returns a datetime value equal to the current UTC time minus the timespan.
Syntax | Description | Example | Length of time |
---|---|---|---|
d | day time interval | 2d |
2 days |
h | hour time interval | 1h |
1 hour |
m | minute time interval | 30m |
30 minutes |
s | second time interval | 10s |
10 seconds |
Example
let time = ago(1h);
Timestamp: bin()
Description
Rounds values down to an integer multiple of a given bin size.
Example
events
| aggregate count() by bin(timestamp, 1d)
Year
Description
Returns the year by a given date in the following format: YYYY
.
Example
let time = year(now());
Month
Description
Returns the year and month by a given date in the following format: YYYY-MM
.
Example
let time = month(now());
Week
Description
Returns the year and month by a given date in the following format: YYYY - Week {week number}
.
Example
let time = week(now());
Join examples
Join between events and communities tables (for Multi-tenant)
events
| where timestamp > ago(5m)
| limit 100
| join communities on sekoiaio.customer.community_uuid == uuid
| select timestamp, sekoiaio.customer.community_uuid, community.name
Join between events and entities tables
events
| limit 100
| lookup entities on sekoiaio.entity.uuid == uuid
| aggregate count=count() by entity.name
| select entity.name, count
Join between alerts and communities tables (for Multi-tenant)
alerts
| aggregate count=count() by community_uuid
| join communities on community_uuid == uuid
| select community.name, community_uuid, count
| limit 100
Alerts query examples
Detection rules ranked by number of alerts
alerts
| where created_at > ago(30d)
| order by occurrences desc
| select rule_name, occurrences
Assets ranked by number of alerts
alerts
| where created_at > ago(30d)
| aggregate count=count() by assets.uuid
| order by count desc
| limit 100
Threats ranked by number of alerts
alerts
| where created_at > ago(30d)
| aggregate count=count() by threats.name
| order by count desc
| limit 100
Alerts per detection type
alerts
| where created_at > ago(30d)
| aggregate count() by detection_type
Average time to detect in last 30 days
alerts
| where created_at > ago(30d)
| aggregate avg(time_to_detect)
Rename columns and convert time_to_detect in minutes
alerts
| where time_to_detect != null
| select entity = entity_name, rule = rule_name, ttd = time_to_detect/60
| limit 10
Ranking of communities by alerts
alerts
| aggregate AlertCount = count() by community_uuid
| left join communities on community_uuid == uuid
| order by AlertCount desc
| select community.name, AlertCount
Ranking of communities by intakes
alerts
| aggregate AlertCount = count() by community_uuid
| left join communities on community_uuid == uuid
| order by AlertCount desc
| select community.name, AlertCount
Events query examples
Number of unique command lines per host.name
events
| where timestamp > ago(24h)
| aggregate count=count_distinct(process.command_line) by host.name
| order by count desc
Number of unique hostname per month
events
| where timestamp > ago(90d)
| aggregate count=count_distinct(log.hostname) by month(timestamp)
Top 10 visited URL
events
| where timestamp >= ago(24h)
| aggregate count() by url.domain
| top 10 by count
Top 10 blocked URL
events
| where timestamp >= ago(24h) and event.action == 'blocked' and user.name != null and url.domain != null
| aggregate count() by url.domain
| top 10 by count
Top 10 login failures on Windows
events
| where timestamp > ago(24h) and event.code == 4625
| aggregate failed_login_count=count() by user.target.name
| top 10 by failed_login_count
Sekoia.io endpoint agents per version
events
| where timestamp >= ago(24h)
| where sekoiaio.intake.dialect == 'sekoia.io endpoint agent'
| where event.action == 'stats'
| aggregate count_distinct(agent.id) by agent.version
List unique user.name
events
| where timestamp >= ago(24h)
| distinct(user.name)
Number of events per IP address
events
| where timestamp > ago(30d)
| aggregate count=count() by client.ip
| order by count desc
Aggregate events by source.ip and action.outcome
events
| where timestamp >= ago(24h) and event.category == 'authentication'
| aggregate count() by source.ip, action.outcome
Events where process.name starts with 'chrome'
events
| where timestamp > ago(24h) and process.name startswith('chrome')
| limit 100
Events of specific intake
events
| left join intakes on sekoiaio.intake.uuid == uuid
| where timestamp >= ago(24h) and intake.name == '<intake name>'
| limit 100
Number of defended assets: unique host.name with more than 10 events during 2 weeks in the last 30 days
events
| where timestamp > ago(30d)
| aggregate events_count = count() by host.name, week = week(timestamp)
| where events_count > 10
| aggregate week_count = count(), total_count = sum(events_count) by host.name
| where week_count >= 2
| order by total_count
| project host.name, total_count
host.os.type per Sekoia endpoint agent
events
| where sekoiaio.intake.dialect == 'sekoia.io endpoint agent'
| aggregate count() by host.os.type
| limit 100