DetectionsDetection as Code

Detection as Code

We want our customers to have the ability to represent their detection as code for a variety of reasons.

  • Reviewing changes prior to deployment.
  • Easily provisioning config to multiple workspaces and environments.
  • Quick development of new configuration.
  • Rapid development and deployment.

Detection as code is facilitated through the RunReveal CLI. To get started with detection as code, you’ll need to download the CLI, and if you don’t yet have an active source with log data you’ll likely want to create a log source first.

Automate your detection workflow: Learn how to build a complete CI/CD pipeline for your detections with automated testing, validation, and deployment. See our CI/CD guide for step-by-step instructions.

There are a few major features that RunReveal supports in order to facilitate our detection as code product.

  1. Getting Started With Detection as Code
  2. Writing a detection
  3. Testing and linting detections
  4. Exporting your detections
  5. Deploying from source control

Getting Started with Detection As Code

Detection as code relies on the RunReveal CLI. Downloading the CLI can be done through brew or from our github.

brew tap runreveal/runreveal
brew install runreveal

Next you’ll need to log into RunReveal in the CLI. This command will open a browser for you to complete your log in through.

runreveal init

Detection Data Model

The field names and validation in this section match the detection schema used by the RunReveal app and API. In the RunReveal codebase, the schema is defined in the app (create detection form: app/src/validation/detectionSchemas.ts) and in the API (detection sync and create payloads: api/models/api.go, e.g. DetectionSyncRequest). When in doubt, treat those as the source of truth for casing and allowed values.

The detection data model is described as following. All fields are marked as either optional or required. This file needs to be in either yaml format or json format, either works. The example below matches Getting Started with Detection As Code; use it as a template and ensure the referenced SQL file (e.g. root_account_usage.sql) exists in the same directory when testing or uploading with the CLI.

You may also use mitreTechniques (MITRE technique IDs such as T1059.004) and tactic IDs in mitreAttacks (e.g. TA0001) where the API accepts them; exports from the CLI may include additional fields such as riskScore.

# Required: The name of the query for reference
name: root_account_usage
 
# Optional: A display name that is shown in the UI
displayName: AWS Root Account Was Used
 
# Required: The name of the file containing the sql query
file: root_account_usage.sql
 
# Optional: A description of the detection.
description: Monitor for usage of the AWS root account.
 
# Optional: Additional notes that can be attached to the detection.
notes: |-
  This is a notes field that can store info about this detection.
  It can be useful to store links to playbooks in your other systems.
  
# Optional: An array of strings to help group queries
categories:
  - aws
  
# Optional: A severity string to identify the importance of the detection results.
severity: low
 
# Optional: An array of mitre attack framework classifications. 
# This is useful when identifying attack patterns.
# Use MITRE ATT&CK tactic IDs (e.g., TA0001 for Initial Access).
mitreAttacks:
  - TA0001
  
# Optional: An array of sources that this detection is used for.
# This can be useful to help identify which sources are lacking coverage.
sourceTypes:
  - cloudtrail
  
# Required: A cron string that identifies how often this detection will execute.
schedule: '*/15 * * * *'
 
# Optional (default: false): If true will disable this detection from running on its schedule
disabled: false
 
# Optional: Key/Value pairs that are replaced when the query executes. 
parameters:
  userAgent: AWS Internal
 
# Optional: An array of notification channel names that should receive an alert if this detection triggers
notificationNames:
  - email
  
# Optional: A notification template name to override the default channel template if this detection triggers.
notificationTemplate: aws_template
 
# Optional: Enable automatic investigation creation and AI agent triage when this detection fires.
# When true, RunReveal creates an investigation and runs the triage agent on each alert.
# Keys are case-sensitive: use exactly "autoTriage" and "agentConfigIdOrName".
autoTriage: false
 
# Optional: Agent to use for triage when autoTriage is true. Can be the agent config ID, name (slug), or display name.
# Omit or leave empty to use the default triage agent.
agentConfigIdOrName: ""

Field reference for SQL detection-as-code YAML/JSON (matches API DetectionSyncRequest). Required means the sync API expects the value for a valid SQL detection; the CLI usually supplies query by reading the file you reference.

