diff --git a/scripts/seed-repo-template.sh b/scripts/seed-repo-template.sh index 53a6f2191..6bcdbcce5 100755 --- a/scripts/seed-repo-template.sh +++ b/scripts/seed-repo-template.sh @@ -77,12 +77,14 @@ readonly -a WORKFLOW_MANIFEST=( # Baseline files (AC #3). One row per file: "path|source". # source=gen → generated inline by _gen_baseline. -# source=fetch: → fetched verbatim from STANDARDS_REPO. +# source=fetch: → fetched verbatim from STANDARDS_REPO at that path. +# source=fetch (bare) → the Dependabot stack special case (path is DEPENDABOT_STACK-derived). readonly -a BASELINE_MANIFEST=( "AGENTS.md|gen" "CLAUDE.md|gen" ".github/CODEOWNERS|gen" ".github/dependabot.yml|fetch" + ".gitleaks.toml|fetch:standards/gitleaks.toml" "sonar-project.properties|gen" ".gitignore|gen" "LICENSE|gen" @@ -309,13 +311,27 @@ _emit_baseline() { echo "::error::unknown baseline file: $path" >&2 return 2 fi + local content std_path if [ "$source" = "gen" ]; then _gen_baseline "$path" else - local content - content="$(_fetch_standard "standards/dependabot/${DEPENDABOT_STACK}.yml")" || true + if [ "$source" = "fetch" ]; then + # Dependabot special case: the standards path is DEPENDABOT_STACK-derived. + std_path="standards/dependabot/${DEPENDABOT_STACK}.yml" + elif [ "${source#fetch:}" != "$source" ]; then + # General verbatim fetch: source=fetch: (e.g. .gitleaks.toml). + std_path="${source#fetch:}" + if [ -z "$std_path" ]; then + echo "::error::empty standards path in source '${source}' for ${path}" >&2 + return 2 + fi + else + echo "::error::unknown baseline source '${source}' for ${path}" >&2 + return 2 + fi + content="$(_fetch_standard "$std_path")" || true if [ -z "$content" ]; then - echo "::error::could not fetch standards/dependabot/${DEPENDABOT_STACK}.yml from ${STANDARDS_REPO}" >&2 + echo "::error::could not fetch ${std_path} from ${STANDARDS_REPO}" >&2 return 1 fi printf '%s\n' "$content" diff --git a/scripts/template_stub_drift.sh b/scripts/template_stub_drift.sh index 54a36b89e..5b87eb904 100644 --- a/scripts/template_stub_drift.sh +++ b/scripts/template_stub_drift.sh @@ -52,6 +52,7 @@ readonly -a TEMPLATE_DRIFT_FILES=( ".github/dependabot.yml|--emit-baseline|.github/dependabot.yml" ".github/CODEOWNERS|--emit-baseline|.github/CODEOWNERS" "CLAUDE.md|--emit-baseline|CLAUDE.md" + ".gitleaks.toml|--emit-baseline|.gitleaks.toml" ) # ── Documented allowlist (AC #2) ────────────────────────────────────────────── diff --git a/tests/template_stub_drift.bats b/tests/template_stub_drift.bats index 909495f18..031cec554 100644 --- a/tests/template_stub_drift.bats +++ b/tests/template_stub_drift.bats @@ -58,6 +58,7 @@ setup() { [[ "$output" == *".github/dependabot.yml"* ]] [[ "$output" == *".github/CODEOWNERS"* ]] [[ "$output" == *"CLAUDE.md"* ]] + [[ "$output" == *".gitleaks.toml"* ]] } @test "covered set: excludes the allowlisted ci.yml (per-stack customizable)" { diff --git a/tests/test_seed_repo_template.bats b/tests/test_seed_repo_template.bats index a1cc495fa..bfee8e3ae 100644 --- a/tests/test_seed_repo_template.bats +++ b/tests/test_seed_repo_template.bats @@ -97,8 +97,8 @@ _stub_gh_empty() { run bash "$SEED" --list-baseline [ "$status" -eq 0 ] for f in AGENTS.md CLAUDE.md .github/CODEOWNERS .github/dependabot.yml \ - sonar-project.properties .gitignore LICENSE SECURITY.md \ - README.md BOOTSTRAP.md; do + .gitleaks.toml sonar-project.properties .gitignore LICENSE \ + SECURITY.md README.md BOOTSTRAP.md; do [[ "$output" == *"$f"* ]] || { echo "missing baseline: $f" >&2; false; } done } @@ -237,6 +237,27 @@ EOF [[ "$output" == *"auto-trigger"* ]] } +# ── .gitleaks.toml baseline is fetched verbatim from standards/gitleaks.toml ─── +# Seeds the secret-scan job's required config (push-protection.md); without it the +# template ci.yml's `gitleaks detect --config .gitleaks.toml` fails file-not-found. +@test "baseline: .gitleaks.toml is fetched verbatim from standards/gitleaks.toml (#575)" { + printf 'title = "gitleaks config"\n[allowlist]\npaths = [ %s ]\n' "'''_bmad/'''" \ + > "$STANDARDS_DIR/standards/gitleaks.toml" + run bash "$SEED" --emit-baseline .gitleaks.toml + [ "$status" -eq 0 ] + [[ "$output" == *'title = "gitleaks config"'* ]] + [[ "$output" == *"_bmad/"* ]] +} + +@test "baseline: .gitleaks.toml FAILS LOUD when standards/gitleaks.toml is absent" { + # No fixture written; empty gh stub blocks the network fallback so the general + # fetch: source must fail loud, not emit empty (it exists in the live repo). + _stub_gh_empty + run bash "$SEED" --emit-baseline .gitleaks.toml + [ "$status" -ne 0 ] + [[ "$output" == *"standards/gitleaks.toml"* ]] +} + # ── dependabot baseline is sourced from the chosen standards/ stack template ─── @test "baseline: dependabot.yml comes from the chosen standards/dependabot stack" { printf 'version: 2\nupdates:\n - package-ecosystem: npm\n' \ @@ -295,11 +316,13 @@ EOF fi done printf 'version: 2\n' > "$STANDARDS_DIR/standards/dependabot/frontend.yml" + printf 'title = "gitleaks config"\n' > "$STANDARDS_DIR/standards/gitleaks.toml" run env DEPENDABOT_STACK=frontend bash "$SEED" --repo petry-projects/repo-template [ "$status" -eq 0 ] [ -f "$CALLS" ] grep -q "contents/.github/workflows/auto-rebase.yml" "$CALLS" grep -q "contents/.github/CODEOWNERS" "$CALLS" + grep -q "contents/.gitleaks.toml" "$CALLS" grep -q "pr create" "$CALLS" } @@ -323,6 +346,7 @@ EOF fi done printf 'version: 2\n' > "$STANDARDS_DIR/standards/dependabot/frontend.yml" + printf 'title = "gitleaks config"\n' > "$STANDARDS_DIR/standards/gitleaks.toml" run env DEPENDABOT_STACK=frontend bash "$SEED" --repo petry-projects/repo-template [ "$status" -eq 0 ] [[ "$output" == *"PR #42 already open"* ]]