Detection as Code: Six Best Practices for Scalable Threat Detection

Learn six Detection as Code best practices for writing, versioning, tuning, and deploying Sigma rules at scale.

AlphaSOC10 min read

Detection as Code (DaC) brings engineering discipline to threat detection logic. Rules live in a repository, are reviewed before they ship, get deployed through a pipeline, and can be rolled back if they misbehave. This approach borrows from tried-and-true software engineering best practices, which is why it works.

However, DaC requires an architecture that makes it easy to maintain and scale. For example, code changes must have audit trails for troubleshooting and rollback, and code translations to vendor-specific query languages must be done at execution time to maintain code portability across platforms. The environment can become unwieldy without such features, which is why modern SIEM architectures move this functionality out of the console and onto a fast, purpose-built, low-cost engine within the security data pipeline (sometimes referred to as a sidecar).

The six DaC best practices below assume that you're authoring code in Sigma and running it on an engine that natively reads Sigma, shifting detection logic left of the SIEM, with the AlphaSOC platform used to illustrate the examples. The practices featured in this article cover the full life cycle of a detection rule: data normalization, authoring, reviewing, storing, tuning, testing (against historical data), and integration with AI agent workflows.

Summary of Detection as Code Best Practices

Best practiceDescription
Standardize on a portable rule formatWrite detections in Sigma and run them on an engine that reads Sigma natively, so no converter sits between your repository and production.
Treat detection rules like softwareKeep rules in Git, require a sample event and expected result in every pull request, and deploy through a pipeline you can revert.
Normalize data to an open schemaMap telemetry to an open schema (e.g., ECS or OCSF) so one network rule covers every source and identity resolves consistently across cloud and application logs.
Manage tuning and suppression as codeCommit severity, scoping, and suppressions alongside the rule, and give every exception a reason and an expiry date.
Fast backtesting and retrospective huntingRun a new rule against stored telemetry before deploying it, then run it across your full retention window to check whether the activity already happened.
Build an agentic pipelineConfirm that the API covers every console action and connects over MCP, so agents can write, test, deploy, and tune rules rather than just read alerts.

Standardize on a Portable Rule Format

Sigma describes what to look for without naming the tool that does the looking. You write the rule in YAML once, and any engine that reads Sigma can run it. The SigmaHQ community repository backs it up with thousands of published rules. Adoption still lags, however, and the reason is more mechanical than cultural. Most platforms can't natively execute Sigma; they need it to be first translated into their own query language, and that translation step is where the portability you were promised evaporates.

At least three things break in translation:

  • Conversion: Backend coverage in tools such as sigma-cli is uneven. Modifiers that work against one target silently degrade against another, and fields that have no equivalent are dropped.
  • Maintenance: Editing the Sigma source isn't necessarily reflected in the deployed query. The two versions diverge quietly, and the rule your reviewers approved is no longer the rule catching threats.
  • Debugging: A rule that fires incorrectly sends you into the generated query rather than the detection logic, making you spend a long time on a translation artifact instead of on an adversary.

This is the toll that security teams pay to write portable rules for a platform that can't read them.

AlphaSOC natively supports Sigma detection rules. The rule you commit is the rule that executes, so there's no translated query to inspect, nothing to revalidate against a second rule syntax, and no gap opening up between your repository and production. Community rules taken from SigmaHQ and custom rules written for your own environment both deploy unchanged, and each one accepts the same adjustments as AlphaSOC's managed detections.

For instance, here's a rule that flags DNS lookups for domains bearing the hallmarks of a domain generation algorithm:

title: Suspicious DNS Query to DGA Domain
status: experimental
logsource:
  category: dns
detection:
  selection:
    query|re: '^[a-z0-9]{12,}\.(top|xyz|biz|click)$'
  condition: selection
level: medium

Read it again and notice what's missing: There's no index, data model, or platform-specific function call. A log category, a pattern, and a severity are enough when the engine speaks Sigma.

Native Sigma support deploys an authored rule straight to the detection engine in two steps. The traditional workflow adds conversion to SPL, CQL, KQL, or another query language, a test in Sigma, a second test in the target platform, deployment of the converted rule, and rule drift over time.
Native Sigma support (AlphaSOC) compared with a traditional Sigma workflow. Each conversion step is a place where logic can break or drift from the source rule.

Treat Detections Like Software

Detection logic changes constantly. For example, rules get tuned after a noisy week, scoped down when a business unit onboards a new tool, and retired when the technique they cover no longer matters. Every one of those changes is an edit to production security coverage, and in most SOCs, there's no record of who made it or why.

