diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f2ea81a..b5b3eca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,6 +52,12 @@ jobs: name: codecov-umbrella fail_ci_if_error: false + - name: Install bats + uses: bats-core/bats-action@3.0.0 + + - name: Run README check tests + run: bats scripts/check_readmes_test.bats + - name: Generate Rules CRD run: make generate-rules-crd diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..37ef783 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,20 @@ +# Agent Instructions for rulelibrary + +When you add, modify, or delete a rule under `pkg/rules/`: + +1. Each rule directory MUST contain a non-empty `README.md` that follows + the template used by sibling rules (see any existing rule's README.md + for the structure: metadata table + Description, Attack Technique, How + It Works, Investigation Steps, Remediation, False Positives). +2. If you add a new rule, generate its `README.md` in the same commit. +3. If you modify a rule's YAML (CEL expression, severity, MITRE fields, + profileDependency), update the affected sections of its `README.md` + in the same commit. +4. Do not commit a rule change without its corresponding README update — + the release build will fail. + +The release build's `gen.sh` invokes `scripts/check_readmes.sh`, which +exits non-zero if any rule under `pkg/rules/` is missing or has an empty +`README.md`. The README content is consumed downstream by the +`armo-rulelibrary` build (which embeds this repo as a submodule) and +shipped as the `documentation` field on each rule. diff --git a/gen.sh b/gen.sh index 3ef0e61..b2ebf38 100755 --- a/gen.sh +++ b/gen.sh @@ -49,6 +49,14 @@ if [ ! -d "$RULES_DIR" ]; then exit 1 fi +# Enforce that every rule directory has a non-empty README.md. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [ ! -f "$SCRIPT_DIR/scripts/check_readmes.sh" ]; then + echo "Error: scripts/check_readmes.sh not found relative to gen.sh" >&2 + exit 1 +fi +bash "$SCRIPT_DIR/scripts/check_readmes.sh" "$RULES_DIR" + # Find all YAML files in rule directories RULE_FILES=$(find "$RULES_DIR" -name "*.yaml" -type f | sort) diff --git a/scripts/check_readmes.sh b/scripts/check_readmes.sh new file mode 100755 index 0000000..4534955 --- /dev/null +++ b/scripts/check_readmes.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Verify every rule directory under the given path has a non-empty README.md. +# Used by the release build as a hard gate. + +set -euo pipefail + +RULES_DIR="${1:-pkg/rules}" + +if [ ! -d "$RULES_DIR" ]; then + echo "check_readmes: directory not found: $RULES_DIR" >&2 + exit 1 +fi + +failed=0 +while IFS= read -r -d '' rule_dir; do + readme="$rule_dir/README.md" + if [ ! -f "$readme" ]; then + echo "check_readmes: missing README.md in $(basename "$rule_dir")" >&2 + failed=1 + continue + fi + if [ ! -s "$readme" ]; then + echo "check_readmes: empty README.md in $(basename "$rule_dir")" >&2 + failed=1 + fi +done < <(find "$RULES_DIR" -mindepth 1 -maxdepth 1 -type d -name 'r[0-9]*' -print0) + +if [ "$failed" -ne 0 ]; then + echo "check_readmes: one or more rules are missing or have empty README.md" >&2 + exit 1 +fi + +echo "check_readmes: OK" diff --git a/scripts/check_readmes_test.bats b/scripts/check_readmes_test.bats new file mode 100644 index 0000000..478a9a6 --- /dev/null +++ b/scripts/check_readmes_test.bats @@ -0,0 +1,33 @@ +#!/usr/bin/env bats + +setup() { + TEST_DIR=$(mktemp -d) + export RULES_DIR="$TEST_DIR/pkg/rules" + mkdir -p "$RULES_DIR/r0001-good" "$RULES_DIR/r0002-bad" + echo "# Rule" > "$RULES_DIR/r0001-good/README.md" + # r0002-bad has no README +} + +teardown() { + rm -rf "$TEST_DIR" +} + +@test "check_readmes.sh fails when a rule directory is missing README.md" { + run bash "$BATS_TEST_DIRNAME/check_readmes.sh" "$RULES_DIR" + [ "$status" -ne 0 ] + [[ "$output" == *"r0002-bad"* ]] +} + +@test "check_readmes.sh passes when all rule directories have README.md" { + echo "# Rule 2" > "$RULES_DIR/r0002-bad/README.md" + run bash "$BATS_TEST_DIRNAME/check_readmes.sh" "$RULES_DIR" + [ "$status" -eq 0 ] +} + +@test "check_readmes.sh fails when README.md exists but is empty" { + echo "# Rule 2" > "$RULES_DIR/r0002-bad/README.md" + : > "$RULES_DIR/r0001-good/README.md" + run bash "$BATS_TEST_DIRNAME/check_readmes.sh" "$RULES_DIR" + [ "$status" -ne 0 ] + [[ "$output" == *"r0001-good"* ]] +}