Skip to content

Latest commit

 

History

History
1052 lines (735 loc) · 55.9 KB

File metadata and controls

1052 lines (735 loc) · 55.9 KB

Repository Configuration Design Document

Scope: Every GitHub repository setting managed by this shared GitHub Terraform framework, with rationale grounded in industry standards, GitHub's own recommendations, and CI/CD best practices.

Audience: Current and future maintainers who need to understand why every default was chosen, not just what it is.

Provider: integrations/github v6.10.2

Last Updated: 2026-03-18


Table of Contents


1. Design Philosophy

This framework manages GitHub repositories as code — every setting is declarative, version-controlled, and auditable. The design priorities, in order:

  1. Security-first: Every default leans toward the most restrictive option that doesn't impede normal maintainer workflows. Public repositories are a living security audit, whether they belong to an individual or an organization.
  2. Clean git history: Squash-only merges, linear history, and signed commits produce a timeline that tells a story, not a tangle.
  3. Minimal surface area: Features that are rarely worth the maintenance burden by default (Wiki, Projects) are disabled, while Discussions is enabled by default because both owner models use it as the standard support entry point.
  4. Convention over configuration: Repos should only specify what makes them different. Everything else inherits from battle-tested defaults.

Governing References:


2. Repository Core Settings

2.1 name

Type string (required)
Source YML filename / map key
Governance

The repository name is the map key in each .yml file. Names follow kebab-case convention (lowercase-words-separated-by-hyphens), which is the GitHub community standard and produces clean, readable URLs.

Recommendation: Always kebab-case. Never underscores, camelCase, or spaces. GitHub normalizes display names but URLs are permanent.

2.2 description

Type string (optional, strongly recommended)
Default null
Governance GitHub Community Standards

The description appears in search results, profile pages, GitHub Explore, and social cards (Open Graph). For any public repository, a description should be treated as required. An empty description on a public repo looks unfinished.

Recommendation: Required for all public repos. Should be a single sentence (under 350 characters) that answers "what does this do and why would someone care?" Avoid marketing language; be specific and technical.

Quality Criteria:

  • Starts with a noun or action (not "This repo..." or "A tool that...")
  • Mentions the primary technology stack
  • Differentiates from similar repos in the account

2.3 visibility

Type string: "public", "private", or "internal"
Default "private"
Governance GitHub Docs: Repository Visibility

The default is private — repos must explicitly opt into public visibility. This prevents accidental exposure of work-in-progress or sensitive material.

Recommendation: Default to private. Portfolio/showcase repos are explicitly set to public in their YML. The internal visibility is only available for GitHub Enterprise and is not used here.

Rationale: The "private by default" pattern is a security fundamental (OWASP: Secure by Default). A public repo is a deliberate publication decision, not an accident.

2.4 topics

Type list(string)
Default []
Governance GitHub Docs: Classifying Your Repository with Topics

Topics power GitHub's search and discovery. GitHub allows up to 20 topics per repository. Topics must be lowercase, hyphenated, and under 50 characters.

Recommendation: Every public repo should have 5-15 well-chosen topics. Topics should include:

  1. Primary technology (e.g., terraform, ansible, packer)
  2. Domain (e.g., infrastructure-as-code, security, compliance)
  3. Platform (e.g., aws, proxmox, linux)
  4. Methodology (e.g., devsecops, ci-cd, gitops)