FieldRequiredDescription
nameYesUnique detection identifier (slug). Allowed characters: letters, numbers, hyphens; normalized and length-limited.
fileYes*Path to the .sql file (relative to this YAML). *Required for the usual CLI workflow; the CLI reads this file and sends the SQL as query. Omit only if you embed query in the same document.
queryYes*SQL body. *Populated automatically from file when you use the file-based layout; include inline only if you are not using file.
scheduleYesCron expression for how often the detection runs (e.g. */15 * * * *). Required for SQL detections at sync time.
displayNameNoHuman-readable title in the UI.
typeNoDetection kind: sql (default), sigma, or health. SQL-as-code configs are normally omitted or sql.
descriptionNoShort explanation of what the detection finds.
notesNoLonger internal notes (playbook links, change history, etc.).
categoriesNoTags for grouping and routing (e.g. aws, signal).
sourceTypesNoLimit which log source types this detection applies to; empty means all sources.
disabledNoIf true, the detection does not run on its schedule (default false).
parametersNoKey/value strings merged into the query at runtime (e.g. static filter values). Distinct from default macros from / to.
defaultParametersNoAPI field for default parameter bindings; most YAML configs use parameters instead.
notificationNamesNoNotification channel names that receive an alert when the detection fires.
notificationTemplateNoName of a workspace notification template to use when alerting.
inlineTemplateNoInline notification content (title / body) instead of or in addition to a named template.
mitreAttacksNoMITRE ATT&CK tactic IDs (e.g. TA0001 for Initial Access). Must use the TA#### format.
mitreTechniquesNoMITRE ATT&CK techniques/sub-techniques (e.g. T1059.004).
fieldsNoOptional list of fields to emphasize in alert/output context.
severityNoSeverity string for the detection (e.g. low, medium, high, critical).
riskScoreNoNumeric risk score stored on the detection.
settingsNoArbitrary JSON object for detection engine–specific settings (RawSettings in the API).
publicNoReserved; not intended for general workspace configuration.
autoTriageNoWhen true, creates/uses an investigation channel and enables AI triage for alerts from this detection (case-sensitive key).
agentConfigIdOrNameNoAgent to use for triage when autoTriage is true: config ID, slug, or display name; omit for default (case-sensitive key).

Detection Time Windowing

One thing that many detection queries tend to do is query by looking back in time and handling time windowing.

When writing a detection query it’s best to write your query in a way that:

  1. Uses receivedAt times for windowing of logs on short windows, not eventTime
  2. Uses parameters {from:DateTime} and {to:DateTime} to ensure all logs are queried.

RunReveal allows you to specify parameters in your queries for dynamic values passed in at query runtime. RunReveal will always pass from and to to your queries whether they are specified or not.

  • from the end of the previous window that was queried.
  • to the current time, and the next from time.

In practice what this looks like, when writing a simple query that looks for the eventName example your query would look something like this.

select * from logs
where eventName='example'
and receivedAt BETWEEN {from:DateTime} and {to:DateTime}

It’s important not to use eventTime because many log sources deliver eventTimes in a delayed manner, and eventTime is the timestamp provided by the log source. If you instead window using eventTime it can cause messages to be missed in situations where log sources deliver delayed data.

By querying receivedAt you ensure data is searched as it’s received by the RunReveal platform regardless of data delays from sources.

AI triage (auto investigation)

When a detection fires, you can have RunReveal automatically create an investigation and run an AI triage agent on the alert. This is controlled in your detection config with two optional fields (case-sensitive):

  • autoTriage (boolean) — When true, each time the detection triggers, RunReveal creates an investigation and runs the triage agent. The investigation notification channel is created or updated when you sync the detection.
  • agentConfigIdOrName (string) — Optional. The agent to use for triage: you can specify the agent config ID, name (slug), or display name. Omit or leave empty to use the default triage agent.

Use exactly autoTriage and agentConfigIdOrName in your YAML or JSON; other spellings or casing are ignored. This applies to both SQL detection-as-code configs and to Sigma rules (see Sigma Streaming).

Creating a new detection

If you’d like to save yourself from writing this yaml, the CLI can help you create the yaml structure needed to create a new detection.

The runreveal detections create command will walk you through a wizard that will output a yaml containing the structure you need to upload a detection to our api.

:) runreveal detections create
Detection name: my-detection
Description: example for the docs.
Categories (comma separated): aws, signal
Severity: Low
Risk Score (number): 10
ATT&CK classification: collection
Enabled: true
Query Type: sql


Detection Created:
        my-detection.yaml
        my-detection.sql

:) 

Now that you understand detection as code, explore these related guides: