nano SIEM
User Guide

Detection-as-Code

Detection-as-Code

Detection-as-Code (DaC) brings GitOps practices to security detection engineering. By managing detection rules as code in git repositories, teams gain version control, peer review, automated testing, and reproducible deployments.

Why Detection-as-Code?

Traditional detection management through UI-based editors creates challenges:

Traditional ApproachDetection-as-Code
Changes tracked in audit logsFull git history with context
No built-in review processPull request approvals required
Manual deploymentAutomated CI/CD pipelines
Difficult rollbackgit revert recovers instantly
Siloed knowledgeShared repository enables collaboration

Benefits:

  • Version Control - Every detection change is tracked with author, timestamp, and rationale
  • Peer Review - Security team reviews PRs before rules go live
  • Testing - Validate rules against historical data in CI
  • Rollback - Revert problematic changes in seconds
  • Collaboration - Detection logic is visible and shareable

Getting Started

Install nanodac CLI

nanodac is available as a separate repository: github.com/the2dl/nanodac

# Clone the repo
git clone https://github.com/the2dl/nanodac.git
cd nanodac

# Install dependencies and build
npm install
npm run build

# Run commands via npx
npx nanodac <command>

Initialize a Repository

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

This creates:

my-detections/
├── nanodac.config.yaml
├── detections/
│   └── examples/
│       └── brute_force_ssh.yaml
└── .github/
    └── workflows/
        └── detection-sync.yml

Configure API Access

Edit nanodac.config.yaml:

apiUrl: https://nanosiem.example.com:3001
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

Detection files use YAML frontmatter followed by the query body:

---
title: brute_force_ssh
description: Detects SSH brute force attempts with 10+ failed logins from single IP
author: security-team
severity: high
mode: staging
detection_mode: scheduled
schedule: "*/5 * * * *"
lookback: 15m
mitre_tactics: TA0006
mitre_techniques: T1110
risk_score: auto
risk_entity: src_ip
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 user recently requested password reset."
---
source_type=ssh_logs
  | where action="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

Required Fields

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

Optional Fields

FieldDescriptionDefault
descriptionWhat the detection identifies-
authorDetection author-
modestaging, live, or alertingstaging
detection_modescheduled or real-timescheduled
scheduleCron expression*/5 * * * *
lookbackTime window (15m, 1h, 24h)15m
mitre_tacticsMITRE ATT&CK tactic IDs-
mitre_techniquesMITRE ATT&CK technique IDs-
risk_score0-100 or autoauto
risk_entityField for risk scoringauto-detect
tagsArray of category tags[]
ai_triage_hintsHints for AI triage and analysts-
enabledWhether rule is enabled on deploytrue
alert_modegrouped or per_event — how matches map to alertsgrouped

CLI Commands

Validate Detections

Check detection files for errors before deploying:

# Validate all detections
nanodac validate

# Validate specific files
nanodac validate detections/malware/*.yaml

# Strict mode - treat warnings as errors
nanodac validate --strict

Validation checks:

  • YAML syntax and schema compliance
  • Required fields present
  • MITRE ATT&CK format (TA####, T####)
  • Cron expression validity
  • Lookback period format

Test Against Historical Data

See what a detection would match before deploying:

# Test with 7-day lookback (default)
nanodac test detections/brute_force_ssh.yaml

# Test last 24 hours
nanodac test detections/brute_force_ssh.yaml --hours 24

# Show all matching events
nanodac test detections/brute_force_ssh.yaml --all

# Show specific fields only
nanodac test detections/brute_force_ssh.yaml --fields src_ip,user,attempts

# Compact output
nanodac test detections/brute_force_ssh.yaml --compact

Compare Local vs Remote

See what would change before deploying:

# Summary of differences
nanodac diff

# Detailed diff output
nanodac diff --verbose

Deploy Detections

Sync local detection files to nano:

# Preview changes (dry-run)
nanodac sync --dry-run

# Deploy changes
nanodac sync

# Skip confirmation prompts
nanodac sync --force

# Delete remote rules not in local files
nanodac sync --delete-orphans

1. Create a Feature Branch

git checkout -b detect/lateral-movement-psexec

2. Write the Detection

Create detections/lateral_movement/psexec_execution.yaml:

---
title: psexec_remote_execution
description: Detects PsExec lateral movement to remote hosts
author: jane.doe@company.com
severity: high
mode: staging
detection_mode: scheduled
schedule: "*/5 * * * *"
lookback: 15m
mitre_tactics: TA0008
mitre_techniques: T1021.002
risk_score: 75
risk_entity: dest_host
tags:
  - lateral_movement
  - psexec

