PHASE 14 OF 14

Governance, Compliance & Enterprise Patterns

GitHub Enterprise Server vs Enterprise Cloud — where the feature gaps actually hurt, org-wide policy enforcement, required workflows for cross-repo CI enforcement, what SOC 2 / HIPAA / FedRAMP mean for your GitHub usage, audit log streaming to SIEM, CODEOWNERS as an access-control layer, CLA management, inner source contribution patterns, and cost governance when Actions minutes and storage start mattering

Enterprise Governance Compliance Audit Log Inner Source Cost Control
14.1

GitHub Enterprise Server vs Enterprise Cloud: Feature Gaps & Migration Path

GitHub Enterprise comes in two deployment models. The choice between them is primarily driven by data residency requirements, network isolation needs, and operational capacity — not by feature preference, since GHEC generally has more features and gets them sooner.

GitHub Enterprise Server (GHES)

Self-hosted: you run GitHub on your own infrastructure (on-prem, private cloud, or air-gapped). You manage the VM, storage, backups, upgrades, and HA. Available as an appliance (OVA/AMI). Requires a GitHub Enterprise license.

GitHub Enterprise Cloud (GHEC)

GitHub-managed SaaS with enterprise controls: org-level policies, EMU, SAML/OIDC SSO, audit log streaming, enterprise billing. Runs on github.com infrastructure. No operational burden. Supports data residency (EU) via GHEC with data residency add-on.

Key differences

AreaGHESGHEC
Data residency / isolationFull — data never leaves your networkUS datacenters by default; EU residency add-on available
Air-gap supportYes — works with no internetNo — requires internet access
Operational burdenHigh — you manage upgrades, HA, backupsNone — GitHub manages infrastructure
Feature velocityLagging ~6–12 months behind GHECGets new features first
Actions runnersSelf-hosted only (no GitHub-hosted runners)GitHub-hosted + self-hosted
Copilot EnterpriseLimited (some features require GHEC)Full feature set
GitHub PagesLimited (no CDN)Full CDN-backed Pages
GitHub Advanced Security (GHAS)Available (add-on license)Available (add-on license)
Enterprise Managed Users (EMU)Not availableAvailable

When to choose GHES

  • Regulatory requirements mandate that source code never leaves your network (some defense, finance, healthcare orgs)
  • Truly air-gapped environments (classified networks, industrial control systems)
  • You already have mature on-prem infrastructure and ops teams

Migration path: GHES → GHEC

GitHub provides the GitHub Enterprise Importer (GEI) tool for migrating repos, issues, PRs, wikis, and settings from GHES to GHEC.

# Install GitHub Enterprise Importer CLI extension $ gh extension install github/gh-gei # Migrate a single repo $ gh gei migrate-repo \ --github-source-org acme-ghes \ --source-repo platform \ --github-target-org acme \ --target-repo platform # Migrate entire org (generates a migration script) $ gh gei generate-migration-script \ --github-source-org acme-ghes \ --github-target-org acme \ --output migrate.sh
WHAT GEI MIGRATES

GEI migrates: git history, pull requests and PR comments, issues and issue comments, releases and release assets, wikis, GitHub Actions workflows and secrets (secrets require re-entry). It does not migrate: branch protection rules (must be recreated), team memberships (must be recreated), webhooks, and deploy keys.

14.2

Org-Wide Policy Enforcement: Actions Policy, OAuth Restrictions & IP Allow Lists

Organization admins can lock down how developers interact with GitHub at a policy level — independent of individual repo settings. These policies are the first line of defense for a compliant enterprise GitHub setup.

GitHub Actions policies

