nano SIEM
Agents & MCPDetection-as-Code

Detection-as-Code

Manage nano detections as version-controlled YAML and ship them with the nanodac CLI, driven by a coding agent in the OSS build

Detection-as-Code

Detection-as-Code (DaC) brings GitOps practices to detection engineering. Detection rules live as YAML files in a git repository, so you get version control, peer review, automated testing in CI, and reproducible deployments. The nanodac CLI validates those files, tests them against historical data, and syncs them to a nano deployment.

Why Detection-as-Code

UI-based rule editors track changes in audit logs but offer no review gate and no easy rollback. Managing detections as code closes those gaps.

UI-based editingDetection-as-Code
Changes tracked in audit logsFull git history with author and rationale
No built-in review processPull request approvals required
Manual deploymentAutomated CI/CD pipelines
Difficult rollbackgit revert recovers instantly
Siloed knowledgeShared repository enables collaboration

A detection moves through three modes as it earns trust. Rules start silent in staging and graduate to alerting once an analyst has validated them.

ModePurposeAlert generation
stagingSilent monitoring during developmentNone
liveBake-in period, generates findingsFindings only
alertingFull productionAlerts and cases

The agent angle

The open-source build of nano ships without the built-in pivt AI assistant. The hosted product uses pivt to generate parsers from sample logs, draft detections from natural-language descriptions, and translate plain English into nPL queries. The OSS build leaves that work to you, and a coding agent (Claude Code, Codex, Cursor) fills the gap by reading the reference repositories and authoring the same artifacts.

What you give up, what you get back

Hosted nano (pivt AI)OSS nano + coding agent
Generate a parser from a sample log pasteAgent reads parsers/ for examples, drafts parser.yaml, you validate with vector vrl
Draft a detection from "alert me when…"Agent reads rules/ for examples, drafts a YAML detection, nanodac validate checks it
Convert natural language to nPLAgent reads the Search Commands docs and drafts the query
Auto-tune false positivesManual review of detection matches, iterate via PR
One-click apply from the UInanodac sync from CI or a local checkout

The agent runs locally or in your CI, so log samples and detection logic never leave your environment. That property matters for air-gapped and compliance-bound deployments.

Reference repositories

Three public repos under github.com/nano-rs provide the file shapes, conventions, and examples the agent treats as ground truth.

RepoPurposeUsed by
parsers60+ sample parsers (parser.yaml with VRL) covering Windows, Sysmon, AWS, GCP, Okta, Palo Alto, CrowdStrikeParser authoring
rulesSample detection rules organized by MITRE tactic, licensed under DRL 1.1Detection authoring
nanodacDetection-as-code CLI (on npm as @nano-rs/dac-cli) with an MCP server for direct agent integrationDetection authoring and sync

Supported agents

These docs work for any agent that can read local files and run shell commands. Tested setups:

  • Claude Code: Anthropic's CLI; reads CLAUDE.md for repo-specific instructions, supports MCP servers
  • OpenAI Codex: reads AGENTS.md, supports MCP servers
  • Cursor: IDE-native agent; reads .cursor/rules/ and mcp-config.json
  • Kiro: reads .kiro/ (nanodac ships with a .kiro/ directory)

Anywhere these docs reference CLAUDE.md, the equivalent file for your agent works the same way.

nanodac essentials

Install

nanodac is published to npm as @nano-rs/dac-cli — no clone or build:

# Install globally so `nanodac` is on your PATH
npm install -g @nano-rs/dac-cli

# …or run any command without installing
npx @nano-rs/dac-cli <command>

Initialize a detections repository:

mkdir my-detections && cd my-detections
git init
nanodac init

This scaffolds nanodac.config.yaml, a detections/ directory with an example rule, and a .github/workflows/detection-sync.yml CI workflow.

Configure API access

Edit nanodac.config.yaml. The API runs on port 3000 and the search service on 3002.

apiUrl: https://nanosiem.example.com:3000
searchUrl: https://nanosiem.example.com:3002
detectionsDir: ./detections
defaults:
  severity: medium
  mode: staging

Set your API key:

export NANOSIEM_API_KEY=your-api-key

Detection file format

A detection file is YAML frontmatter followed by the nPL query body:

---
title: brute_force_ssh
description: Detects SSH brute force with 10+ failed logins from a single IP
author: security-team
severity: high
mode: staging
detection_mode: scheduled
schedule: "*/5 * * * *"
lookback: 15m
folder: identity
mitre_tactics:
  - TA0006