Quality Criteria:

  • Use established community topics (check GitHub's topic pages)
  • Topics must accurately describe the repo content — not aspirational
  • Remove generic filler topics that don't aid discovery
  • Each topic in the list must be unique across the repo's own topic list

2.5 homepage_url

Type string
Default null
Governance

A URL displayed next to the description on the repo page. Useful for linking to documentation sites, deployed applications, or GitHub Pages.

Recommendation: Set only when a meaningful external URL exists (e.g., a GitHub Pages site, published documentation, or live demo). Do not set to the repo's own GitHub URL — that's redundant. For repos with GitHub Pages enabled, this is automatically populated by GitHub.


3. Repository Features

3.1 has_issues

Type bool
Default true
Governance GitHub Community Standards

Issues are GitHub's built-in bug tracker and feature request system. GitHub's Community Standards checklist considers an issue tracker a sign of a healthy project.

Recommendation: true for all repos. Even for personal portfolio projects, enabled Issues signal that the repository is maintained and open to feedback. Employers reviewing your GitHub will see the Issues tab as a sign of professionalism.

3.2 has_discussions

Type bool
Default true
Governance GitHub Docs: Discussions

GitHub Discussions is a forum-style feature for Q&A and open-ended conversation. It's most valuable for projects with active communities.

Recommendation: true by default. This framework standardizes on Discussions as the primary support channel for questions and open-ended conversation, while Issues remain reserved for actionable work. Repositories that want a different posture should override has_discussions explicitly in their YAML definition.

3.3 has_projects

Type bool
Default false
Governance

GitHub Projects (V2) provides Kanban/roadmap boards attached to a repository.

Recommendation: false for all repos. Repository-level project boards are legacy (GitHub now recommends organization-level Projects V2). An empty Projects tab on a portfolio repo is visual noise. If you need project management, use organization-level Projects which don't create per-repo tabs.

3.4 has_wiki

Type bool
Default false
Governance Docs-as-Code methodology

GitHub Wiki is a separate git repository attached to the main repo. Wikis cannot be protected with branch rules, cannot require PR review, and don't appear in code search.

Recommendation: false for all repos. The modern industry standard is docs-as-code — documentation lives in the repository itself (typically docs/ or README.md), goes through the same PR review process as code, and benefits from the same branch protection. An empty Wiki tab is worse than no Wiki tab.

Rationale: The OpenSSF Scorecard Documentation check looks for documentation in the repository, not in a Wiki. Wikis are also a known attack vector for SEO spam on public repos.

3.5 is_template

Type bool
Default false
Governance GitHub Docs: Template Repositories

Marks the repository as a template, enabling the "Use this template" button.

Recommendation: false unless the repository is explicitly designed to be scaffolded/cloned as a starting point. Most portfolio repos are reference implementations, not templates.


4. Merge Strategy

The merge strategy is one of the most opinionated and impactful configuration areas. The choices here directly affect git history readability, git bisect usability, and CI/CD pipeline reliability.

4.1 allow_squash_merge

Type bool
Default true
Governance Git Project: Squash Merging, GitHub Engineering Blog

Squash merging combines all commits from a feature branch into a single commit on the target branch.

Recommendation: true — this is the only merge method enabled by default. Squash merging produces:

  • One commit per feature/fix on the default branch — clean, scannable history
  • Atomic reverts — any change can be reverted with a single git revert
  • Clean git bisect — every commit on main is a complete, working state
  • Simplified CHANGELOG generation — one commit = one entry

This aligns with the practices of GitHub, Google (Google Engineering Practices), and most modern CI/CD pipelines.

4.2 allow_merge_commit

Type bool
Default false
Governance Same as 4.1

Traditional merge commits create a merge node with two parents, preserving the full feature branch history.

Recommendation: false. Merge commits create a non-linear history that is harder to read, harder to bisect, and produces noisy git log output. The "merge commit per PR" pattern is a legacy of workflows where branch history was considered valuable — with squash merge, the PR itself preserves that history.

4.3 allow_rebase_merge

Type bool
Default false
Governance Same as 4.1

Rebase merging replays each commit from the feature branch onto the target branch individually.

Recommendation: false. While rebase produces linear history, it creates multiple commits per PR on the target branch, negating the "one feature = one commit" benefit of squash. It also rewrites commit SHAs, which breaks signature verification for individual commits.

4.4 squash_merge_commit_title

Type string: "PR_TITLE" or "COMMIT_OR_PR_TITLE"
Default "PR_TITLE"
Governance Conventional Commits

Controls the default title of the squashed commit.

Recommendation: "PR_TITLE". The PR title is a curated, human-written summary reviewed during the PR process. COMMIT_OR_PR_TITLE falls back to the first commit message, which is often a WIP or draft message like "initial attempt" or "fix thing". Using the PR title ensures the mainline history reads as a series of intentional, well-described changes.

4.5 squash_merge_commit_message

Type string: "PR_BODY", "COMMIT_MESSAGES", or "BLANK"
Default "PR_BODY"
Governance Same as 4.4

Controls the default body of the squashed commit.

Recommendation: "PR_BODY". The PR description typically contains context, motivation, and test plans — exactly the information that should be preserved in the commit message. COMMIT_MESSAGES would dump all intermediate commit messages (often "fix lint", "address review", "oops") into the body. BLANK discards context entirely.

4.6 merge_commit_title / merge_commit_message

Type string
Default null (not set)
Governance

These settings configure merge commit formatting. Since allow_merge_commit = false, these have no effect and are intentionally left unset.

Recommendation: null. Setting these when merge commits are disabled would be misleading dead configuration.

4.7 delete_branch_on_merge

Type bool
Default true
Governance GitHub Docs: Managing Branch Deletion

Automatically deletes the head branch after a PR is merged.

Recommendation: true. Stale branches are technical debt. Automatic cleanup:

  • Prevents the "200 branches" problem
  • Removes confusion about which branches are active
  • Keeps the branch namespace clean for git fetch --prune

Branches can always be restored from the PR page if needed.

4.8 allow_auto_merge

Type bool
Default true
Governance GitHub Docs: Auto-merge

Allows PR authors to enable auto-merge, which merges the PR automatically once all required status checks and reviews pass.

Recommendation: true. Auto-merge reduces manual toil without reducing safety — the PR still must pass all branch protection rules before merging. This is especially valuable for Dependabot PRs and trivial fixes where waiting for a human to click "merge" after all checks pass is pure waste.

4.9 allow_update_branch

Type bool
Default true
Governance GitHub Docs: Keeping Your Pull Request in Sync

Shows an "Update branch" button on PRs when they're behind the base branch.

Recommendation: true. This button provides a one-click way to bring a PR up to date with the base branch, which is essential when strict_required_status_checks_policy is enabled (requiring the branch to be tested against the latest base). Even without strict checks, it's a convenience with zero downside.

4.10 allow_forking

Type bool
Default false
Governance Open Source Licensing Principles

Controls whether the repository can be forked (for private/internal org repos - public repos are always forkable per GitHub's terms of service).

Recommendation: Default this to false conceptually, but do not enforce it from the shared framework resource. Public repos are forkable by definition, and personal-account repositories cannot reliably update this field through the same API path as organization repositories. If an owner needs strict forking control, handle it in an owner-specific layer.

4.11 web_commit_signoff_required

Type bool
Default true
Governance Developer Certificate of Origin (DCO), CNCF DCO Requirement

Requires contributors making commits via the GitHub web interface to sign off on their commits, adding a Signed-off-by trailer.

Recommendation: true. The DCO sign-off is a lightweight contributor agreement used by the Linux kernel, CNCF projects, and many enterprise open-source projects. It certifies the contributor has the right to submit the code. Combined with required_signatures in rulesets, this creates a complete chain of authorship verification.


5. Initialization & Licensing

5.1 auto_init

Type bool
Default true
Governance

Creates an initial commit with a README.md when the repository is created.

Recommendation: true. An initialized repository with a README is immediately cloneable and visible. GitHub's Community Standards requires a README. The lifecycle { ignore_changes = [auto_init] } block ensures this only takes effect on creation and doesn't cause drift.

5.2 license_template

Type string
Default "mit"
Governance OSI Approved Licenses, GitHub Docs: Licensing a Repository, OpenSSF Scorecard: License Check

The license applied to the repository on creation.

Recommendation: "mit" for all public repos. The MIT License is:

  • The most popular open-source license on GitHub (~44% of licensed repos)
  • Maximally permissive — allows commercial use, modification, and distribution
  • Simple and well-understood by legal teams
  • Required by OpenSSF Scorecard's License check

MIT is a strong default for broadly reusable public repositories because it signals openness without adding legal complexity. The lifecycle { ignore_changes = [license_template] } block prevents Terraform from fighting with manual license changes.

5.3 gitignore_template

Type string
Default null
Governance

A GitHub-maintained .gitignore template applied on repo creation.

Recommendation: null (not set by default). Each repository's .gitignore should be tailored to its specific technology stack and managed as code in the repo itself, not as a one-time initialization artifact. The available templates are too generic for most real projects.


6. Lifecycle Management

6.1 archived

Type bool
Default false
Governance GitHub Docs: Archiving Repositories

Archives the repository, making it read-only. Archived repos are clearly marked in the GitHub UI and cannot receive pushes, issues, or PRs.

Recommendation: false by default. Set to true explicitly in a repo's YML when the project is intentionally sunset. Archived repos retain their branch protection, rulesets are skipped (no writes possible), and branches are not managed.

Important: GitHub's API does not support un-archiving via the Terraform provider. Archiving is a one-way operation in this framework.

6.2 archive_on_destroy

Type bool
Default true
Governance NIST SP 800-53 SC-5: Defense in Depth

When true, terraform destroy will archive the repo instead of deleting it.

Recommendation: true. This works in concert with prevent_destroy = true in the resource lifecycle block to provide defense in depth:

  1. prevent_destroy — Stops the Terraform plan from proceeding. This is the primary guard.
  2. archive_on_destroy — Archives instead of deleting if destruction somehow proceeds (e.g., someone removes prevent_destroy from the lifecycle block and runs destroy).

These two controls protect against different failure modes. prevent_destroy is a Terraform-level guard that can be removed by editing a .tf file. archive_on_destroy is an API-level guard that ensures data preservation regardless of Terraform configuration changes. The cost of enabling both is zero; the cost of not having the fallback is potentially catastrophic data loss.

Rationale: Defense in depth is a core security principle (NIST SP 800-53 SC-5). Multiple independent controls are always stronger than a single control, even when the primary control is robust.


7. Security & Analysis

All secret scanning features are enabled universally regardless of repository visibility. As of January 2024, GitHub made secret scanning and push protection free for all repositories (public and private). There is no longer a GHAS requirement for these features on private repos. Per OWASP A02:2021 (Cryptographic Failures), all repositories handling any form of credentials or configuration must have secret detection enabled.

7.1 vulnerability_alerts

Type bool
Default true
Governance GitHub Docs: Dependabot Alerts, OpenSSF Scorecard: Vulnerabilities Check

Enables Dependabot vulnerability alerts for known CVEs in dependencies.

Recommendation: true for all repos. Dependabot alerts are free, automatic, and essential. Ignoring known vulnerabilities in dependencies is the #6 item on the OWASP Top 10 (A06:2021). Enabling them is one of the clearest low-friction security defaults a repository can have.

7.2 dependabot_security_updates

Type bool
Default true
Resource github_repository_dependabot_security_updates
Governance GitHub Docs: Dependabot Security Updates

Enables Dependabot to automatically create PRs to fix vulnerable dependencies.

Recommendation: true for all non-archived repos. Automated security patches paired with auto-merge and branch protection create a continuous patching pipeline with zero manual intervention for safe upgrades. This is a core tenet of SLSA Level 1.

7.3 advanced_security

Type bool or null
Default null (not set)
Governance GitHub Docs: GHAS

GitHub Advanced Security (GHAS) is a paid feature for private/internal repos. For public repos, all GHAS features are free and automatically available.

Recommendation: null for all repos. Public repos get these features for free without enabling this flag. Private repos on a free plan cannot enable it. Setting it to null avoids provider errors while allowing the individual security features to be configured independently.

7.4 code_security

Type bool
Default true (all visibilities)
Governance GitHub Docs: Code Security

Enables the code security overview and recommendations for the repository.

Recommendation: true universally. Code security is a free feature that surfaces dependency vulnerabilities, code scanning alerts, and secret scanning results in a unified view. There is no cost or downside to enabling it.

7.5 secret_scanning

Type bool
Default true (all visibilities)
Governance GitHub Docs: Secret Scanning, OWASP A02:2021: Cryptographic Failures

Scans repository contents for known secret patterns (API keys, tokens, certificates).

Recommendation: true for all repos. Secret scanning is free for both public and private repositories (as of January 2024) and is one of the most impactful security features GitHub offers — it has prevented millions of credential leaks. Leaked secrets in public repos are actively scraped by automated tools within seconds of exposure. Private repos are equally at risk from insider threats and compromised CI/CD pipelines.

7.6 secret_scanning_push_protection

Type bool
Default true (all visibilities)
Governance GitHub Docs: Push Protection

Blocks pushes that contain known secret patterns before they enter the repository.

Recommendation: true for all repos. This is the most critical secret scanning feature — it prevents secrets from ever being committed, rather than detecting them after the fact. Once a secret is in git history, it requires history rewriting to fully remove. Prevention is orders of magnitude better than detection. This applies equally to private repos — a secret committed to a private repo becomes a liability if the repo is ever made public, forked, or accessed by a compromised service account.

7.7 secret_scanning_ai_detection

Type bool
Default true (all visibilities)
Governance GitHub Docs: AI-Powered Secret Detection

Uses AI/ML models to detect secrets that don't match known provider patterns (e.g., custom API keys, internal tokens, hardcoded passwords).

Recommendation: true for all repos. AI detection catches the "long tail" of secrets that pattern-based scanning misses — custom API keys, internal tokens, hardcoded passwords, and other non-standard credential formats. This feature is now available for all repository visibilities.

7.8 secret_scanning_non_provider_patterns

Type bool
Default true (all visibilities)
Governance GitHub Docs: Non-Provider Patterns

Scans for secret patterns that aren't tied to specific service providers (e.g., generic private keys, HTTP basic auth credentials, connection strings).

Recommendation: true for all repos. Non-provider patterns catch infrastructure secrets like database connection strings and private keys that provider-specific patterns would miss. Combined with AI detection, this provides comprehensive secret coverage across all repository visibilities.


8. GitHub Pages

Type Nested object (optional)
Default null (Pages not enabled)
Governance GitHub Docs: GitHub Pages

GitHub Pages hosts static websites directly from a repository. Configuration includes:

  • build_type: "workflow" (GitHub Actions) or "legacy" (Jekyll). "workflow" is recommended as it provides full control over the build process.
  • source.branch: The branch to deploy from (typically "main").
  • source.path: The directory within the branch ("/" or "/docs").
  • cname: Custom domain (optional).

Recommendation: Only enable for repos that genuinely serve a website (documentation sites, project landing pages). Currently only proxmox-terraform-framework uses Pages. The workflow build type is preferred over legacy because it:

  • Supports any static site generator (not just Jekyll)
  • Provides CI/CD visibility in the Actions tab
  • Allows custom build steps and validation

9. Templates & Forking

Template Configuration

Type Nested object (optional)
Default null

Used when creating a repository from a template. Includes owner, repository, and include_all_branches.

Recommendation: Only set when explicitly creating a repo from a template. include_all_branches defaults to false because template repos typically only need the default branch's content — additional branches are development artifacts of the template, not the consumer.

Fork Configuration

Type fork (bool), source_owner (string), source_repo (string)
Default fork = false

Used when the repository is a fork of an upstream repository.

Recommendation: Only set when the repo is genuinely a fork. The framework correctly gates source_owner and source_repo behind fork = true to prevent orphaned configuration.


10. Branch Protection via Rulesets

10.1 Why Rulesets over Branch Protection Rules

GitHub offers three mechanisms for branch protection:

Feature branch_protection (GraphQL v4) branch_protection_v3 (REST) repository_ruleset (REST, newer)
Status Legacy Legacy Recommended
Multi-rule per ref No No Yes (aggregate, most restrictive wins)
Tag protection No No Yes
Push protection No No Yes
Evaluate mode No No Yes (dry-run)
Merge queue No No Yes
Code scanning gates No No Yes
File restrictions No No Yes

Decision: This framework uses github_repository_ruleset exclusively. GitHub's official documentation states: "Rulesets are the recommended way to manage branch and tag protections." Rulesets replace branch protection rules with a more powerful, composable model.

10.2 Ruleset: Default Branch Protection

Name: Default Branch Protection Target: branch Enforcement: active Scope: ~DEFAULT_BRANCH (the repo's default branch, typically main)

This ruleset protects the integrity of the default branch with fundamental safety rules.

10.2.1 creation

Value true
Governance SLSA: Source Integrity

Prevents anyone without bypass permissions from creating refs matching the condition (i.e., creating a new branch named main). This prevents branch confusion attacks where an attacker creates a branch with the same name as the default branch.

10.2.2 deletion

Value true
Governance GitHub Docs: Branch Protection

Prevents deletion of the default branch. Deleting the default branch is catastrophic and effectively destroys the repository's primary timeline.

10.2.3 non_fast_forward

Value true
Governance Git Project: Force Push, SLSA: Source Integrity

Prevents force pushes to the default branch. Force pushing rewrites history, destroys commit signatures, breaks git bisect, and can silently remove commits. This is the single most important branch protection rule.

Rationale: Force pushing to a shared branch is universally considered a destructive anti-pattern. The SLSA framework specifically calls out history integrity as a source requirement. Even for a solo developer, force-push protection prevents accidents.

10.2.4 required_linear_history

Value true
Governance Conventional Commits, Git Bisect Documentation

Requires a linear commit history on the default branch (no merge commits).

Recommendation: true. Combined with squash-only merging, this guarantees the default branch is a clean, linear sequence of atomic changes. Benefits:

  • git log --oneline reads as a changelog
  • git bisect works optimally (no merge nodes to confuse the binary search)
  • Every commit on main represents a complete, tested state

10.2.5 required_signatures

Value true
Governance Git Commit Signing, GitHub Docs: Commit Signature Verification, OpenSSF Scorecard: Signed-Releases

Requires all commits on the default branch to have verified GPG, SSH, or S/MIME signatures.

Recommendation: true. Commit signing provides:

  • Non-repudiation: Proves who actually authored a commit (git's author field is trivially forgeable)
  • Tamper detection: Signed commits cannot be modified without invalidating the signature
  • Supply chain security: Part of SLSA Level 2+ requirements

Signed commits display a green "Verified" badge and provide a visible, low-friction signal that the repository treats authorship integrity seriously.

10.2.6 update

Value false
Governance

When true, prevents all pushes to the matching branches (not just force pushes). This would make the branch completely read-only, which is only appropriate for archived branches or release branches with strict promotion workflows.

Recommendation: false. The default branch needs to receive squash-merged PRs. Blocking all updates would prevent any development workflow.

10.3 Ruleset: Pull Request Gate

Name: Pull Request Gate Target: branch Enforcement: active Scope: ~DEFAULT_BRANCH Bypass: Repository Admins (actor_id: 5, actor_type: RepositoryRole, bypass_mode: always)

This ruleset enforces the pull request workflow on the default branch.

10.3.1 allowed_merge_methods

Value ["squash"]
Governance See 4.1 allow_squash_merge

Restricts the merge methods available via the PR merge button. Set to ["squash"] only, reinforcing the squash-only merge strategy at the ruleset level (belt-and-suspenders with the repository-level allow_merge_commit = false).

10.3.2 dismiss_stale_reviews_on_push

Value true
Governance GitHub Docs: Dismissing Stale Reviews

Automatically dismisses existing approvals when new commits are pushed to the PR.

Recommendation: true. Without this, a PR could be approved, then have its code completely changed, and still merge with the stale approval. This is a well-known attack vector in supply chain security — the XZ Utils backdoor exploited exactly this pattern (approved PR, then later force-pushed malicious code).

10.3.3 require_code_owner_review

Value true
Governance GitHub Docs: CODEOWNERS

Requires at least one approving review from a designated code owner (defined in .github/CODEOWNERS).

Recommendation: true when a repository actually has maintainers to assign. A CODEOWNERS file formalizes ownership and creates an audit trail, but the concrete owners should be supplied per owner context rather than hard-coded into shared framework guidance.

10.3.4 require_last_push_approval

Value true
Governance GitHub Docs: Last Push Approval

Requires that the most recent push to the PR be approved by someone other than the pusher.

Recommendation: true. This prevents a scenario where a developer pushes a malicious commit as the last commit in a PR (after the review), then merges before anyone notices. Combined with dismiss_stale_reviews_on_push, this creates airtight review coverage.

Solo developer note: This rule is bypassed by the admin bypass actor, which is necessary for a single-maintainer account. The rule's presence still demonstrates to employers that you understand the principle.

10.3.5 required_approving_review_count

Value 1
Governance Google Engineering Practices: Code Review

The minimum number of approving reviews required before a PR can be merged.

Recommendation: 1. For a solo developer account, this is bypassed via the admin bypass actor. Setting it to 1 (rather than 0) demonstrates the configuration is ready for team use. Setting it higher than 1 would be theater — you'd always need to bypass it.

10.3.6 required_review_thread_resolution

Value true
Governance

Requires all review conversation threads to be resolved before merging.

Recommendation: true. Unresolved review threads indicate open questions or concerns. Merging with unresolved threads means merging code that hasn't been fully vetted. This is a quality gate — every piece of feedback must be explicitly acknowledged (resolved or addressed).

10.4 Ruleset: Bypass Actors

The Pull Request Gate ruleset includes a bypass actor for Repository Admins:

bypass_actors:
  - actor_id: 5
    actor_type: RepositoryRole
    bypass_mode: always

Actor ID 5 = Repository Admin role. This is essential for a solo developer account — without it, no one could merge PRs (since you can't approve your own PR). The bypass is scoped to the Pull Request Gate only; the Default Branch Protection ruleset has no bypass actors, meaning even admins cannot force-push or delete the default branch.

Available Actor IDs:

ID Role
1 Organization Admin
2 Maintain
4 Write
5 Admin

Bypass Modes:

  • always: Can bypass for both direct pushes and PR merges
  • pull_request: Can only bypass during PR merges
  • exempt: Exempt from the rule entirely

10.5 Ruleset: Conditions

Both rulesets target ~DEFAULT_BRANCH with no exclusions:

conditions:
  include:
    - "~DEFAULT_BRANCH"
  exclude: []

~DEFAULT_BRANCH is a GitHub magic ref that always resolves to whatever the repo's default branch is (typically main). This is preferred over hardcoding refs/heads/main because:

  • It automatically adapts if the default branch is renamed
  • It works across repos that may use different default branch names
  • ~ALL is available to protect all branches, but would be overly restrictive

10.6 Rules Not Enabled by Default (and Why)

Rule Why Not Default When to Enable
required_status_checks No CI/CD workflows defined yet globally Per-repo when CI pipelines exist
required_code_scanning Requires CodeQL or other scanning tool setup When code scanning Actions are configured
required_deployments No deployment environments defined When repos have deployment pipelines
merge_queue Overkill for solo developer When multiple contributors submit concurrent PRs
copilot_code_review Requires GitHub Copilot subscription When Copilot is available
branch_name_pattern Enterprise-only feature When GitHub Enterprise is in use
commit_message_pattern Enterprise-only feature When GitHub Enterprise is in use
commit_author_email_pattern Enterprise-only feature When GitHub Enterprise is in use
committer_email_pattern Enterprise-only feature When GitHub Enterprise is in use
tag_name_pattern No tag-based release workflow yet When semantic versioning releases are adopted
file_path_restriction Push-target only, requires Team plan When sensitive file paths need protection
file_extension_restriction Push-target only, requires Team plan When binary/large file extensions should be blocked
max_file_size Push-target only, requires Team plan When large file uploads should be prevented
max_file_path_length Push-target only, requires Team plan When path length limits are needed

11. Terraform Resource Lifecycle

The github_repository resource includes:

lifecycle {
  prevent_destroy = true
  ignore_changes  = [auto_init, license_template]
}
  • prevent_destroy = true: Prevents accidental repository deletion via terraform destroy. Repositories contain irreplaceable git history — deletion should never be a side effect of infrastructure operations.
  • ignore_changes = [auto_init, license_template]: These are creation-time-only settings. After the initial terraform apply, changes to the README or LICENSE file within the repo should not cause Terraform drift. Without this, Terraform would try to "re-initialize" already-populated repos.

12. Properties Intentionally Omitted

These github_repository properties exist in the provider but are intentionally not managed:

Property Reason for Omission
private Deprecated — superseded by visibility
default_branch Deprecated — use github_branch_default resource instead
has_downloads Deprecated — controls a legacy GitHub feature
ignore_vulnerability_alerts_during_read Deprecated — now handled automatically by the provider. Scheduled for removal from the framework in a future cleanup pass.

13. Per-Repository Deviation Policy

The framework's power comes from strong defaults with explicit opt-outs. Deviations from defaults should be:

  1. Minimal: Only specify what's different
  2. Justified: Add a YAML comment explaining why
  3. Auditable: The diff between a repo's YML and the defaults is the repo's "deviation surface"

Current justified deviations:

Repository Deviation Justification
github-sandbox visibility: private Experimental workspace, not portfolio-ready
screenshot-gpt-blinkstick visibility: private Personal project, not portfolio material
Personal visibility: private Personal notes/config
Resume visibility: private Contains personal information
.github Special repo Account-level default community health files and templates; must be public for GitHub to apply defaults across repositories
proxmox-terraform-framework pages: {...} Serves documentation site via GitHub Pages
NWarila Profile repo GitHub profile README — special repo with unique purpose

Any other deviations from defaults should be documented in this table when added.


14. Input Validation

Governance OWASP Input Validation, NIST SP 800-53 SI-10: Information Input Validation

All inputs at system boundaries must be validated. This framework ingests YAML files as its primary input, which are then processed by Terraform and sent to the GitHub API. Invalid inputs (e.g., visibility: "banana", enforcement: "off") could produce undefined behavior or silent misconfigurations.

The framework enforces validation at two levels:

14.1 Variable-Level Validation

Terraform validation blocks on input variables enforce constraints before the plan is computed:

  • github_owner: Must match GitHub username format (alphanumeric and hyphens, cannot start/end with hyphen).
  • github_token: Marked sensitive = true to prevent exposure in plan output or logs.

14.2 Plan-Time Checks

Terraform check blocks validate computed locals during terraform plan, catching YAML misconfigurations before they reach the GitHub API:

  • validate_repo_visibility: All repository visibility values must be one of public, private, or internal.
  • validate_ruleset_enforcement: All ruleset enforcement values must be one of active, evaluate, or disabled.
  • validate_public_repos_have_description: All public repositories must have a non-empty description (per Section 2.2).

Rationale: The GitHub API may silently accept some invalid values or reject them with unhelpful error messages. Validating at the Terraform layer provides clear, actionable error messages before any API calls are made. This follows the principle of fail fast — catching errors as early as possible in the pipeline.


15. Infrastructure Security

This section covers security controls for the Terraform framework itself — the infrastructure that manages the infrastructure.

15.1 Version Control Hygiene (.gitignore)

Governance OWASP: Sensitive Data Exposure, OpenSSF Scorecard: Binary-Artifacts

This repository uses a deny-all .gitignore strategy: everything is ignored by default (**), and only explicitly allowlisted files are tracked. This prevents accidental commits of sensitive files (.env, state files, credentials) or build artifacts.

Critical allowlist rules:

  • !/terraform/repos/*.yml and !/terraform/repos/*.yaml — Repository definition files (the framework's primary input).
  • !/terraform/*.tf — Terraform configuration files.
  • !/DESIGN.md — This governing design document.
  • Standard repo files (LICENSE, README.md, SECURITY.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md).

Explicitly excluded (not in allowlist):

  • .env — Contains the GitHub Personal Access Token. Must never be committed.
  • terraform.tfstate / terraform.tfstate.backup — State files contain sensitive data in plaintext (API tokens, resource metadata).
  • terraform.tfvars — Intentionally excluded to prevent accidental credential commits. Sensitive variables must be provided via environment variables or .env. Only terraform.auto.tfvars is allowlisted for non-sensitive configuration overrides.
  • .terraform/ — Provider binaries and plugin cache.

Rationale: The deny-all approach inverts the typical .gitignore model. Instead of trying to enumerate everything that shouldn't be committed (a losing game), it enumerates only what should be committed. This is the more secure default — new files are ignored unless explicitly approved.

15.2 Credential Management

Governance OWASP A07:2021: Identification and Authentication Failures, NIST SP 800-53 IA-5: Authenticator Management

The framework requires a GitHub Personal Access Token (PAT) for API authentication. Credential handling follows these principles:

  1. github_token is marked sensitive = true in the variable definition, preventing it from appearing in terraform plan output or state file diffs.
  2. Credentials are provided via environment variables (sourced from .env), not via terraform.tfvars or command-line arguments.
  3. .env is excluded from version control by the deny-all .gitignore strategy — it is never allowlisted.
  4. terraform.tfvars is excluded from version control to prevent the natural instinct of adding sensitive values to the conventional variable file.

Recommendation: Use environment variables (TF_VAR_github_token) or a secrets manager. Never store credentials in files that could be committed, even accidentally.

15.3 Terraform State Security

Governance NIST SP 800-53 SC-28: Protection of Information at Rest

Terraform state files contain the full configuration of all managed resources, including sensitive values like API tokens (post-apply). The current configuration uses local state (S3 backend is commented out in 00-providers.tf).

Current state: Local backend (file-based).

Security implications of local state:

  • No encryption at rest (state file is plaintext on disk)
  • No state locking (risk of concurrent modification in CI/CD)
  • No access control beyond filesystem permissions
  • State contains the GitHub PAT in plaintext after terraform apply

Recommended remediation: Enable the S3 backend with:

  • Server-side encryption (SSE-S3 or SSE-KMS)
  • State locking via S3-native locking (DynamoDB is no longer required with S3's native lock support)
  • Bucket policy restricting access to the CI/CD IAM role
  • Versioning enabled for state rollback capability

This is a known limitation documented for future remediation when the S3 backend configuration is finalized.

15.4 Terraform Output Security

Governance NIST SP 800-53 SI-11: Error Handling

The locals_debug output is marked sensitive = true to prevent exposure of repository configurations, security settings, ruleset enforcement levels, and bypass actors in Terraform plan/apply output or CI/CD logs.

Recommendation: Debug outputs should only exist during development. In production CI/CD pipelines, consider removing debug outputs entirely or gating them behind a variable flag. Terraform's sensitive marker prevents the values from appearing in CLI output but does not encrypt them in the state file.

15.5 Provider Version Pinning

Governance SLSA: Build Integrity, OpenSSF Scorecard: Pinned-Dependencies

All provider versions are exact-pinned (e.g., version = "6.10.2", not version = "~> 6.10"):

  • integrations/github: 6.10.2
  • hashicorp/aws: 6.28.0
  • Terraform itself: 1.14.3 (via required_version)

Rationale: Exact pinning prevents supply chain attacks where a compromised provider version is automatically pulled during terraform init. Range constraints (~>, >=) allow automatic upgrades that could introduce malicious code. Exact pins require explicit, reviewable version bumps — each upgrade is a deliberate, auditable decision.

This aligns with SLSA Level 3 requirements for hermetic, reproducible builds and the OpenSSF Scorecard's Pinned-Dependencies check.