Actions enablement Disable Actions entirely, allow only GitHub-authored actions, allow only actions from orgs you choose, or allow all. Set at the org level under Settings → Actions → General. Repositories inherit this policy and can further restrict (but not expand) it.
Third-party action allowlist When set to "selected actions only", you configure specific action owner/name patterns that are permitted. A good starting allowlist: actions/*, github/*, and specific vetted community actions. All others are blocked at dispatch time.
Runner groups Org-level runner groups control which repos can use which self-hosted runners. Prevents a compromised repo workflow from accessing a runner that has privileged network access.
Workflow permissions default Set GITHUB_TOKEN default permissions to read-only org-wide, requiring workflows to explicitly request write permissions. Reduces blast radius if a workflow is compromised.

OAuth App restrictions

By default any OAuth App a member authorizes gets access to org resources. Enable OAuth App access restrictions to require org admin approval before any OAuth App can access org data.

# List OAuth Apps with org access (via API) $ gh api orgs/acme/oauth_authorizations --paginate \ --jq '.[] | {app: .app.name, scopes: .scopes}' # Review pending OAuth app requests # Org Settings → Third-party access → OAuth App policy

IP allow lists

GitHub Enterprise Cloud supports IP allow lists that restrict who can access your org's resources. Only requests from listed CIDR ranges are accepted — everyone else gets a 403, even with valid credentials.

# Add a CIDR range to the org IP allow list via API $ gh api orgs/acme/ip_allow_list_entries \ --method POST \ --field name="office-london" \ --field allow_list_value="203.0.113.0/24" \ --field is_active=true # List current allow list entries $ gh api graphql -F org=acme -f query=' query($org:String!) { organization(login:$org) { ipAllowListEntries(first:100) { nodes { name allowListValue isActive } } } } '
IP ALLOW LIST + ACTIONS RUNNERS

When you enable an IP allow list, GitHub-hosted Actions runners are blocked from accessing your org by default. You must either add GitHub's runner IP ranges (they publish a JSON list at api.github.com/meta) or switch to self-hosted runners. Self-hosted runners inside your network are the cleaner solution at enterprise scale.

14.3

Required Workflows: Enforcing CI Across All Repos in an Org

Required workflows (GitHub Enterprise Cloud, 2023+) let you define a workflow in one "policy repo" and enforce it as a required status check on every repo in an org — without touching each repo's individual settings. It's the governance equivalent of a CODEOWNERS rule applied at the CI layer.

Setup

  1. Create a policy source repo in your org (e.g. acme/.github or acme/org-workflows)
  2. Add a workflow file to that repo: .github/workflows/org-security-scan.yml
  3. Go to Org Settings → Actions → Required workflows → Add workflow
  4. Select the source repo and workflow file
  5. Choose which repos it applies to: all repos, or a specific list
# .github/workflows/org-security-scan.yml (in the policy source repo) name: Org Security Baseline on: pull_request: branches: [main, master, develop] jobs: secret-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Scan for secrets uses: trufflesecurity/trufflehog@main with: path: ./ base: ${{ github.event.repository.default_branch }} head: HEAD license-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/dependency-review-action@v4 with: deny-licenses: GPL-3.0, AGPL-3.0 fail-on-severity: high

How required workflows behave in target repos

  • The workflow runs as if it were defined in the target repo — it has access to the target repo's code and secrets
  • It appears as a required status check on PRs, just like any other status check
  • Developers in target repos cannot disable or bypass it (only org admins can)
  • The workflow uses GITHUB_TOKEN scoped to the target repo, not the policy source repo
GOVERNANCE USE CASES

Required workflows are ideal for: org-wide secret scanning on every PR, license compliance checks, required SAST scan before merge, and mandatory deployment approval steps. They eliminate the "someone forgot to add the security scan to this new repo" problem at scale.

14.4

Repository Creation Policy: Naming, Visibility Defaults & Auto-Init

Left unmanaged, an org quickly accumulates hundreds of repos with inconsistent naming, surprise public visibility, and no standard files. A repo creation policy prevents this before it starts.

Policy controls available

SettingLocationRecommendation
Who can create reposOrg Settings → Member privilegesMembers (not just admins), but require naming convention via automation
Default repo visibilityOrg Settings → Member privilegesPrivate — members must explicitly choose public
Fork policyOrg Settings → Member privilegesRestrict forking to internal/private repos only for sensitive codebases
Repo topics requirementEnforced via webhook/botRequire at least one topic (team name or service type) for discoverability

Automating repo bootstrap with a creation webhook

Combine a repository.created webhook with a GitHub App to automatically configure every new repo: add branch protection, apply the org CODEOWNERS template, create required labels, and add the repo to the relevant team.

// repo-bootstrap bot: handles repository.created webhook async function handleRepoCreated(payload) { const { owner, name } = payload.repository; // 1. Add standard branch protection to main await octokit.rest.repos.updateBranchProtection({ owner: owner.login, repo: name, branch: "main", required_status_checks: { strict: true, contexts: ["ci/build"] }, required_pull_request_reviews: { required_approving_review_count: 1 }, enforce_admins: false, }); // 2. Create standard labels const labels = [ { name: "priority:high", color: "d93f0b" }, { name: "priority:low", color: "0e8a16" }, { name: "do-not-close", color: "e4e669" }, ]; for (const label of labels) { await octokit.rest.issues.createLabel({ owner: owner.login, repo: name, ...label }); } // 3. Create CODEOWNERS file from org template const template = await getOrgCodeownersTemplate(owner.login); await octokit.rest.repos.createOrUpdateFileContents({ owner: owner.login, repo: name, path: ".github/CODEOWNERS", message: "chore: add org CODEOWNERS template", content: Buffer.from(template).toString("base64"), }); }
14.5

Compliance Frameworks: SOC 2, HIPAA & FedRAMP — What GitHub Certifies, What You Own

GitHub (github.com / GHEC) holds several compliance certifications. Understanding which certifications apply, what they cover, and where your responsibility begins is critical for any org operating under a regulated framework.

SOC 2 Type II

GitHub certified

GitHub Enterprise Cloud is SOC 2 Type II certified (Trust Services Criteria: Security, Availability, Confidentiality). This means GitHub's infrastructure controls have been audited by an independent auditor annually. You can request GitHub's SOC 2 report under NDA for your own audit evidence. Your responsibilities: access control (who has GitHub accounts), what you store in repos (no unencrypted PII/PHI), and your own SDLC processes.

HIPAA

Shared responsibility

GitHub will sign a Business Associate Agreement (BAA) for GHEC customers, which enables you to store PHI-adjacent code (code that processes health data) in GitHub repos. The BAA covers GitHub's infrastructure. It does not cover: storing actual PHI in issues/comments/wikis, GitHub Actions logs that contain PHI, or Copilot processing PHI-containing code. In practice: code is generally not PHI, but test fixtures, logs, and issues sometimes are — keep those clean.

FedRAMP

GitHub Government Cloud (FedRAMP Moderate)

GitHub Government Cloud (a separate GHEC deployment at github.us) holds FedRAMP Moderate authorization. It's only available to US federal agencies and their contractors. If your org operates on standard github.com, FedRAMP does not apply. If you need FedRAMP High, GHES in a government cloud (AWS GovCloud, Azure Government) is the typical approach.

ISO 27001 / 27018

GitHub certified

GitHub maintains ISO 27001 (information security management) and ISO 27018 (protection of PII in public cloud) certifications. These are relevant for EU/global enterprise procurement and vendor risk assessments.

The shared responsibility model in practice

Control areaGitHub's responsibilityYour responsibility
Physical securityData center, hardwareYour laptop, your office
Network securityGitHub.com network perimeterYour corporate network, VPN
Identity & accessGitHub account security (2FA enforcement option)Who gets an account, SSO config, PAT hygiene
Data classificationEncrypts data at rest and in transitWhat data you put in repos, issues, Copilot context
Vulnerability managementGitHub platform vulnerabilitiesDependencies in your code (Dependabot alerts)
Logging & monitoringProvides audit log, retains 180 daysStream and retain logs per your policy (1–7 years)
14.6

Audit Log Compliance: Exporting, Retention & Streaming to SIEM

GitHub's org audit log records every security-relevant action: who logged in, who pushed to main, who changed a branch protection rule, which OAuth apps were authorized, and more. For most compliance frameworks, you're required to retain these logs for 1–7 years and to monitor them for anomalies — GitHub's built-in 180-day retention is not enough.

What's in the audit log

  • Authentication events: logins, 2FA changes, SSO sessions, PAT creation/deletion
  • Repo events: creation, deletion, visibility change, archive, transfer, fork
  • Branch protection changes: who changed what rule, when
  • Member events: org join/leave, team add/remove, role changes
  • Actions events: workflow runs (success/failure), self-hosted runner additions
  • Secret scanning events: secret detected, push protection bypass
  • Billing events: seat additions, plan changes

Querying the audit log via API

# Get recent audit log events for the org $ gh api "orgs/acme/audit-log?per_page=100" \ --jq '.[] | {action: .action, actor: .actor, repo: .repo, created_at: .created_at}' # Filter to branch protection changes only $ gh api "orgs/acme/audit-log?phrase=action:protected_branch&per_page=100" \ --paginate --jq '.[] | {action, actor, repo, created_at}' # Export last 7 days to JSONL file for archival $ gh api "orgs/acme/audit-log?per_page=100&after=$(date -d '7 days ago' +%s)000" \ --paginate > audit-$(date +%Y%m%d).jsonl

Streaming the audit log to a SIEM

Audit log streaming (GHEC Enterprise) pushes every audit event in real time to your chosen destination. This is the enterprise-grade solution — no polling, no retention gap.

Streaming destinationSetup path
Amazon S3Org Settings → Audit log → Log streaming → Amazon S3. Requires an S3 bucket + IAM role. Events delivered as JSONL, gzip compressed.
Azure Blob StorageSame settings path. Requires Storage Account + SAS token or managed identity.
Google Cloud StorageSame settings path. Requires GCS bucket + service account JSON key.
Splunk (via HEC)Same settings path. Requires Splunk HEC token + endpoint URL. Events indexed directly into Splunk.
DatadogSame settings path. Requires a Datadog API key. Events appear as log events in Datadog Logs.
# Example audit log event payload (branch protection change) { "action": "protected_branch.update_admin_enforced", "actor": "octocat", "actor_location": { "country_code": "US" }, "repo": "acme/platform", "created_at": 1749024000000, "_document_id": "abc123", "business": "acme-enterprise" }
DETECTION RULES TO WRITE

Once logs are in your SIEM, write detection rules for: protected_branch.* changes outside business hours, org.remove_member followed by large repo access, secret_scanning.push_protection_bypass events, and repo.visibility_change from private to public. These are the signals that matter most for insider threat and misconfig detection.

14.7

CODEOWNERS + Required Reviews as an Access Control Layer

CODEOWNERS combined with branch protection required reviews creates a code-level access control layer: certain paths in the codebase cannot be merged to main without an approving review from a designated owner. This is a lightweight alternative to repo-level permissions for organizations that keep everything in a monorepo.

CODEOWNERS patterns

# .github/CODEOWNERS # Default owner for everything not matched below * @acme/platform-leads # Security-sensitive paths require the security team /src/auth/** @acme/security-team /src/crypto/** @acme/security-team /.github/workflows/** @acme/devops-team # Payment processing requires both payments team AND a lead /src/payments/** @acme/payments-team @alice # Infrastructure-as-code: only SRE team can approve /terraform/** @acme/sre-team /k8s/** @acme/sre-team # Docs: any member of the writing team can approve /docs/** @acme/tech-writers # Multiple file extensions across the whole tree *.tf @acme/sre-team Dockerfile @acme/devops-team

Branch protection wiring

CODEOWNERS only takes effect when branch protection Required pull request reviews has "Require review from Code Owners" checked. Without this, CODEOWNERS only requests reviews — it doesn't block merge.

# Enforcing via API (repository ruleset — newer, preferred) POST /repos/acme/platform/rulesets { "name": "main-protection", "target": "branch", "enforcement": "active", "conditions": { "ref_name": { "include": ["refs/heads/main"] } }, "rules": [ { "type": "required_reviewers", "parameters": { "required_reviewers": [{ "type": "CodeownersReview" }] } } ] }

Bypass actors

Rulesets support bypass actors — roles or teams that can merge without the review requirement. Limit bypasses to: the repo admin team, an automated release bot, and on-call incident responders. Never give bypass to developers' personal accounts.

CODEOWNERS GOTCHA

CODEOWNERS matching is last-match-wins: the last matching line in the file determines the owner. A * at the top as a catch-all is overridden by specific paths below — this is correct behavior but surprises teams that put the default at the bottom instead of the top.

14.8

Managing Contributor Agreements with cla-assistant

A Contributor License Agreement (CLA) is a legal document that grants your org the rights to use and distribute a contributor's code. Required by most companies for external contributions to public or open-source repos. The cla-assistant bot automates the signing workflow on GitHub.

How cla-assistant works

  1. You deploy cla-assistant as a GitHub App or use the hosted version at cla-assistant.io
  2. Link your CLA document (a Gist or any public URL containing the CLA text)
  3. When an external contributor opens a PR, the bot comments with a link to sign
  4. The contributor signs via GitHub OAuth — their GitHub username is recorded in a Gist
  5. The bot adds a status check (cla/signed) to the PR, which you require before merge
  6. The bot re-checks on every new commit to the PR; once signed, subsequent PRs by the same contributor auto-pass
# cla-assistant status check output on a PR ✓ cla/signed — All committers have signed the CLA ✗ cla/signed — 1 of 2 committers has not signed the CLA @newcontributor — please sign at https://cla-assistant.io/acme/platform

Self-hosted vs hosted

OptionProsCons
cla-assistant.io (hosted)Zero ops, free for open source, immediate setupSignature data stored on their servers; not suitable for highly regulated orgs
Self-hosted cla-assistantFull control of data, store signatures in your own databaseRequires deployment and maintenance of the bot service
Custom GitHub AppFully bespoke logic, integrate with your legal systemSignificant development effort
WHEN YOU NEED A CLA

You need a CLA for: public repos accepting external contributions to a commercial product, open-source projects that may need to relicense, and any repo where your legal team requires copyright assignment. You don't need a CLA for internal employee contributions (employment agreement covers it) or purely personal open-source projects.

14.9

Inner Source Patterns: Forking Within the Org & Shared Service Contribution Workflows

Inner source applies open-source contribution patterns (fork, PR, review, merge) to internal codebases across teams. It solves a specific organizational problem: how do you let teams contribute improvements to a shared service without giving everyone write access to that service's repo?

The inner source contribution model

1. Trusted Committer role

Each shared repo has designated trusted committers — the team that owns the codebase. They review and merge contributions, maintain quality, and communicate the roadmap. Everyone else contributes via PRs from forks or branches.

2. Contributing guide

Every inner source repo must have a CONTRIBUTING.md that explains: how to set up the dev environment, what the PR review SLA is, what constitutes a mergeable PR, and who the trusted committers are. Without this, contributors give up after one failed attempt.

3. Internal forks

Enable forking within the org so teams can work on changes without needing write access to the upstream. Forks within the same org remain private. PRs from forks show the originating team clearly to reviewers.

4. Discoverable repos

Use GitHub topics to make inner source repos discoverable: inner-source, platform-team, shared-service. Maintain an inner source portal (a hub repo or internal wiki) that lists available shared services and their contribution guides.

GitHub settings that enable inner source

SettingWhereValue for inner source
Repository visibilityRepo settingsInternal (visible to all org members, not public)
Allow forkingRepo settings → GeneralEnabled
Base permissions for org membersOrg settings → Member privilegesRead — all org members can view internal repos
Team accessRepo settings → Collaborators & teamsOwner team: Write/Maintain; all others: Read (contribute via fork/PR)
INNER SOURCE PITFALL

The most common inner source failure is PRs that sit unreviewed for weeks. Set a published review SLA (24–48 hours for initial response) and track it. A contributing team that submits a PR and hears nothing for 2 weeks will never contribute again. The trusted committer's review response time is the single biggest predictor of inner source health.

14.10

GitHub at Scale: Actions Usage Limits, Concurrency Planning & Cost Governance

GitHub Actions is consumption-billed. For large engineering orgs, Actions costs can grow faster than headcount as CI usage scales. Understanding the billing model, identifying waste, and setting guardrails is how platform teams keep costs predictable.

Actions billing model

Runner typeRate (GitHub-hosted)Notes
Linux (ubuntu-*)$0.008 / minute2-core default; billed in 1-minute increments
Windows$0.016 / minute (2×)Higher due to license costs
macOS$0.08 / minute (10×)Significant — avoid for tasks that don't require macOS
Larger runners (4–64 core)Proportional to core countGitHub Enterprise only; price per minute × multiplier
Self-hosted runnersFree (you pay infra cost)Break-even vs GitHub-hosted at ~500 minutes/day per runner

Common cost drivers and fixes

# 1. Workflows running on every push, including draft PRs # Fix: filter to meaningful triggers only on: pull_request: types: [opened, synchronize, reopened, ready_for_review] push: branches: [main] # 2. Expensive macOS job running for every language test # Fix: run macOS only on release branches macos-test: runs-on: macos-latest if: startsWith(github.ref, 'refs/heads/release/') # 3. No cache — reinstalling 200MB of dependencies every run # Fix: cache dependencies aggressively - uses: actions/setup-node@v4 with: node-version: "22" cache: "npm" # saves ~2 min per run # 4. Redundant parallel matrix jobs that could be serial # Fix: only fan out the dimension that actually differs strategy: matrix: node: [20, 22] # needed: test both Node versions # os: [ubuntu, windows] # removed: our app doesn't run on Windows

Concurrency limits

ScopeDefault limitConfigurable?
Concurrent jobs per org500 (Free/Team), higher for EnterpriseIncrease via GitHub Support
Concurrent jobs per repo20 (GitHub-hosted)No
Job queue time before timeout24 hoursNo
Job execution timeout6 hours (GitHub-hosted)Via timeout-minutes: in workflow
API requests per repo per workflow1,000/hr (GITHUB_TOKEN)Use a PAT for higher limits

Cost governance in practice

# Query Actions usage via API (billing) $ gh api orgs/acme/settings/billing/actions \ --jq '{ included_minutes: .included_minutes, total_minutes_used: .total_minutes_used, total_paid_minutes_used: .total_paid_minutes_used, estimated_paid_minutes_cost: .estimated_paid_minutes_cost }' # Per-repo breakdown (requires enterprise billing API) $ gh api /enterprises/acme/settings/billing/actions \ --jq '.repositories | sort_by(-.total_minutes_used) | .[0:10]'
# Governance playbook for Actions cost control 1. Set spending limits per org: Org Settings → Billing → Spending limits → Actions Start with 120% of last month's cost. Alert at 80%. 2. Monthly cost review: Pull per-repo breakdown. Repos consuming >10% of total budget need a workflow audit. 3. Default job timeout: Add timeout-minutes: 30 to every job (default is 360 min). A hung test suite without a timeout burns money silently. 4. Self-hosted runner break-even: If a repo runs >500 minutes/day, a self-hosted runner (t3.large ~$0.08/hr = $1.92/day) pays for itself in 2 days. 5. Cache hit rate target: >80%. Below that, investigate cache key strategy. A missed cache on a Node.js project wastes 2-3 minutes per run.
STORAGE COSTS

GitHub also bills for artifact and package storage beyond the free tier. Set retention-days: on artifact uploads (default 90 days, cost drops to zero at day 1 for ephemeral CI artifacts). Set up automated package version deletion for old package versions using the delete-package-versions action on a weekly schedule.

Series Complete — View the Appendix

All 14 phases done. The appendix has cheat sheets for everything covered: GitHub search syntax, Actions expressions, gh CLI reference, branch protection comparison, REST API quick-reference, security decision tree, and SemVer.

Appendix: Reference Cards → Back to Hub