Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 16 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=lf

# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.csv text
*.sh text
*.py text

# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary

# force lf for yaml files
*.yml text eol=lf
*.yaml text eol=lf
45 changes: 45 additions & 0 deletions .github/workflows/yaml-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: YAML Validation

on:
pull_request:
paths:
- 'detections/**/*.yml'
- 'detections/**/*.yaml'
push:
branches:
- develop
paths:
- 'detections/**/*.yml'
- 'detections/**/*.yaml'

jobs:
validate:
name: Validate Detection YAML Files
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
architecture: 'x64'

- name: Install yamllint
run: pip install yamllint

- name: Set up Go
uses: actions/setup-go@v6

- name: Install yamlfmt
run: |
go install github.com/google/yamlfmt/cmd/yamlfmt@latest
echo "$HOME/go/bin" >> $GITHUB_PATH

- name: Validate all detection YAML files
run: |
python scripts/validate_yaml.py detections/
16 changes: 15 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,24 @@ repos:
exclude: "package/bin/da_ess_contentupdate/|package/bin/splunklib/|venv/"
- id: check-json
- id: check-symlinks
- id: check-yaml
# - id: check-yaml
- id: pretty-format-json
args: [--autofix]
- id: requirements-txt-fixer
- id: detect-aws-credentials
args: ['--allow-missing-credentials']
- id: detect-private-key
- id: forbid-submodules

# yamlfmt: Auto-format YAML files in detections/ folder
- repo: local
hooks:
- id: yamlfmt
name: yamlfmt (detections only)
description: Format YAML files in detections/ with yamlfmt
entry: python .pre-commit-hooks/yamlfmt-hook.py
language: system
files: ^detections/.*\.(yml|yaml)$
pass_filenames: true
# Optional: Specify custom yamlfmt binary path if not in PATH
# args: [--yamlfmt-path, /path/to/yamlfmt]
124 changes: 124 additions & 0 deletions .pre-commit-hooks/yamlfmt-hook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""
Pre-commit hook script for yamlfmt
Formats YAML files in the detections/ directory only
Cross-platform compatible (Linux, macOS, Windows)
"""
import argparse
import os
import subprocess
import sys
from pathlib import Path


def find_yamlfmt(custom_path=None):
"""Find yamlfmt executable in common locations or use custom path

Args:
custom_path: Optional path to yamlfmt binary

