From 7eb926a078d0bf337578744a302930bfbe9c9952 Mon Sep 17 00:00:00 2001 From: JacobPEvans <20714140+JacobPEvans-personal@users.noreply.github.com> Date: Thu, 4 Jun 2026 01:25:35 -0400 Subject: [PATCH] feat(security): enable free CodeQL default-setup on every public org repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code scanning default-setup is FREE on public repos (no GHAS license consumed). Adds a script under scripts/ that idempotently turns it on for every public org repo with a supported language. Private repos are never targeted (would require paid GHAS Code Security per AGENTS.md "Cost policy"). Workaround for a provider gap: integrations/terraform-provider-github does not expose a resource for PUT /repos/{owner}/{repo}/code-scanning/default-setup. When upstream ships one, this script becomes an "import.sh"-style one-shot and the .tf takes over. Cost impact: $0. Script enumerates --visibility public --no-archived before the PUT loop, so a private repo name can never reach the call. Belt-and-suspenders: org-default GHAS is off, so an accidental PUT against a private repo would 403 anyway — no charge can land. --- README.md | 7 ++- scripts/enable-codeql-default-setup.sh | 71 ++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100755 scripts/enable-codeql-default-setup.sh diff --git a/README.md b/README.md index 5fbaffe..a4a05c1 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ defined **once** here and applied to **every** repo automatically. | `github_organization_ruleset.org_branch_protection` | Quality gate on every default branch: required signatures, linear history, branch name pattern, strict Conventional Commits regex, PR thread resolution. **No bypass** — applies to everyone including org admins. | | `github_organization_ruleset.org_review_gate` | Review gate on every default branch: 1 approving review + CODEOWNER review on PRs. **OrganizationAdmin bypass in `pull_request` mode** so admins can merge their own PRs; bots and other contributors must obtain the review. | | `github_organization_ruleset.markdown_lint` | Requires the markdownlint workflow in the org's `.github` repo to pass on every ref of every repo. Single source of truth: the workflow + `.markdownlint-cli2.yaml` both live in `.github`. `do_not_enforce_on_create` so brand-new repos don't fail before their default branch exists. | +| `scripts/enable-codeql-default-setup.sh` | Idempotently enables free CodeQL default-setup on every public org repo with a supported language. Run on first apply and again whenever a new public repo joins. **Not** a Terraform resource (provider gap); script-managed until `integrations/terraform-provider-github` ships a `github_repository_code_scanning_default_setup` or equivalent. | Imports needed on first apply (declared in `rulesets.tf` via `import` blocks, executed automatically by `tofu apply`): @@ -31,7 +32,11 @@ After successful apply, the `import` blocks can be removed in a follow-up PR (they're idempotent but only useful once). Next up (separate PRs): org Actions permissions, org-level settings, org -variables, per-repo labels and LICENSE files via `for_each`. +variables, per-repo labels and LICENSE files via `for_each`. Code scanning +default-setup is currently script-managed +(`scripts/enable-codeql-default-setup.sh`) pending a +`github_repository_*` resource in `integrations/terraform-provider-github`; +swap to the resource when upstream ships one. ## Layout diff --git a/scripts/enable-codeql-default-setup.sh b/scripts/enable-codeql-default-setup.sh new file mode 100755 index 0000000..12cacc7 --- /dev/null +++ b/scripts/enable-codeql-default-setup.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# +# Idempotently enable free CodeQL default-setup on every public org repo +# with a supported language. +# +# Code scanning default setup is FREE on public repos (no GHAS license +# consumed). Private repos are never targeted — they require GHAS Code +# Security ($30/committer/month, AGENTS.md "Cost policy") which the +# org policy keeps off as an org default. Per-repo opt-in on cost-approved +# private repos can be added later by extending the filter; not in scope +# here. +# +# Workaround for a provider gap: integrations/terraform-provider-github +# does not expose a resource for +# PUT /repos/{owner}/{repo}/code-scanning/default-setup. When upstream +# ships one this script becomes a one-shot import-style helper and the +# .tf takes over; until then, run after every new public repo joins. +# +# Idempotent: already-configured repos no-op; repos with no supported +# language are skipped. Re-running prints "skip" for every repo and +# touches nothing. +# +# Requires: gh (authenticated with admin:org or administration:write on +# the org — the ORG_ADMIN tier per AGENTS.md "Applying"), jq. + +set -euo pipefail + +OWNER="dryvist" +enabled=0 +already_on=0 +skipped_no_lang=0 +errors=0 + +# --visibility public is the safety belt: a private repo name can never +# reach the PUT call. Belt-and-suspenders: GHAS isn't enabled at the org +# default level, so even an accidental PUT against a private repo would +# 403 — no charge can land. +while IFS= read -r name; do + body=$(gh api "repos/$OWNER/$name/code-scanning/default-setup" 2>/dev/null || echo "") + if [[ -z "$body" ]]; then + echo " ERROR $name (GET failed)" + errors=$((errors + 1)) + continue + fi + + state=$(printf %s "$body" | jq -r '.state // ""') + langs=$(printf %s "$body" | jq -r '.languages // [] | join(",")') + + if [[ "$state" == "configured" ]]; then + echo " skip $name (already on)" + already_on=$((already_on + 1)) + continue + fi + + if [[ -z "$langs" ]]; then + echo " skip $name (no supported language)" + skipped_no_lang=$((skipped_no_lang + 1)) + continue + fi + + if gh api --method PUT "repos/$OWNER/$name/code-scanning/default-setup" -f state=configured > /dev/null 2>&1; then + echo " ON $name (langs: $langs)" + enabled=$((enabled + 1)) + else + echo " ERROR $name (PUT failed)" + errors=$((errors + 1)) + fi +done < <(gh repo list "$OWNER" --no-archived --visibility public --limit 100 --json name --jq '.[].name' | sort) + +echo +echo "enabled: $enabled · already-on: $already_on · skipped-no-lang: $skipped_no_lang · errors: $errors"