ai_triage_hints:
  ignore_when:
    - "Destination is a jump server for admin access"
    - "User is in approved admin group with documented access"
    - "Activity matches known deployment automation"
  suspicious_when:
    - "User has never accessed this host before"
    - "Destination host is sensitive (DC, file server)"
    - "Activity occurs outside business hours"
  context: "PsExec is legitimate admin tool but commonly used by attackers. Verify user had business need for remote access."
---
source_type=windows_security
  | where event_id=4688
  | where process_name CONTAINS "psexec"
  | stats count() as executions,
          values(dest_host) as targets,
          min(timestamp) as first_seen,
          max(timestamp) as last_seen
    by src_host, user
  | where count(targets) > 1

3. Validate and Test Locally

# Check for errors
nanodac validate detections/lateral_movement/psexec_execution.yaml

# Test against last 14 days
nanodac test detections/lateral_movement/psexec_execution.yaml --days 14

Review the output - check for:

  • Excessive matches (possible false positives)
  • No matches (query might be too narrow)
  • Expected behavior for known activity

4. Create Pull Request

git add detections/lateral_movement/psexec_execution.yaml
git commit -m "feat(detection): add PsExec lateral movement detection

Detects PsExec usage to multiple remote hosts from single source.
Maps to MITRE T1021.002 (Remote Services: SMB/Windows Admin Shares).

Tested against 14 days of data: 3 matches, all expected admin activity."

git push -u origin detect/lateral-movement-psexec

5. CI Validates Automatically

GitHub Actions runs on the PR:

  • Validates YAML syntax and schema
  • Tests query against nano API
  • Posts results as PR comment

6. Team Reviews

Reviewers check:

  • Detection logic correctness
  • MITRE mapping accuracy
  • Threshold appropriateness
  • Triage hint completeness
  • Query performance

7. Merge Deploys to Production

After approval, merge to main:

  • Detection automatically deploys to nano
  • Starts in staging mode for silent monitoring
  • Analyst promotes to alerting after validation

GitHub Actions Setup

Basic Workflow

Create .github/workflows/detection-sync.yml:

name: Detection Sync

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

jobs:
  validate:
    name: Validate Detections
    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:
    name: Deploy Detections
    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

Repository Secrets

Add these secrets in GitHub repository settings:

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

PR Comment Example

When a PR is opened, the action posts validation results:

## Detection Validation Results

| Status | Count |
|--------|-------|
| Valid | 5 |
| Warnings | 1 |
| Errors | 0 |

### Warnings

**detections/risky_rule.yaml**
- mitre: No MITRE ATT&CK mappings specified

### Test Results

| Detection | Matches (7d) | Status |
|-----------|--------------|--------|
| brute_force_ssh | 12 | OK |
| psexec_execution | 3 | OK |

AI-Assisted Detection Writing

MCP Server Setup

The nanodac MCP server enables AI assistants to create and manage detections.

For Cursor, add to .cursor/mcp.json:

{
  "mcpServers": {
    "nanodac": {
      "command": "node",
      "args": ["/Users/you/nanodac/packages/mcp-server/dist/index.js"],
      "env": {
        "NANOSIEM_API_URL": "https://nanosiem.example.com:3001",
        "NANOSIEM_SEARCH_URL": "https://nanosiem.example.com:3002",
        "NANOSIEM_API_KEY": "your-api-key"
      }
    }
  }
}

For Kiro, add to .kiro/settings/mcp.json:

{
  "mcpServers": {
    "nanodac": {
      "command": "node",
      "args": ["/Users/you/nanodac/packages/mcp-server/dist/index.js"]
    }
  }
}

Replace /Users/you/nanodac with your actual nanodac installation path.

AI Tools Available

ToolDescription
list_detectionsList all detections
get_detectionGet detection by ID/name
create_detectionCreate from YAML content
validate_detectionValidate syntax locally
test_detectionTest against historical data
searchExecute ad-hoc queries
get_schemaGet file format documentation
create_issueCreate a GitHub issue
list_issuesList GitHub issues
create_branchCreate feature branch
write_detection_fileWrite detection to disk
commit_and_pushCommit and push changes
create_pull_requestCreate PR with issue linking

