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: completeYour 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:
- T1059Author 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 logsAuthor 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: falseValidate
opentide validateOn 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-DEADBEEF0000Re-run:
opentide validateIssues 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 coverageinfo 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-rundeploy (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 docsThis 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
- Detection-as-code — the daily authoring and review loop.
- CI/CD — gate this validation on every pull request.
- Configuration — real credentials, statuses, and promotion.
- Troubleshooting — when a step above does not behave.