> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clarion.cantina.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Signals

> How raw events from your monitors become issues: alert filters, deduplication, and the title and description templates.

> [!NOTE]
>
> A signal is a raw event from one of your monitors, such as a push to GitHub, a sign-in to Okta, or a DNS record change. Like a SIEM, Clarion stores every signal it receives, but a signal opens an issue only when an alert filter matches it. This page explains how that works and how to write filters that open the right issues.

## Signals

Some integrations run their own detection and send Clarion a finished alert, such as an Amazon GuardDuty finding or a Microsoft Sentinel incident. Each of those becomes an issue directly, without alert filters.

All other monitors send signals. A signal has an event type, such as `push` or `user.session.start`, and the event's original payload from the source. Clarion keeps every signal, whether or not a filter matches it, the way a SIEM keeps your logs. Agents and chat can search them later during an investigation.

## Alert filters

An alert filter, also called a detection rule, decides which signals open an issue. In the app, filters are listed under **Issue rules** on the monitor's page. If no filter matches a signal, Clarion records it and opens nothing.

Each filter has:

| Setting | What it does |
| - | - |
| **Steps** | One or more steps. Each step lists the event types it accepts and the conditions a signal's fields must meet. |
| **Time window** | For filters with more than one step, how close together the matching signals must be. |
| **Group by** | Fields the matching signals must share, such as the same user or the same IP address, to count as one match. |
| **Severity** | The severity of the issue the filter opens. |
| **Kind** | What the issue is, such as a threat or a risk. See [Issue kinds](/learn/issues/introduction.md#issue-kinds). |
| **Title and description templates** | The text of the issue. See [Title and description templates](#title-and-description-templates). |

A filter with one step opens an issue for each signal that matches. A filter with several steps looks for a sequence: it opens an issue only when signals match every step, in order, inside the time window. For example, a filter can look for a failed MFA challenge followed by a successful sign-in for the same user within ten minutes.

Clarion ships recommended filters for every supported source. A filter added from the library shows as a **Managed rule**. If you edit it, it becomes a **Customized rule**. Filters you write yourself show as **Custom rule**.

Before you save a filter, you can test it against the signals your monitor received in the last 24 hours, 3 days, 7 days, or 15 days. The test shows how many issues the filter would have opened and how many matches deduplication would have dropped.

## Writing a detection rule

The matching part of a detection rule is written in JSON. You can edit it in the rule editor, which suggests facts and operators as you type. This rule opens an issue when someone pushes to `main` in the `cantina/repo` repository:

```json theme={null}
{
  "withinMs": 60000,
  "groupByFields": ["after"],
  "steps": [
    {
      "eventTypes": ["push"],
      "conditions": {
        "all": [
          {
            "fact": "signalData",
            "path": "$.ref",
            "operator": "equal",
            "value": "refs/heads/main"
          },
          {
            "fact": "signalData",
            "path": "$.repository.full_name",
            "operator": "equal",
            "value": "cantina/repo"
          }
        ]
      }
    }
  ]
}
```

### Top-level fields

| Field | What it does |
| - | - |
| `steps` | The list of steps. A signal has to match every step, in order, for the rule to fire. Most rules have one step. |
| `withinMs` | The time window, in milliseconds. All steps must match within this window. `60000` is one minute. |
| `groupByFields` | Signal fields that every matched signal must share. In the example, `after` is the commit the branch points to after the push. In a multi-step rule, use a field such as the user name so that steps from different users don't combine into one match. Can be an empty list. |
| `distinctFields` | Optional. The rule fires only if these fields have at least two different values across the matched signals. For example, the same user signing in from two different IP addresses. |
| `excludedPrincipalPrefixes` | Optional. Signals whose user or service account starts with one of these prefixes are ignored. Use it to skip automated accounts. |

### Steps

Each step has two parts:

* `eventTypes` lists the event types the step accepts, such as `push` for GitHub or `user.session.start` for Okta. A signal of any other type can't match the step.
* `conditions` is a tree of checks on the signal. Group checks with `all`, where every check must pass, or `any`, where at least one must pass. Groups can contain other groups.

Each check has a `fact`, an `operator`, and a `value`:

| Fact | What it reads |
| - | - |
| `signalData` | A field of the signal's payload. Set `path` to the field, such as `$.repository.full_name`. Nested fields are separated with dots. |
| `eventType` | The signal's event type. Use it without a `path`. |
| `fieldEquals` | Compares two fields of the same signal. Use it with `"operator": "equal"` and `"value": true`, and name the two fields in `params`. See the example below. |

For example, this check passes when a push goes to the repository's default branch, whatever that branch is called:

```json theme={null}
{
  "fact": "fieldEquals",
  "operator": "equal",
  "value": true,
  "params": {
    "left": "$.ref",
    "right": "$.repository.default_branch",
    "prefix": "refs/heads/"
  }
}
```

`prefix` and `suffix` are optional. They're added around the `right` field before the two are compared.

### Operators

| Operator | Passes when | `value` |
| - | - | - |
| `equal` | The field equals the value exactly. | Text, number, or `true`/`false` |
| `notEqual` | The field doesn't equal the value. | Text, number, or `true`/`false` |
| `greaterThan` | The field is a number larger than the value. | Number |
| `greaterThanInclusive` | The field is a number larger than or equal to the value. | Number |
| `lessThan` | The field is a number smaller than the value. | Number |
| `lessThanInclusive` | The field is a number smaller than or equal to the value. | Number |
| `in` | The field is one of the values in the list. | List of text or numbers |
| `notIn` | The field is none of the values in the list. | List of text or numbers |
| `contains` | The field is a list, and it includes the value. | Text or number |
| `doesNotContain` | The field is a list, and it doesn't include the value. | Text or number |
| `stringContainsAny` | The field is text that contains any of the values. Ignores upper and lower case. | List of text |
| `notStringContainsAny` | The field is text that contains none of the values. Ignores upper and lower case. | List of text |
| `startsWithAny` | The field is text that starts with any of the values. Upper and lower case must match. | List of text |
| `notStartsWithAny` | The field is text that starts with none of the values. Upper and lower case must match. | List of text |

If a field is missing from the signal, checks with `notEqual`, `notIn`, `notStringContainsAny`, and `notStartsWithAny` pass, and checks with any other operator fail.

## How a signal becomes an issue

When a signal arrives, Clarion runs these steps:

1. It checks the signal against every filter on the monitor.
2. For each filter that matches, it fills in the title and description templates with values from the signal.
3. It checks for a duplicate, as described in [Deduplication](#deduplication). If the match is a duplicate, Clarion drops it and stops here.
4. It opens an issue with the filter's severity and kind, and links the signal to it.
5. The agent assigned to the monitor starts triage.

The same signal can match more than one filter. Each filter then opens its own issue, unless deduplication drops it.

## Deduplication

A noisy source can send the same event many times. Deduplication keeps that from opening a pile of identical issues and starting an agent run for each one.

Clarion decides whether two matches are the same by comparing their **identity**. For most sources, the identity is the issue title after the template has been filled in. A few sources, such as DNS monitoring, send a fingerprint of the event's content, and Clarion compares that instead. Matches only count as the same if they also come from the same source and the same monitor.

When a filter matches, Clarion drops the match in these cases:

| Situation | What happens |
| - | - |
| An issue with the same identity was opened in the last 24 hours and is still open. | The match is dropped. It doesn't open a new issue, and it isn't added to the open one. |
| Recent issues with the same identity were closed as `Benign` or `False positive`. | The match is dropped for a quiet period. The more of those issues in a row, the longer the quiet period. See the table below. |
| The source sent the same event twice. | The second copy is dropped. |

The quiet period grows with each `Benign` or `False positive` close in a row, counting back from the most recent one. It starts when the most recent of those issues was opened.

| Closes in a row | Quiet period |
| - | - |
| 1 | 1 day |
| 2 | 2 days |
| 3 | 4 days |
| 4 | 8 days |
| 5 | 16 days |
| 6 or more | 30 days |

Only closes from the last 30 days count. An issue closed as `Duplicate` of a `Benign` or `False positive` issue counts too. A close for any other reason, such as `Confirmed Threat`, ends the run. After that, the next match opens an issue right away, and the quiet period builds up again from one day.

Dropped matches stay visible on the timeline of the affected asset, marked as suppressed. They don't appear on any issue.

## Title and description templates

Every alert filter has a **title template** and a **description template**. Clarion fills the `{{field}}` placeholders in both with values from the signal and uses the results as the title and description of the issue it opens. Both templates matter: the title decides which matches count as duplicates, and the description decides what the agent knows when it starts.

### Title template

For most sources, the title is the identity Clarion uses for [deduplication](#deduplication). Two matches that produce the same title count as the same problem.

Put the fields that tell two alerts apart in the title, and leave out values that change with every event, such as commit SHAs, request IDs, and timestamps. For example, with GitHub pushes:

| Title template | What happens |
| - | - |
| `Push to default branch` | Only the first push opens an issue. For up to a day, a push by someone else, or to another repository, is dropped and nobody sees it. |
| `Push to default branch on {{repository.full_name}} by {{sender.login}}` | One issue for each person and repository. The agent investigates once for that person, not for every commit. |
| `Push {{head_commit.id}} to default branch` | Every commit opens a new issue and starts a new agent run. |

### Description template

The agent starts its investigation from the issue description. Include every detail it needs to judge the event: who did it, to which resource, from where, when, and the values that made the filter match. If a detail is missing, the agent has to look it up, or it may not find it at all. Descriptions support Markdown, so you can link to the user and the resource when the signal includes their URLs.

### Available fields

Any field in the signal's payload can be used, with dots for nested fields, such as `{{repository.full_name}}`. Clarion also adds these fields to every signal:

| Field | Value |
| - | - |
| `{{_signalId}}` | The signal's ID in Clarion. |
| `{{_signalEventType}}` | The event type, such as `push`. |
| `{{_signalSource}}` | The source the signal came from. |
| `{{_signalReceivedAt}}` | When the event happened, as an ISO 8601 timestamp. |
| `{{_signalReceivedAtReadable}}` | The same time, written for people to read. |

Putting `{{_signalId}}` in a title makes every match unique, so every match opens its own issue.

[Learn about Issues →](/learn/issues/introduction.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.