Returns:
Path to yamlfmt executable or None if not found
"""
# If custom path provided, verify and use it
if custom_path:
custom_path = Path(custom_path)
if custom_path.exists():
return str(custom_path)
else:
print(f"ERROR: yamlfmt not found at specified path: {custom_path}")
return None

# Check if yamlfmt is in PATH
for cmd in ['yamlfmt', 'yamlfmt.exe']:
try:
result = subprocess.run([cmd, '--version'], capture_output=True, text=True)
if result.returncode == 0:
return cmd
except FileNotFoundError:
pass

# Check common installation paths
possible_paths = [
Path.home() / 'go' / 'bin' / 'yamlfmt',
Path.home() / 'go' / 'bin' / 'yamlfmt.exe',
Path('/usr/local/bin/yamlfmt'),
Path('/usr/bin/yamlfmt'),
# Check in repo yamlfmt-main folder (for development)
Path(__file__).parent.parent.parent / 'yamlfmt-main' / 'yamlfmt.exe',
]

for path in possible_paths:
if path.exists():
return str(path)

print("ERROR: yamlfmt not found. Install with: go install github.com/google/yamlfmt/cmd/yamlfmt@latest")
print("Make sure $GOPATH/bin is in your PATH")
print(f"Or place yamlfmt.exe in: {Path.home() / 'go' / 'bin'}")
print("Or use --yamlfmt-path to specify a custom yamlfmt binary location")
return None


def main():
"""Run yamlfmt on changed YAML files in detections/"""
# Parse arguments
parser = argparse.ArgumentParser(description='Pre-commit hook for yamlfmt')
parser.add_argument('--yamlfmt-path', help='Path to yamlfmt binary')
parser.add_argument('files', nargs='*', help='Files to format')

args = parser.parse_args()
files = args.files

if not files:
return 0

# Filter to only YAML files in detections/
yaml_files = [
f for f in files
if f.startswith('detections/') and f.endswith(('.yml', '.yaml'))
]

if not yaml_files:
return 0

# Find yamlfmt
yamlfmt = find_yamlfmt(args.yamlfmt_path)
if not yamlfmt:
return 1

# Get repo root to find .yamlfmt config
repo_root = subprocess.run(
['git', 'rev-parse', '--show-toplevel'],
capture_output=True,
text=True,
check=True
).stdout.strip()

config_path = Path(repo_root) / '.yamlfmt'

# Run yamlfmt on each file
failed = False
for file in yaml_files:
file_path = Path(repo_root) / file
if not file_path.exists():
continue

cmd = [yamlfmt]
if config_path.exists():
cmd.extend(['-conf', str(config_path)])
cmd.append(str(file_path))

result = subprocess.run(cmd, capture_output=True, text=True)

if result.returncode != 0:
print(f"[FAIL] yamlfmt failed for {file}:")
print(result.stderr)
failed = True
else:
print(f"[OK] Formatted: {file}")

return 1 if failed else 0


if __name__ == '__main__':
sys.exit(main())
12 changes: 12 additions & 0 deletions .yamlfmt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
formatter:
type: basic
indent: 4
include_document_start: false
line_ending: lf
retain_line_breaks: false
scan_folded_as_literal: true
indentless_arrays: false
pad_line_comments: 1
eof_newline: true
max_line_length: 0
trim_trailing_whitespace: true
126 changes: 126 additions & 0 deletions .yamllint
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# https://yamllint.readthedocs.io/en/latest/configuration.html
# yamllint configuration for security_content
# Aligned with .yamlfmt config to avoid conflicts
# This config validates YAML syntax and enforces consistency while yamlfmt handles formatting

extends: default

# Ignore all YAML files except those in detections/
ignore: |
/.git/
/dist/
/venv/
/node_modules/
/*.yml
/*.yaml
/app_template/
/baselines/
/dashboards/
/data_sources/
/deployments/
/docs/
/lookups/
/macros/
/notebooks/
/playbooks/
/removed/
/response_templates/
/stories/
/workbooks/

rules:
# Comments: Enforce proper spacing for readability
# - require-starting-space: Ensures "# comment" not "#comment"
# - min-spaces-from-content: Requires space between code and inline comment
comments:
require-starting-space: true
min-spaces-from-content: 1

# Comments indentation: Disabled to allow flexible comment placement
# Useful for multi-line field comments that may not align with strict indent rules
comments-indentation: disable

# Document start: Don't require "---" at the beginning
# Our YAML files are standalone detection rules, not multi-document streams
document-start: {present: false}

# Empty lines: Allow up to 2 blank lines for visual separation
# Helps organize long detection rules into logical sections
empty-lines: {max: 2, max-start: 2, max-end: 2}

# Indentation: Disabled - yamlfmt handles this consistently
# yamlfmt uses 4-space base indent with 2-space offsets for nested structures
# yamllint's indent rules conflict with yamlfmt's behavior, so we let yamlfmt control it
indentation: disable

# Line length: Disabled due to extremely long search queries
# Detection rules often have 500+ character search fields that can't be wrapped
line-length: disable

# New line at end of file: Required for POSIX compliance
# Prevents issues with git diffs and ensures proper file termination
new-line-at-end-of-file: enable

# Trailing spaces: Not allowed
# Catches accidental whitespace that causes git diff noise
trailing-spaces: {}

# New lines: LF only (Unix style)
# Enforces consistent line endings across all platforms for git compatibility
new-lines: {type: unix}

# Key duplicates: Critical validation to catch errors
# Prevents accidentally defining the same field twice (e.g., two "name:" fields)
key-duplicates: enable

# Truthy values: Allow both YAML 1.1 and 1.2 boolean representations
# Permits 'true/false', 'yes/no', 'on/off' for compatibility with various tools
# check-keys: false allows "no" as a key name (e.g., for test scenarios)
truthy:
allowed-values: ['true', 'false', 'yes', 'no', 'on', 'off']
check-keys: false

# Brackets: Consistent spacing in flow sequences []
# Enforces "[item1, item2]" not "[ item1, item2 ]"
brackets:
min-spaces-inside: 0
max-spaces-inside: 0

# Braces: Consistent spacing in flow mappings {}
# Allows "{key: value}" with optional space after colon
braces:
min-spaces-inside: 0
max-spaces-inside: 1
min-spaces-inside-empty: 0
max-spaces-inside-empty: 0

# Colons: Enforce "key: value" spacing (not "key : value" or "key:value")
# Standard YAML formatting for readability
colons:
max-spaces-before: 0
max-spaces-after: 1

# Commas: Enforce consistent spacing in flow collections
# Requires "item1, item2" not "item1,item2" or "item1 ,item2"
commas:
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1

# Hyphens: Enforce "- item" spacing for array items (not "-item" or "- item")
# Ensures consistent block sequence formatting
hyphens:
max-spaces-after: 1

# Empty values: Control where null/empty values are allowed
# Allow "field:" with no value in mappings (common in our detection rules)
# Forbid in flow mappings to catch likely errors: "{key:}" is probably wrong
empty-values:
forbid-in-block-mappings: false
forbid-in-flow-mappings: true

# Quoted strings: Allow both single and double quotes
# Don't require quotes on unquoted strings - let yamlfmt handle quote style
quoted-strings:
quote-type: any
required: false
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Follow these steps to get started with Splunk Security Content.
1. Clone this repository using `git clone https://github.com/splunk/security_content.git`
2. Navigate to the repository directory using `cd security_content`
3. Install contentctl using `pip install contentctl` to install the latest version of contentctl, this is a pre-requisite to validate, build and test the content like the Splunk Threat Research team
4. Install pre-commit using `pip install pre-commit` then proceed to installing the hooks via `pre-commit install`. this is a pre-requisite to validate and apply the proper formatting.

# Quick Start 🚀

Expand Down
Loading
Loading