> For the complete documentation index, see [llms.txt](https://docs.scanner.dev/scanner/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.scanner.dev/scanner/using-scanner-complete-feature-reference/detections-and-alerting/detection-rules/detection-rules-as-code/cli.md).

# CLI

Scanner provides a Python CLI for validating, testing, and syncing detection rule YAML files. The package is published as [`scanner-cli`](https://pypi.org/project/scanner-cli/) on PyPI.

## Installation

We recommend installing `scanner-cli` with [`pipx`](https://pipx.pypa.io/), which puts the CLI in its own isolated virtual environment and on your `PATH`:

```bash
pipx install scanner-cli
```

### Installing pipx

If you don't already have `pipx`, install it via one of the following.

{% tabs %}
{% tab title="macOS" %}

```bash
brew install pipx
pipx ensurepath
```

{% endtab %}

{% tab title="Linux (Debian / Ubuntu 23.04+)" %}

```bash
sudo apt install pipx
pipx ensurepath
```

{% endtab %}

{% tab title="Linux (other) / Windows" %}

```bash
python3 -m pip install --user pipx
python3 -m pipx ensurepath
```

{% endtab %}
{% endtabs %}

Restart your shell after `pipx ensurepath` so the updated `PATH` takes effect. See the [pipx installation docs](https://pipx.pypa.io/stable/installation/) for additional platforms.

## Authentication

You will need the API URL of your Scanner instance and an API key. Find both under **Settings > API Keys**.

Either export them as environment variables:

```bash
export SCANNER_API_URL=<Scanner API URL>
export SCANNER_API_KEY=<Scanner API key>
```

or pass them on every invocation:

```bash
scanner-cli <command> --api-url=<Scanner API URL> --api-key=<Scanner API key>
```

## Validate detection rule

To validate files:

```
scanner-cli validate -f detections/some_detection.yaml
```

To validate directories:

```
scanner-cli validate -d detections
```

Only YAML files with the correct schema header will be validated. Multiple files or directories can be provided. To recursively search through directories, use the recursive flag `-r`.

`validate` checks that the specified files are valid Scanner detection rule files. After validating files, use [`run-tests`](#run-detection-rule-tests)to check that all tests pass before syncing to Scanner.

### Example

<figure><img src="/files/kfaqGB6RdkLg0gNH1kKi" alt=""><figcaption><p>Validate detection rules</p></figcaption></figure>

## Run detection rule tests

To run detection rule tests on files:

```
scanner-cli run-tests -f detections/some_detection.yaml
```

To run detection rule tests on directories:

```
scanner-cli run-tests -d detections
```

This will only run tests on YAML files with the correct schema header. Multiple files or directories can be provided. To recursively search through directories, use the recursive flag `-r`.

### Example

<figure><img src="/files/91U8PR655aQT0a8P1XUO" alt=""><figcaption><p>Run tests</p></figcaption></figure>

## Sync an entire repository (`sync-git-repo`)

`sync-git-repo` is the CLI implementation of [user-pushed sync](/scanner/using-scanner-complete-feature-reference/detections-and-alerting/detection-rules/detection-rules-as-code/getting-started.md#set-up-user-pushed-sync). It uploads the tracked files in a Git working tree (via `git ls-files`) to a push sync source on Scanner by calling [`POST /v1/upload_git_repo_zipball`](/scanner/using-scanner-complete-feature-reference/developer-tools/api/git-sync.md#upload-a-repository-zipball). The server runs the same validate / test / materialize pipeline as a GitHub-polled sync, so a successful upload is equivalent to a poll-mode sync cycle — including deleting rules whose YAML has been removed from the repository.

> If your rules are on GitHub.com or GitHub Enterprise Server, the [`scanner-inc/sync-detection-rules`](https://github.com/scanner-inc/sync-detection-rules) GitHub Action wraps this command and surfaces failures as PR annotations. See [getting-started.md → user-pushed sync](/scanner/using-scanner-complete-feature-reference/detections-and-alerting/detection-rules/detection-rules-as-code/getting-started.md#set-up-user-pushed-sync). The notes below describe the underlying `scanner-cli` command.

To run a sync from the current directory:

```bash
scanner-cli sync-git-repo --push-key <push-key>
```

To sync a different working tree, pass it as a positional argument:

```bash
scanner-cli sync-git-repo --push-key <push-key> path/to/rules-repo
```

`<push-key>` identifies the push sync source on Scanner — find or create one under **Detections > Synced Repositories**. The current branch (the working tree must not be in a detached-HEAD state), commit SHA, and HEAD commit message (`git log -1 --pretty=%B`) are derived from the repo and sent with the upload; a dirty working tree is allowed and reported with a `+dirty` suffix on the commit SHA. There's no `--branch` flag — the branch is always the one currently checked out, and the upload targets the sync source whose `(push_key, branch)` matches. If no source is registered for the current branch, the upload is rejected.

If any rule in the upload fails to validate or its tests fail, the server rejects the whole upload — no rules from it are synced. The CLI prints the per-file failures so you can fix them and re-run.

## Sync individual rules (`sync`)

{% hint style="warning" %}
Prefer [`sync-git-repo`](#sync-an-entire-repository-sync-git-repo) (or [GitHub-polled sync](/scanner/using-scanner-complete-feature-reference/detections-and-alerting/detection-rules/detection-rules-as-code/getting-started.md#set-up-github-polled-sync)) for normal flows: both reconcile the whole set of rules in a sync source on every run. Per-file `sync` only creates and updates rules — it has no way to delete a rule that's been removed from the repo — and shouldn't be mixed with the repo-level flows on the same rules.
{% endhint %}

```bash
scanner-cli sync -f detections/some_detection.yaml \
  --team-id <team-id> \
  --sync-config-file detections/sync-config.yaml
```

`-f`, `-d <dir>`, and `-r` (recursive) work the same way as `validate` and `run-tests`. `--team-id` is the Scanner team to sync into (see **Settings > General**). `--sync-config-file` points at a YAML file containing the `event_sink_keys → event_sink_ids` mapping that resolves any `event_sink_keys` referenced by your rules; pass an empty file if your rules don't use event-sink keys.

Each rule's YAML must include a `push_key` field at the top level — that field is the per-rule identifier the server uses to look up an existing detection rule to update (or create a new one if none matches). See the [detection rule schema](https://scanner.dev/schema/scanner-detection-rule.v1.json) for the field's exact placement.

## Failing tests

Tests fail if:

* The `expected_detection_result`is true and the provided log events do not trigger a detection in the specified time window
* The `expected_detection_result`is false and the provided log events trigger a detection in the specified time window

Tips for debugging failed tests:

* Check that the log events are hits for `query_text`
* Check that the log events have timestamps that fall in the time window specified by `now_timestamp`, `time_range_s` , and `run_frequency_s`
  * If `now_timestamp`is not provided, the timestamp of the latest log event is used. In both cases, the timestamps are rounded to the next `run_frequency_s`.
  * Example: if `time_range_s`is 600 seconds, `run_frequency_s`is 60 seconds, and `now_timestamp`is `2024-07-05T10:15:00.000Z`, the time window specified is `2024-07-05T10:05:00.000Z` to `2024-07-05T10:15:00.000Z`. Log events must have timestamps in this time window in order to trigger an alert.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.scanner.dev/scanner/using-scanner-complete-feature-reference/detections-and-alerting/detection-rules/detection-rules-as-code/cli.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
