The repository uses two permanent branches:
| Branch | Purpose | Who can push directly |
|---|---|---|
dev |
Staging — analysts submit detections here | Nobody |
main |
Production — only promoted from dev |
Nobody |
All changes to either branch must go through a pull request.
git checkout dev && git pull origin dev
git checkout -b dev/<your-name>/<detection-name>
# write and validate your detection
git push origin dev/<your-name>/<detection-name>
# open PR → target: dev
After review and merge to dev, a separate PR promotes the changes to main:
PR: dev → main
CI validates again
Approving review required
Merge → deploy to Splunk
dev/<analyst-name>/<change-summary>
Examples:
dev/alice/brute-force-rdp
dev/bob/powershell-encoding-tuning
dev/security-team/lateral-movement-coverage
Avoid a shared dev branch per analyst (e.g. dev/alice). Short-lived, scoped branches keep each change isolated and make PR ownership unambiguous.
- Analyst creates a branch from
devfollowing the naming convention above - Analyst writes or updates detection YAML files under
detections/ - Analyst validates locally:
python3 scripts/validate.py --no-splunk <file> - Analyst opens a PR into
dev - CI runs
Validate Changed Detections— schema + SPL syntax check - At least one reviewer approves the detection logic
- Merge to
dev - A promotion PR (
dev → main) is opened (manually or via automation) - CI validates again
- Merge to
main Deploy Detections to Splunkruns automatically
The deploy workflow verifies that every push to main originated from a merged PR. This is a soft guard against accidental direct pushes.
Apply these settings to both dev and main:
| Setting | Value |
|---|---|
| Require pull request before merging | Enabled |
| Required approving reviews | 1 |
| Dismiss stale approvals after new commits | Enabled |
| Require approval of the most recent push | Enabled |
| Require status checks before merging | Enabled |
| Required status check | Validate Changed Detections |
| Require branches to be up to date before merging | Enabled |
| Require conversation resolution before merging | Enabled |
| Allow force pushes | Disabled |
| Allow deletions | Disabled |
| Include administrators | Enabled |
GitHub plan requirement: Branch protection rules on private repositories require GitHub Pro or GitHub Enterprise. On the free plan, these settings cannot be enforced via the API.
If branch protection is unavailable, the deploy workflow partially compensates — it blocks deployments that did not originate from a merged PR. However, this does not prevent direct pushes to
devormainby users with write access.Mitigations for free plan:
- Grant analysts
readaccess only; submit changes via forks- Move the repository to a GitHub organization on a paid plan
- Enable repository rulesets (available on some org plans at no extra cost)
Detection files must not be deleted from the repository. The CI pipeline blocks PRs that contain deleted detection files.
To retire a detection:
- Set
status: deprecatedin the YAML file - Merge the change — the detection is updated to disabled in Splunk
- Handle Splunk removal through a deliberate decommission step (manual or a separate workflow)
Deleting a YAML file leaves no record of what the detection was, making it impossible to automate safe Splunk cleanup.
| Mechanism | Trigger | Scope |
|---|---|---|
| GitHub "delete branch on merge" | Automatic after PR merge | The merged PR branch only |
Cleanup Merged Branches workflow |
Weekly (Sunday 03:17 UTC) or manual | All merged analyst branches older than 14 days |
The cleanup workflow only removes branches that:
- Are already merged into
main - Have a last commit older than 14 days
- Match a managed prefix (
dev/,feature/,fix/,detection/,hotfix/)
main and dev are never deleted by the workflow.
- Keep PRs focused on one detection or one tightly related set of detections
- Validate locally before opening a PR — catches schema errors without spending CI time
- Increment
versionwhen modifying an existing detection - Keep detection
idvalues stable — they are the stable identifier for the saved search across environments - Treat YAML
statusas lifecycle metadata, not the only source of deployment state - Let the deploy workflow set the effective Splunk state with
DEPLOY_STATUS; merges tomaindeploy withDEPLOY_STATUS=production - Use
status: draftfor detections that are not ready to be enabled - Use
status: deprecatedto retire detections; draft and deprecated detections are always deployed disabled