DaC attempts to close this gap: The detection rule is an artifact under version control, and every change to it inherits the machinery you already trust for application code.

The workflow is unremarkable, which is the point. Rules live in a repository, changes arrive as pull requests, and someone who didn't write the rule reviews it. A pipeline then validates the syntax and pushes approved rules to the detection engine through its API. Months later, when someone asks why a rule excludes a particular subnet, the answer is right in the commit message.

A six-step vertical flow: author or fork a Sigma rule, open a pull request carrying the rule, a sample event, and the expected result, peer review, CI pipeline syntax validation, deployment to the detection engine via API, and the rule running in production. Review sends changes back to authoring, and production failures revert to the previous commit.

Detection rules follow the same path as application code.

Review only works if the reviewer can see how the rule behaves. A pull request that contains nothing but YAML asks for approval on faith. Instead, give the reviewer three things:

  • The rule
  • A sample event that should trigger it
  • The result you expect when it runs

Now the review is a check rather than a courtesy. Your reviewer confirms that the logic fires on the event you claim it does and that disagreements are flagged before the rule reaches production.

It is good practice not to write everything from scratch. SigmaHQ covers many documented techniques already, and starting from a published rule puts you ahead of a blank file in two ways: The logic has been exercised in other environments, and the false positive patterns are usually documented in the rule itself. So fork what fits, adapt it to your log sources and naming, and keep your version in your own repository. After that, pull upstream changes when you choose to, not automatically.

Rollback is the part that teams underinvest in until they need it. A detection rule can fail in two directions, as shown in the table below.

FailureWhat it looks likeWhy it persists
Scoped too broadlyThe rule buries the queue with low-value matches.Analysts learn to skim that category and then ignore it. The noise is visible, so it usually gets fixed.
Scoped too narrowlyThe rule covers nothing, but your coverage map says it does.Nothing alerts you to silence. The gap only shows during the incident it should have caught.

The second failure is dangerous. Noise announces itself; a rule pointed at the wrong log source does not.

When rules live in Git, reverting is quick. You know what changed, when, and what the previous version looked like, so the fix is a commit and a pipeline run. When rules live in a console, you end up reconstructing a prior state from memory and screenshots while the queue fills up.

AlphaSOC deploys through its API, which is what makes the whole loop work. Your repository stays the source of truth and your pipeline pushes rules to the engine, so every rule running in production traces back to a reviewed commit.

Normalize Data to an Open Schema

Normalization is what lets one rule cover many sources. Sigma frees your logic from a vendor's query language, but the rule still references field names, and those come from whatever produced the log. If you map those fields to a common schema, the rule doesn't care which system emitted the event.

The payoff is different depending on the telemetry.

Network logs are the clean case. DNS, IP, and HTTP events describe the same handful of things no matter what emitted them: a query, destination, response, or timestamp. If you map those to common field names once, the DGA rule from the previous section runs against every DNS source you collect, whether that's a resolver, a firewall, or the network events from an EDR agent.

Cloud platform and application logs work differently, and the distinction matters. Okta, GitHub, AWS, and Dropbox each name their API actions and product features in their own way, and no amount of normalization can make those field names converge. What you normalize across them is identity. Once every source resolves to a common actor, your analyst can request all application operations performed by a user and receive a single answer covering AWS, Azure, GitHub, Okta, and Dropbox. That's an investigation gain rather than a detection gain, and for this class of log, it's where the value sits.

Both cases assume that you've collected the data somewhere, but that assumption isn't universal.

Centralized detectionFederated search
Where detections runAgainst normalized telemetry in one engine or lakeAgainst data where it already sits (Amazon S3, CrowdStrike, Databricks, Snowflake, etc.)
What you gainConsistent fields, enrichment applied before detection, and one place to queryNo data movement and no duplicate storage
What it costsCollection and storage of telemetryDetection logic translated per source, so the portability problem returns
Example platformsAlphaSOC, RunRevealQuery.AI, Vega

Federated search is a reasonable answer to a storage bill, but it's a poor answer to Detection as Code because running logic against each source in place also means translating it for each source in place.

AlphaSOC takes the centralized path deliberately: Collect, normalize, enrich, detect, index, and store. This approach makes rules run against consistent, enriched data rather than raw sources in five different shapes.

Normalizing once also makes retention affordable. AlphaSOC stores normalized telemetry in a managed data lake your team can query across months. It also uses OCSF in three places:

  • Incoming telemetry is normalized to OCSF, so field names stay consistent across sources.
  • Detections generate OCSF detection findings, so your SIEM, SOAR, and AI tools read one schema.
  • Telemetry, enrichment, and findings are stored together in an OCSF data lake your team can search.

