{"id":369,"date":"2025-10-28T12:04:25","date_gmt":"2025-10-28T12:04:25","guid":{"rendered":"https:\/\/logsmith.io\/?p=369"},"modified":"2025-10-28T12:04:27","modified_gmt":"2025-10-28T12:04:27","slug":"detection-as-code","status":"publish","type":"post","link":"https:\/\/logsmith.io\/index.php\/2025\/10\/28\/detection-as-code\/","title":{"rendered":"\ud83d\udd27 Detection-as-Code: What It Really Means"},"content":{"rendered":"\n<p><em>Masterclass Series \u2013 Part 2<\/em><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udded TL;DR: It\u2019s Not Just \u201cPut Your Rules in Git\u201d<\/h2>\n\n\n\n<p>\u201cDetection-as-Code\u201d sounds like a trendy buzzword \u2014 and in some orgs, that\u2019s all it is. But when done right, it\u2019s a powerful transformation that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Turns detections into <strong>collaborative, auditable, testable logic<\/strong><\/li>\n\n\n\n<li>Makes your SIEM stack <strong>faster to adapt, easier to trust, and safer to scale<\/strong><\/li>\n\n\n\n<li>Pulls detection engineering out of the \u201cpower user\u201d corner and into proper software development<\/li>\n<\/ul>\n\n\n\n<p>And the best part? You don\u2019t need a Kubernetes cluster or a team of DevOps engineers to start doing it.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddf1 What <em>Is<\/em> Detection-as-Code?<\/h2>\n\n\n\n<p>At its core, Detection-as-Code (DaC) is this:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\ud83d\udd04 <strong>Treat detection logic like software code \u2014 not static SIEM config.<\/strong><\/p>\n<\/blockquote>\n\n\n\n<p>This includes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Storing your rules, macros, thresholds, parsers in Git<\/strong><\/li>\n\n\n\n<li><strong>Structuring your detection content as modular, reusable files<\/strong><\/li>\n\n\n\n<li><strong>Using proper workflows \u2014 pull requests, reviews, CI pipelines<\/strong><\/li>\n\n\n\n<li><strong>Building, testing, and deploying changes safely and visibly<\/strong><\/li>\n<\/ul>\n\n\n\n<p>Think: the same way dev teams manage production code, you manage production logic.<\/p>\n\n\n\n<p><strong>It\u2019s not a tool.<\/strong> It\u2019s a <strong>mindset<\/strong> backed by a workflow.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\ude9a Old World vs New World<\/h2>\n\n\n\n<p>Let\u2019s compare what many orgs still do vs what a Detection-as-Code approach looks like.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>\ud83c\udfda\ufe0f Old School SIEM<\/th><th>\ud83d\ude80 Detection-as-Code<\/th><\/tr><\/thead><tbody><tr><td>Click \u201cSave\u201d in Splunk Web<\/td><td>Git commit + push<\/td><\/tr><tr><td>One-off rule tweaks nobody tracks<\/td><td>Version history + code review<\/td><\/tr><tr><td>Same logic copied across rules<\/td><td>Reusable macros or libraries<\/td><\/tr><tr><td>Rules break silently<\/td><td>CI pipeline catches errors<\/td><\/tr><tr><td>Alert noise is \u201cjust part of it\u201d<\/td><td>Alerting logic is tested + refined<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddea Real-World Example: Managing Correlation Rules in a Custom App (<code>savedsearches.conf<\/code>)<\/h2>\n\n\n\n<p>Let\u2019s say we\u2019re building a detection for <strong>RDP lateral movement<\/strong>, and we want to manage it properly \u2014 no UI edits, no ad hoc hacks. Just good, clean detection engineering.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\uddc2\ufe0f Step 1: Create or Edit a Custom App<\/h3>\n\n\n\n<p>Create a folder structure for your detection app, e.g.:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>my_detection_app\/\n\u2514\u2500\u2500 default\/\n    \u2514\u2500\u2500 savedsearches.conf\n<\/code><\/pre>\n\n\n\n<p>Inside <code>savedsearches.conf<\/code>, we define the rule:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;ES - Lateral Movement via RDP - Multiple Targets]\naction.notable = 1\naction.notable.param.rule_description = This detection identifies users making RDP connections to multiple unique internal hosts in a short period, which may indicate lateral movement.\naction.notable.param.severity = high\naction.notable.param.urgency = high\naction.notable.param.risk_score = 80\naction.notable.param.confidence = 90\naction.notable.param.priority = critical\naction.notable.param.rule_title = RDP Lateral Movement - Multiple Hosts\naction.notable.param.threat_object_field = user\naction.notable.param.threat_object_type = user\naction.notable.param.threat_group = endpoint\naction.notable.param.ttl = 60\nalert.track = 1\nalert.suppress = 0\nalert.suppress.fields = user\ncron_schedule = *\/5 * * * *\ndispatch.earliest_time = -5m@m\ndispatch.latest_time = now\nenableSched = 1\ndescription = Correlation rule to detect potential lateral movement via RDP connections to multiple targets from a single user in a short window.\nsearch = `get_windows_rdp_logs()` \n  | stats dc(dest) AS dest_count, values(dest) as dest_list, values(src) as sources by user \n  | where dest_count >= 5 \n  | `notable` \n\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83d\udd01 <em>No SPL in the UI \u2014 it&#8217;s all here<\/em><\/li>\n\n\n\n<li>\ud83e\udde0 Easy to tweak logic or thresholds<\/li>\n\n\n\n<li>\ud83d\udce6 Packaged like any other app<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udd27 Step 2: Track It in Git<\/h3>\n\n\n\n<p>Add it to your Git repo and version it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git add my_detection_app\/default\/savedsearches.conf\ngit commit -m \"Add RDP Lateral Movement detection to app\"\n<\/code><\/pre>\n\n\n\n<p>Make changes via pull requests \u2014 not directly in Splunk.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\ude80 Step 3: Deploy It Like an App<\/h3>\n\n\n\n<p>You can deploy this app:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Via scripted packaging (<code>.tgz<\/code>)<\/li>\n\n\n\n<li>Through GitLab pipelines<\/li>\n\n\n\n<li>Or even manually (copy to <code>$SPLUNK_HOME\/etc\/apps\/<\/code>)<\/li>\n<\/ul>\n\n\n\n<p>On restart (or reload), your detection is <strong>live<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83e\uddea Bonus: Local Testing Before Commit<\/h3>\n\n\n\n<p>You can manually test the <code>search = ...<\/code> SPL in Search, or:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use a dev\/test instance<\/li>\n\n\n\n<li>Test macros (e.g. <code>get_endpoint_data()<\/code>) for expansion<\/li>\n\n\n\n<li>Validate logic before pushing<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 What You Get<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\ud83e\uddfe Version control with real audit trails<\/li>\n\n\n\n<li>\ud83d\udee0\ufe0f Consistent detection packaging<\/li>\n\n\n\n<li>\ud83e\uddea Ability to test and review changes<\/li>\n\n\n\n<li>\ud83d\udce6 Easy rollback and portability<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>This is <strong>Detection-as-Code in its simplest form<\/strong>: treat correlation rules like software. No manual clicks. Just real config, owned in Git, shipped like any other product.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\uddf0 Tooling: What You Need (And What You Don\u2019t)<\/h2>\n\n\n\n<p>Many think Detection-as-Code = complex infrastructure. Not true.<\/p>\n\n\n\n<p>Here\u2019s what the <strong>maturity curve<\/strong> looks like:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Level<\/th><th>Setup<\/th><th>Git Needed?<\/th><th>Bonus<\/th><\/tr><\/thead><tbody><tr><td>\ud83d\udfe2 Level 1: Git Lite<\/td><td>Store detection rules in Git repos manually<\/td><td>Yes<\/td><td>Manual reviews, simple folder structure<\/td><\/tr><tr><td>\ud83d\udfe1 Level 2: Workflow<\/td><td>Introduce merge requests and peer review<\/td><td>Yes<\/td><td>Team collaboration + structured changes<\/td><\/tr><tr><td>\ud83d\udfe0 Level 3: CI\/CD<\/td><td>Add pipelines to validate, package and deploy content<\/td><td>Yes<\/td><td>Auto-checks, app inspect, validation gates<\/td><\/tr><tr><td>\ud83d\udd34 Level 4: Automation Nirvana<\/td><td>Fully automated testing, staging, deployment<\/td><td>Yes<\/td><td>Auto-rollback, tests on sample data, tagging, approvals<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Even <strong>Level 1<\/strong> gives you wins. You don\u2019t need to jump to full automation to start.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udc63 How to Start Detection-as-Code <em>Right Now<\/em><\/h2>\n\n\n\n<p>No budget? No problem.<\/p>\n\n\n\n<p>Start with this:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Create a Git repo<\/strong> (private is fine) for your detection content<\/li>\n\n\n\n<li>Export all saved searches and macros into <code>.conf<\/code> files<\/li>\n\n\n\n<li>Commit those files with useful commit messages<\/li>\n\n\n\n<li>Start writing detection rules in modular form \u2014 use macros, tags, comments<\/li>\n\n\n\n<li>Introduce <strong>merge requests<\/strong> for any change<\/li>\n\n\n\n<li>If you&#8217;re using Splunk:\n<ul class=\"wp-block-list\">\n<li>Use <code>btool<\/code> locally for testing<\/li>\n\n\n\n<li>Use <code>app inspect<\/code> before deploying<\/li>\n\n\n\n<li>Zip and upload via the UI or API<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<p>\ud83d\udca1 <strong>Tip:<\/strong> Work in modular apps (e.g. <code>detections_custom<\/code>) so you can deploy safely without risking core ES apps.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde0 Cultural Shift: Detection Engineers = Engineers<\/h2>\n\n\n\n<p>One of the biggest blockers to Detection-as-Code isn\u2019t tech \u2014 it\u2019s <strong>attitude<\/strong>.<\/p>\n\n\n\n<p>Detection engineers are often seen as &#8220;power users&#8221; or &#8220;admins&#8221;. That\u2019s wrong.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Detection Engineers <strong>design<\/strong>, <strong>build<\/strong>, <strong>debug<\/strong>, <strong>document<\/strong>, and <strong>maintain<\/strong> logic that directly affects the organisation\u2019s risk posture.<\/p>\n<\/blockquote>\n\n\n\n<p>That\u2019s engineering.<\/p>\n\n\n\n<p>And that means we should:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Work from branches, not prod<\/li>\n\n\n\n<li>Review each other\u2019s changes<\/li>\n\n\n\n<li>Refactor bad logic<\/li>\n\n\n\n<li>Write comments like devs do<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd04 But My SOC Isn\u2019t Ready\u2026<\/h2>\n\n\n\n<p>That\u2019s okay.<\/p>\n\n\n\n<p>You can still be the person who <strong>starts the shift<\/strong>. Even if it&#8217;s just you and GitHub at first.<\/p>\n\n\n\n<p>Introduce version control. Start writing modular detections. Share the benefits.<\/p>\n\n\n\n<p>Every mature Detection-as-Code setup started with one person saying:<br><strong>\u201cThere has to be a better way.\u201d<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcac Final Thoughts<\/h2>\n\n\n\n<p>Detection-as-Code is <strong>not about overengineering<\/strong>. It\u2019s about building <strong>trust, quality, and agility<\/strong> into your detection content.<\/p>\n\n\n\n<p>Even small steps have massive payoffs:<\/p>\n\n\n\n<p>\u2705 Fewer mistakes<br>\u2705 Better visibility<br>\u2705 Faster fixes<br>\u2705 Happier SOCs<br>\u2705 Stronger detections<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udccc Coming Next in the Masterclass Series\u2026<\/h2>\n\n\n\n<p>\ud83d\udd1c <strong>Accelerated Data Models: When They Help \u2014 and When They Hurt<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Masterclass Series \u2013 Part 2 \ud83e\udded TL;DR: It\u2019s Not Just \u201cPut Your Rules in Git\u201d \u201cDetection-as-Code\u201d sounds like a trendy&#8230; <\/p>\n<div class=\"art-el-more\"><a href=\"https:\/\/logsmith.io\/index.php\/2025\/10\/28\/detection-as-code\/\" class=\"art-link art-color-link art-w-chevron\">Read more<\/a><\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"iawp_total_views":19,"footnotes":""},"categories":[37],"tags":[30,41,42,38,31],"class_list":["post-369","post","type-post","status-publish","format-standard","hentry","category-masterclass","tag-detection-engineering","tag-detection-as-code","tag-gitlab","tag-masterclass","tag-splunk-es"],"acf":[],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/posts\/369","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/comments?post=369"}],"version-history":[{"count":2,"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/posts\/369\/revisions"}],"predecessor-version":[{"id":371,"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/posts\/369\/revisions\/371"}],"wp:attachment":[{"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/media?parent=369"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/categories?post=369"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/logsmith.io\/index.php\/wp-json\/wp\/v2\/tags?post=369"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}