mitre_techniques:
  - T1110
tags:
  - authentication
  - brute_force
ai_triage_hints:
  ignore_when:
    - "Source IP is a known security scanner"
    - "User is a service account with expected auth patterns"
  suspicious_when:
    - "Multiple usernames targeted from same IP"
    - "Activity occurs outside business hours"
  context: "SSH brute force can trigger on legitimate password recovery. Check if the user recently requested a password reset."
---
source_type="ssh_logs" event_type="login_failed"
| stats count() as attempts, values(user) as targeted_users, min(timestamp) as first_attempt, max(timestamp) as last_attempt by src_ip
| where attempts > 10
| risk score=70 entity=src_ip factor="SSH brute force"

Required fields:

FieldDescription
titleSnake_case identifier (e.g. brute_force_ssh)
severitycritical, high, medium, low, or informational
query bodynPL query after the frontmatter

Optional fields:

FieldDescriptionDefault
descriptionWhat the detection identifies-
authorDetection author-
modestaging, live, or alertingstaging
detection_modescheduled (only supported value)scheduled
scheduleCron expression*/5 * * * *
lookbackTime window (15m, 1h, 24h)15m
folderGrouping folder for the rule (e.g. identity, endpoint, cloud, network)-
mitre_tacticsYAML list of MITRE ATT&CK tactic IDs (TA####)-
mitre_techniquesYAML list of MITRE ATT&CK technique IDs (T####)-
alert_modegrouped (all matches → one alert) or per_eventgrouped
tagsArray of category tags[]
ai_triage_hintsHints for AI triage and analysts-
enabledWhether the rule is enabled on deploytrue

Risk is not set in frontmatter. Score an entity inside the query with the risk pipe command:

| risk score=80 entity=user factor="Login from new country"

score is a number (or an expression such as if(external_ip, 80, 40)), entity is the field to attribute risk to, and factor is a human-readable reason. A query can apply risk multiple times.

CLI commands

CommandPurpose
nanodac validateCheck YAML syntax, schema, required fields, MITRE format, cron and lookback validity. Add --strict to treat warnings as errors.
nanodac test <file>Run the detection against historical data and show matches. --hours N / --days N set the window, --all shows every match, --fields a,b limits columns.
nanodac diffShow what would change between local files and the remote deployment. --verbose for full output.
nanodac syncPush local detections to nano. --dry-run previews, --force skips prompts, --delete-orphans removes remote rules absent locally.

GitHub Actions CI

nanodac init writes .github/workflows/detection-sync.yml. It validates detections on pull requests and deploys them on push to main:

name: Detection Sync

on:
  push:
    branches: [main]
    paths: ['detections/**']
  pull_request:
    paths: ['detections/**']

jobs:
  validate:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - name: Validate detections
        uses: ./.github/actions/nanodac
        with:
          nanosiem-url: ${{ secrets.NANOSIEM_URL }}
          api-key: ${{ secrets.NANOSIEM_API_KEY }}
          action: validate
          github-token: ${{ secrets.GITHUB_TOKEN }}

  deploy:
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - name: Deploy detections
        uses: ./.github/actions/nanodac
        with:
          nanosiem-url: ${{ secrets.NANOSIEM_URL }}
          api-key: ${{ secrets.NANOSIEM_API_KEY }}
          action: deploy

Add these repository secrets in GitHub settings:

SecretDescription
NANOSIEM_URLnano API URL (e.g. https://nanosiem.example.com:3000)
NANOSIEM_API_KEYAPI key with detection permissions
NANOSIEM_SEARCH_URLOptional search service URL (e.g. https://nanosiem.example.com:3002)

On a pull request the action posts validation and test results as a PR comment. Merging to main deploys the changed rules; new rules land in staging mode until an analyst promotes them.

Where to go next

  • Setup: clone the reference repos, wire up the nanodac MCP server, drop in agent instructions
  • Authoring parsers: scaffold a parser.yaml from a log sample and validate it with Vector
  • Authoring detections: draft a YAML detection, validate it, ship it via nanodac sync
  • Crafting searches: turn natural-language hunting questions into nPL queries

For the operations side, see the Investigator agent, which triages alerts and runs investigations against a live deployment.

On this page

On this page