OCSF is one of three open schemas in use today; the other two are Elastic Common Schema (ECS) and Open Source Security Events Metadata (OSSEM). Which one your tooling aligns with determines how much translation work you inherit downstream.

Manage Tuning and Suppression as Code

Tuning is where detection coverage quietly degrades. Usually, nobody deletes a rule. They might lower its severity after a bad week, add an exception for a scanner that keeps triggering it, or scope it away from the subnet that generates the most noise. Each change is defensible on the day it's made, but collectively, over a year, they determine what your SOC can and cannot see.

The problem isn't that teams tune. Tuning is necessary, and a detection engine that can't be tuned is one your analysts will learn to ignore. The problem is where the tuning lives. A severity downgrade clicked into a console leaves no author and no reason. It's a permanent change to your coverage that is recorded nowhere.

There are four failure modes that might come out of that:

  • A suppression can outlive the service it was written for, so anything later reusing that IP or domain is invisible.
  • A severity downgrade can leave the rule firing below the threshold your analysts triage, which is coverage on paper and nowhere else.
  • A narrowed scope can stop covering new systems, so your coverage shrinks every time the environment grows.
  • A raised correlation threshold can split related detections, forcing analysts to work on fragments instead of an incident.

AlphaSOC exposes tuning through its API, so per-rule severity, scoping, suppression of known-good infrastructure, and correlation thresholds are all set the same way that rules are (committed, reviewed, and deployed by your pipeline). One repository holds both what you detect and how you've tuned it.

When tuning, put severity, scoping, suppressions, and correlation thresholds under version control alongside the rule. They are detection logic, and they deserve the same review that a rule change gets. A pull request that suppresses a destination should say what the destination is, who approved it, and why, in a form that someone can read a year later.

title: Suspicious DNS Query to DGA Domain
status: stable
logsource:
  category: dns
detection:
  selection:
    query|re: '^[a-z0-9]{12,}\.(top|xyz|biz|click)$'
  condition: selection
level: low  # downgraded from medium 2026-06-14, see PR #218
custom:
  scope:
    - corp-endpoints
  suppress:
    - domain: telemetry.vendor-monitoring.example
      reason: Vendor health checks, ticket SEC-4471
      expires: 2026-12-31

In the snippet above, the level change and everything in custom is tunable, and every value is reviewable. The downgrade carries the pull request that made it. The suppression names the domain, the reason, and the date it stops applying.

Also, give suppressions an expiry. An exception that expires forces a decision: Renew it because the service still exists, or let it lapse and restore coverage. An exception that never expires accumulates into a blind spot that nobody chose. If your tooling won't expire suppressions natively, put the review date in the pull request and track it, but do it somehow.

The audit trail also answers questions that are otherwise hard to answer. When a technique gets through, the first question is whether you had a rule for it. The second is whether that rule was still doing what it was written to do. Version-controlled tuning gives you the commit history, so you have something to work with.

Tuning as code also widens who can safely make these calls. When a suppression requires a reviewed commit, a junior analyst can propose one without the risk that a bad call silently removes coverage. The review catches it, and the history preserves it.

Fast Backtesting and Retrospective Hunting

A Sigma rule runs in two directions. Forward, it evaluates events as they arrive. Backward, it evaluates everything you've already stored, and that second direction does two jobs that your team needs.

The first job is backtesting.

You run the rule against telemetry you've already collected and see exactly what it would have done: how many times it fires across a month, which assets trigger it, and whether the matches are the behavior you meant to catch or a scanner doing its job.

The rule proves itself against your environment before it reaches production. That changes what a detection pull request can contain. The sample event from the review section confirms that the logic is sound. A backtest result confirms that the logic is appropriate, which is a different question. For example, a rule that correctly identifies encoded PowerShell is well written. But a rule that correctly identifies encoded PowerShell four thousand times a day in your environment is a rule you need to scope before it ships.

The second job is hunting.

Threat intelligence arrives after the fact. For example, a report published this week describes infrastructure that was live months ago, which means the interesting question isn't whether the rule catches that campaign going forward but whether the campaign was already in your environment before anyone had a name for it. A rule written today, run across telemetry from last quarter, answers that.

Both jobs depend on the same thing: normalized telemetry, retained long enough to be worth querying and structured well enough to query quickly. Most SIEMs retain data for a few weeks before it ages out or the cost of keeping it no longer makes sense, which puts a hard ceiling on how far back either job can reach.