AI-Driven Detection Workflow

The most powerful feature is the new_detection_workflow prompt. Just tell the AI:

"Let's create a new detection for PowerShell encoded commands"

The AI will automatically:

  1. Create a GitHub issue to track the work
  2. Create a feature branch (detect/powershell-encoded-commands)
  3. Generate the detection with MITRE mappings and triage hints
  4. Write the file to detections/
  5. Validate and test against historical data
  6. STOP and show you the results

After you review and approve, the AI will: 7. Commit and push the changes 8. Create a PR linked to the issue (Closes #X) 9. Add "awaiting-review" label

The issue closes automatically when the PR is merged.

Example AI Prompts

Generate a detection:

"Create a detection for PowerShell execution with encoded commands"

Tune a noisy rule:

"The brute_force_ssh detection is triggering on security scanners. Add an exclusion for our Nessus scanner at 10.1.1.50"

Explain a detection:

"Explain what the lateral_movement_wmi detection does and what MITRE techniques it covers"

Issue Tracking Integration

Track detection improvements with GitHub issues:

# Create an issue for detection tuning
nanodac issue create \
  --title "Tune brute_force_ssh - false positives from scanners" \
  --body "Detection triggering on security scanners. Need to add exclusions." \
  --labels detection,tuning,false-positive

# List open detection issues
nanodac issue list --labels detection

Requires GitHub CLI (gh) to be installed and authenticated.

Best Practices

Directory Organization

detections/
├── authentication/
│   ├── brute_force_ssh.yaml
│   ├── failed_mfa.yaml
│   └── impossible_travel.yaml
├── lateral_movement/
│   ├── psexec_execution.yaml
│   └── wmi_process_creation.yaml
├── malware/
│   ├── known_bad_hashes.yaml
│   └── suspicious_powershell.yaml
└── data_exfiltration/
    ├── large_upload.yaml
    └── cloud_storage_access.yaml

Naming Conventions

  • Snake_case for titles: brute_force_ssh, lateral_movement_psexec
  • Descriptive names that indicate the threat, not the query
  • Prefix experimental rules: wip_, test_

Mode Progression

ModePurposeAlert Generation
stagingSilent monitoring during developmentNo
liveBake-in period, generates findingsFindings only
alertingFull productionAlerts + Cases

Commit Messages

Use conventional commits for clear history:

feat(detection): add PsExec lateral movement detection
fix(detection): reduce brute_force_ssh false positives
tune(detection): adjust dns_tunnel threshold from 100 to 500
docs(detection): add triage hints to suspicious_powershell

Testing Guidelines

Before deploying, always verify:

  1. Match count - Excessive matches indicate false positives
  2. Sample review - Inspect actual matched events
  3. Performance - Complex queries may impact system load
  4. Coverage - Ensure query catches intended behavior
# Check match volume over 30 days
nanodac test detections/new_rule.yaml --days 30 --all | wc -l

# Review samples
nanodac test detections/new_rule.yaml --limit 10

AI Triage Hints

Always include triage hints to help analysts and AI systems:

ai_triage_hints:
  ignore_when:
    - "Source is known security tool (scanner, EDR)"
    - "Activity matches documented automation"
    - "User is in approved exception list"
  suspicious_when:
    - "First time user accessed this system"
    - "Activity outside business hours"
    - "Multiple systems affected in short timeframe"
  context: "Background information helping analysts understand what to investigate."

Troubleshooting

Validation Fails

YAML syntax error:

Error: YAML parse error at line 15

Fix: Check indentation and YAML formatting.

Missing required field:

Error: title is required

Fix: Add the missing field to frontmatter.

Invalid MITRE format:

Warning: mitre_tactics should be TA#### format

Fix: Use TA0001 format for tactics, T1059 for techniques.

Test Command Fails

API key invalid:

Error: Invalid API key

Fix: Check NANOSIEM_API_KEY environment variable.

Query syntax error:

Error: Query parse error at position 42

Fix: Validate query syntax in nano Search first.

Sync Conflicts

Detection already exists:

Warning: Detection 'brute_force_ssh' already exists with different ID

Options:

  • Use --force to overwrite
  • Delete the existing rule first
  • Rename your detection

Next Steps

On this page

On this page