m5c detections
Detections are source-controlled rules that produce findings or other detection results from contract-shaped data. They can be authored directly in Mach5 Detection IR or imported from foreign formats for review and lowering.
Where detection files live
m5c discovers detections from *.yaml files under a directory named detections inside packages.
A typical layout is:
packages/security-analytics/
detection_families/
identity-access.yaml
detections/
okta-password-spray.yaml
github-repository-activity.yaml
tests/
expected_findings/
okta-password-spray.yaml
Build a detection
Build a detection in this order:
- Choose a detection family and detector.
- Require the contracts and capabilities the rule needs.
- Write matchers against contract fields, not raw source fields.
- Set detector parameters.
- Define dedupe fields.
- Define finding output and entity fields.
- Add ATT&CK, lifecycle, promotion, and changelog metadata.
- Add expected-finding tests.
- Run
m5c validate,m5c test, andm5c lower.
Detection document
kind: Detection
apiVersion: semantic-catalog.mach5.io/v1alpha1
metadata:
name: okta-password-spray
labels:
source: native
version: "0.3.0"
spec:
id: M5-SA-OKTA-001
title: Multiple failed Okta authentications from one source
description: A single client IP produced repeated failed Okta authentication events in a short window.
family: identity_access
detector: aggregation_threshold
level: medium
confidence: 0.65
enabled: true
feature_gate: okta_identity_detections
requires:
contracts:
identity.authentication_event.v1: ">=1.0.0 <2.0.0"
capabilities:
- identity.authentication_event.v1.principal_available
- identity.authentication_event.v1.source_ip_available
- identity.authentication_event.v1.status_available
matchers:
- field: status_id
operator: eq
value: 2
parameters:
group_by:
- src_endpoint.ip
min_count: 10
window: 15m
dedupe:
strategy: windowed_entity
fields:
- detection_id
- src_endpoint.ip
- window_start
output:
finding_type: identity.password_spray
entity:
kind: ip
field: src_endpoint.ip
mitre_attack:
- technique_id: T1110
tactic: credential-access
tags:
- okta
- identity
- brute_force
lifecycle:
owner: security-detections@mach5.io
updated_at: "2026-05-22"
review_after: "2026-08-22"
source: native
promotion:
stage: staging
validation_status: runtime_smoke
reviewer: secops-lead@mach5.io
promoted_at: "2026-05-22T00:00:00Z"
changelog:
- version: "0.3.0"
date: "2026-05-22"
summary: Added aggregation threshold metadata and smoke-test evidence.
Top-level fields
| Field | Required | Meaning |
|---|---|---|
kind | Yes | Must be Detection. |
apiVersion | Recommended | Current examples use semantic-catalog.mach5.io/v1alpha1. |
metadata.name | Yes | File-friendly rule name. |
metadata.version | Recommended | Semver version of this detection document. |
metadata.labels | Optional | Labels for ownership, source, or grouping. |
metadata.annotations | Optional | Import diagnostics and other non-schema metadata. |
spec.id | Yes | Stable detection ID. Must be unique in the workspace. |
spec.title | Yes | Human-readable title. |
spec.family | Yes | Detection family name. |
spec.detector | Yes | Detector name within the family. |
spec.requires | Recommended | Contracts and capabilities needed by the rule. |
spec.matchers | Recommended | Field/operator/value criteria. |
spec.parameters | Detector-dependent | Values declared by the selected detector. |
spec.dedupe | Often required | Required when the selected detector has dedupe.required: true. |
spec.output | Recommended | Finding type and primary entity. |
Identity and state
Use metadata.name for source control and spec.id for stable rule identity.
metadata:
name: okta-password-spray
spec:
id: M5-SA-OKTA-001
title: Multiple failed Okta authentications from one source
spec.enabled defaults to true when omitted. Set it explicitly for review workflows:
enabled: false
status: experimental
Common level values are informational, low, medium, high, and critical. confidence is a number between 0 and 1 by convention.
Family and detector
Every detection must point to a declared detector shape.
family: identity_access
detector: aggregation_threshold
m5c validate checks that the family exists and that the detector exists inside that family.
Contract and capability requirements
Detections require contracts by name and semver range.
requires:
contracts:
identity.authentication_event.v1: ">=1.0.0 <2.0.0"
capabilities:
- identity.authentication_event.v1.source_ip_available
The contract name must match a local DataContract.metadata.name. Capabilities must be defined by the target contract under spec.capabilities and referenced as contract.capability.
Write detections against contract fields and capabilities, never vendor raw fields.
Matchers
Matchers compare contract fields to values.
matchers:
- field: status_id
operator: eq
value: 2
The field must exist in one of the required contracts. If operator is omitted or empty, it defaults to eq.
Common matcher shapes:
# equality
- field: status_id
operator: eq
value: 2
# list membership
- field: event_type
operator: in
value: [PushEvent, DeleteEvent]
# substring match
- field: repository.full_name
operator: contains
value: mach5-io/
Allowed operators come from the selected DetectionFamily. m5c validate rejects operators that are not listed by the detector.
Parameters
Parameters are detector-specific.
parameters:
group_by:
- src_endpoint.ip
min_count: 10
window: 15m
m5c validate checks that every provided parameter is declared by the detector and that values match supported parameter types such as int, duration, bool, or list<string>.
Dedupe
Dedupe controls stable finding or alert grouping.
dedupe:
strategy: windowed_entity
fields:
- detection_id
- src_endpoint.ip
- window_start
If the detector requires dedupe, both strategy and non-empty fields are mandatory. Use fields that produce stable grouping without hiding distinct incidents.
Output
Output describes the generated finding type and primary entity.
output:
finding_type: identity.password_spray
entity:
kind: ip
field: src_endpoint.ip
The entity field should be a contract field. It is used by expected-finding tests and by downstream finding presentation.
ATT&CK, tags, and lifecycle
Use ATT&CK labels only when the rule has a defensible technique mapping.
mitre_attack:
- technique_id: T1110
tactic: credential-access
tags: [okta, identity, brute_force]
lifecycle:
owner: security-detections@mach5.io
updated_at: "2026-05-22"
review_after: "2026-08-22"
source: native
Promotion metadata records release state:
promotion:
stage: staging
validation_status: runtime_smoke
reviewer: secops-lead@mach5.io
promoted_at: "2026-05-22T00:00:00Z"
Expected-finding tests
Expected-finding tests are YAML files under a directory named expected_findings. They are separate from detection files.
kind: DetectionExpectedFindingsTest
spec:
cases:
- name: okta_password_spray_matches
detection_id: M5-SA-OKTA-001
input_ref: fixtures/okta-password-spray-events.json
should_match: true
expect:
detection_id: M5-SA-OKTA-001
entity_kind: ip
entity_id: 203.0.113.10
matched_event_count: 10
input_ref is resolved relative to the expected-finding test file. For event-match detectors, the input is usually one contract-shaped JSON object. For aggregation-style tests, the input is usually an array of contract-shaped JSON objects.
Run tests with:
m5c test apps/security-analytics --workspace
The human report includes:
expected findings: 12 case(s), 0 failed
Source-level expected-finding tests currently evaluate eq, in, contains, startswith, and endswith. Cover other operators and lowering-specific behavior with runtime smoke tests.
Lower detection output
m5c lower apps/security-analytics --dev --bindings bindings/dev.yaml --out lowered
Lowering plans enabled detections, resolves contract bindings and feature gates, writes generated bundles, and emits reports such as detection plan, coverage, lineage, freshness, promotion, and ATT&CK coverage where available.
Validate and test workflow
m5c validate apps/security-analytics --workspace --offline
m5c test apps/security-analytics --workspace
m5c lower apps/security-analytics --dev --out /tmp/m5c-lowered
If validation fails, check:
- duplicate
spec.idvalues; - unknown family or detector names;
- contract names and semver ranges;
- capability references;
- matcher fields against required contracts;
- matcher operators against the detector;
- parameter names and types;
- required dedupe fields.
Common mistakes
| Mistake | Fix |
|---|---|
Matching raw vendor fields such as outcome.result | Match normalized contract fields such as status_id. |
Using an alias under requires.contracts | Use the contract name as the key and a semver range as the value. |
| Referencing a capability without the contract prefix | Use identity.authentication_event.v1.status_available. |
| Using an operator not allowed by the family | Add the operator to the family only if the lowering template supports it. |
| Omitting required detector parameters | Add values under spec.parameters. |
| Using unstable dedupe fields | Use stable entity, event, or window fields. |
| Enabling imported or partial detections without review | Keep them disabled until validated and tested. |
Best practices
- Require contracts and capabilities, not raw source names.
- Keep matchers simple and reviewable.
- Use stable dedupe fields.
- Keep ATT&CK labels evidence-based.
- Add lifecycle and promotion metadata before production use.
- Add expected-finding tests for match and non-match cases.
- Keep imported rules reviewable; do not treat unsupported imports as production-ready.