AlphaSOC runs Sigma rules against stored telemetry in the data lake, so both jobs use the same rule in the same format with no separate hunting syntax to learn. Deploy a rule, and it evaluates incoming events. Run it retrospectively, and it evaluates months of history.

The outcomes divide cleanly.

BacktestRetrospective hunt
QuestionWhat will this rule do in my environment?Did this activity already happen here?
Run againstRecent history, enough to gauge volume and precisionThe full retention window
Good resultA manageable number of true matchesNo matches
What you do nextTune the scope, adjust severity, then deployInvestigate the matches, or record the coverage and move on

A backtest returning nothing usually means the rule is broken. A hunt returning nothing means you weren't compromised. Same query, opposite readings, which is why it helps to be clear about which one you're running.

Build an Agentic Pipeline

Ask a simple question about any detection tool: Can an AI agent do everything your analysts can do in the console?

Many tools fail this test. If the API only returns alerts, the agent is just reading a queue. It can summarize what fired and draft a triage note, but it can't write a rule, test one, or query the telemetry behind a finding. The work still lands on a human, and the agent has automated the easiest part.

An agent that can operate the platform is a different proposition, and the requirement is unglamorous. Every action available in the console has to be available programmatically: rule creation, backtesting, data lake queries, tuning, and deployment. If any of those are console-only, that's the step where the automation stops and a person takes over.

Two things have to be true:

  • The API is the capability, meaning that it defines what can be done without a human.
  • MCP is the connection, meaning that it's how an agent reaches the capability without someone building a bespoke integration for each tool.

AlphaSOC exposes the data lake and the engine through MCP, so Claude Code, Codex, and similar tools connect directly and work against the same functions your team uses.

A new campaign gets written up publicly, and the agent runs the whole response as follows:

  • It reads the threat intelligence report and extracts IOCs and TTPs.
  • It translates them into Sigma rules targeting the described behavior.
  • It backtests each rule against your stored telemetry.
  • Finally, it deploys the rules that pass and reports the backtest results.

Your team ends up with two answers from one piece of intelligence. The retrospective result tells you whether the campaign has already touched your environment, and you're hoping that comes back empty. The deployed rule tells you you're covered from here. Both arrive before anyone has read the report.

For this workflow to run, the previous five practices we discussed must be in place.

  • The agent writes Sigma because Sigma deploys without conversion.
  • It commits to a repository because rules are version-controlled.
  • It queries months of history because telemetry is normalized and retained.
  • It backtests because the engine runs rules against stored data.
  • It tunes what it deploys because tuning is an API call.

If you take away any one of them, the loop breaks at that step and waits for a human.

The direction of travel is the part worth planning for. Vendors are currently racing to build proprietary AI agents into their platforms, which look more integrated today. It's a bet against the model providers, who will deliver better agents more cheaply than any security vendor can and keep doing it. The durable position isn't owning the agent. It's owning the data and detection layer the agents plug into and keeping that layer open enough that your team can swap agents as they improve.

AlphaSOC is built for that arrangement. The API covers what the console covers, MCP exposes the data lake to external agents, and the whole surface stays on open standards, so the tools your team uses to hunt and investigate can change without the layer underneath them changing at all.

A threat intelligence report on a new campaign enters an AI agent such as Claude or Codex, which writes Sigma rules from the IOCs and TTPs, backtests them against an OCSF data lake of normalized events, and deploys the passing rules to the engine. The agent returns a retrospective result and confirmed coverage, two answers from one report.
An agent operating the detection pipeline through MCP. Intelligence goes in, rules and backtest results come back, and no step waits for a console.

Conclusion

The six practices discussed in this article are deliberately sequential. Portable rules are worth little if the underlying data isn't normalized, version control is worth less if tuning happens outside it, and backtesting needs retained history to run against. The agent workflow in the last section needs all five practices for it to function. This dependency is the argument for treating the detection layer as infrastructure rather than as a feature of whichever platform you happen to own. Rules, schema, retention, and API surface outlast any particular SIEM, and they outlast any particular agent. As new agents are released, the layer they plug into is the part you should be deliberate about.

When you evaluate a detection engine, think about the following: Does it read Sigma natively, or convert first? Is tuning an API call or a console click? How far back does stored telemetry go, and can you run a rule against all of it? Does the API cover everything the console does? An engine that answers those four questions well is one your team can build on and one your agents can operate.

Evaluate AlphaSOC for free.

AlphaSOCDetection as CodeSigmaOCSFMCP