opentide

Tutorial: your first detection

Build a complete threat → objective → rule chain, validate it, fix an error, and dry-run a deploy — end to end.

The quickstart runs commands against an existing repo. This tutorial is different: you will author a real detection chain from nothing and take it through the whole lifecycle. By the end you will have a threat, an objective, and a Sentinel rule that reference each other, pass validation, and are ready to deploy.

Budget 15–20 minutes. You need Python 3.10+ and opentide installed — see Installation.

Scaffold a repository

opentide setup --yes \
  --name "Tutorial Detections" \
  --org "Example Corp" \
  --platform sentinel \
  --path ./tutorial-detections

cd tutorial-detections
export OPENTIDE_REPO_ROOT="$PWD"

You now have objects/{threats,objectives,rules}/, a .opentide/ config directory, and platform scaffolding. See Repository setup for what each piece is.

Generate the framework artifacts

Before validation can work, the schemas and templates must exist:

opentide generate
✓ vocabularies loaded
✓ templates written        .opentide/templates/
✓ schemas written          .opentide/schemas/
✓ IDE router               .opentide/schemas/opentide.schema.json
generate: complete

Your editor can now validate objects/**/*.yaml live, because the router maps each file's metadata.schema to the right JSON Schema.

Author the threat

Create objects/threats/simulated-actor.yaml:

name: Simulated Actor
criticality: High
metadata:
  uuid: 00000000-0000-4000-8001-000000000001
  schema: threat::1.0
  version: 1
  tlp: clear
threat:
  description: Simulated threat actor exercising credential access
  severity: High
  impact: Data Breach
  leverage: High
  viability: High
  terrain: Endpoint workstations and user devices.
  surface:
    - Windows::Desktop
  att&ck:
    - T1059

Author the objective

The objective covers the threat (by UUID) and declares the signals that satisfy it. Create objects/objectives/credential-access.yaml:

name: Credential Access Objective
metadata:
  uuid: 00000000-0000-4000-8002-000000000001
  schema: objective::1.0
  version: 1
  tlp: clear
composition:
  strategy: synergetic
  description: Compose signals for credential access detection
objective:
  priority: High
  type: Threat
  description: Detect credential access techniques
  composition:
    strategy: synergetic
    description: Compose signals for credential access detection
  threats:
    - 00000000-0000-4000-8001-000000000001   # the threat from step 3
  signals:
    - name: Suspicious logon signal
      uuid: 00000000-0000-4000-8099-000000000001
      description: Suspicious authentication activity
      severity: Medium
      methodology: analytics
      entities: [host]
      data:
        availability: Complete
        requirements: Security event logs

Author the rule

The rule implements the objective (via detection_model) and carries a Sentinel query. Create objects/rules/sentinel-suspicious-process.yaml:

name: Sentinel KQL Rule
metadata:
  uuid: 00000000-0000-4000-8003-000000000001
  schema: rule::1.0
  version: 1
  tlp: clear
description: Detects credential access via suspicious process creation
status: STAGING
severity: High
techniques: [T1059]
detection_model: 00000000-0000-4000-8002-000000000001   # the objective from step 4
response:
  alert_severity: High
configurations:
  sentinel:
    enabled: true
    name: Sentinel KQL Rule
    status: STAGING
    query: |
      SecurityEvent
      | where EventID == 4688
      | take 1
    scheduling:
      frequency: PT1H
      lookback: PT2H
    alert:
      title: Sentinel KQL Rule
      suppression: false

Validate

opentide validate

On success the CLI logs that all content passed validation (exit 0). For a machine-readable report:

opentide --json validate
{
  "checks": {
    "id-uniqueness": { "check": "id-uniqueness", "status": "passed" },
    "uuid-format": { "check": "uuid-format", "status": "passed" },
    "schema": { "check": "schema", "status": "passed" }
  },
  "report": { "ok": true, "issues": [], "warnings": [], "stats": {} },
  "ok": true,
  "status": "passed",
  "message": "Validation passed"
}

See validate.

Break it on purpose

Understanding failure output is half the job. Change the rule's detection_model to a UUID that does not exist:

detection_model: 00000000-0000-4000-8002-DEADBEEF0000

Re-run:

opentide validate

Issues print in a "Validation issues" panel, grouped by file. A dangling detection_model is an invalid_ref from the cross-object reference check (not the threat chaining check):

## objects/rules/sentinel-kql-rule.yaml
  [error] detection_model: Unknown objective reference '00000000-0000-4000-8002-DEADBEEF0000'

The process exits 1. This is the dangling reference anti-pattern from the object model. Restore the correct UUID and validation passes again.

Check the coverage graph

opentide info
opentide --json info --technique T1059 coverage

info shows your objects and how they chain; the coverage query shows that T1059 is now covered by one objective and one rule. See info.

Validate the query and dry-run the deploy

Sentinel supports query validation, so check the KQL before deploying:

opentide validate query --platform sentinel
opentide deploy --platform sentinel --dry-run
deploy (dry-run): sentinel
  Sentinel KQL Rule  STAGING  → would create/update
deploy: 1 rule planned, 0 applied (dry-run)

--dry-run shows exactly what a real deploy would change without touching the platform. A real deploy needs credentials; remove --dry-run when you are ready.

Generate documentation

opentide generate docs

This renders wiki-style markdown for each object under docs/, including a Mermaid diagram of the chain you just built. See generate docs.

What you built

flowchart LR
  Rule["Sentinel KQL Rule<br/>rule::1.0 · STAGING"] -->|detection_model| Objective["Credential Access<br/>objective::1.0"]
  Objective -->|threats| Threat["Simulated Actor<br/>threat::1.0"]

Next steps

On this page