Caffeine, Code, & Chaos

Systems engineer. Robotics geek. Maker of shiny things. Part race car, part glitter. Powered by caffeine and curiosity.

Locking Down GitHub Enterprise: A Security-First Approach That Actually Works

Updated 2026-06-24: Added a Personal Access Token Security section (fine-grained PATs, lifetime enforcement, scope discipline, org-wide PAT policies, and audit log monitoring with links to the relevant GitHub audit log event categories). Also fixed a broken Custom Properties code block, moved the Access Control Flow diagram into the IAM section where it belongs, switched the audit log streaming example to env-var-style secrets, and updated GitHub Advanced Security references to the current Code Security + Secret Protection branding.

Introduction

Let’s be honest: securing GitHub Enterprise often feels like trying to nail jell-O to a wall while blindfolded. You know you need to lock things down, but you also can’t turn your development environment into a bureaucratic nightmare where opening a PR requires three forms of ID and a blood sample.

I’ve had countless conversations with enterprise customers who want to “do security right” but don’t know where to start. They’ve got compliance requirements breathing down their necks, auditors asking uncomfortable questions, and developers who will revolt if you make their workflow any more complicated than it already is.

This post is my attempt to codify the security practices that actually work in the real world – the ones I walk through with customers who are serious about protecting their intellectual property without sacrificing developer productivity. Think of this as your GitHub Enterprise security implementation guide, presentation deck, and sanity-preservation manual all rolled into one.

Fair warning: This is a living document. Security best practices evolve, new features get released, and threat landscapes shift. I’m hoping to crowdsource additional practices from the community, so if you’ve got battle-tested security configurations that aren’t covered here, let’s make this better together.

The Foundation: Enterprise Managed Users (EMU)

If you’re running GitHub Enterprise and you’re not using EMU, we need to have a serious conversation. Enterprise Managed Users is like having a security guard who actually knows everyone’s name instead of just waving people through the door.

Why EMU Matters

Traditional GitHub setups let users bring their personal accounts into your enterprise, which is like letting employees use their home keys to access the office. Sure, it’s convenient, but it’s also a security nightmare waiting to happen.

EMU creates a clean separation:

  • Managed identities: All user accounts are controlled by your organization
  • Centralized provisioning: Users are created and managed through your identity provider
  • Clear boundaries: No mixing of personal and enterprise GitHub activities
  • Audit trail: Complete visibility into who has access to what

Identity and Access Management: The “Principle of Least Privilege” Actually Applied

Set Base Permissions to None

This is where we separate the security-conscious from the “everyone gets admin because it’s easier” crowd. Setting base permissions to none means users start with zero access and earn their privileges through explicit grants.

Default configuration should be:

  • Organization base permissions: None
  • Repository default permissions: None
  • External collaborator permissions: None

IDP Group-Backed Teams

Manual team management is like manually managing DNS records – technically possible, but why would you torture yourself? Connect your GitHub teams directly to your identity provider groups.

Benefits:

  • Automatic provisioning and deprovisioning
  • Consistent access patterns across systems
  • Reduced administrative overhead
  • Better audit trail

Implementation checklist:

  • Map GitHub teams to existing AD/LDAP groups
  • Set up automated synchronization
  • Define team naming conventions
  • Create process for new team requests
  • Establish regular access reviews

The Access Control Flow: How It All Connects

Here’s the core concept that many people struggle with – the simple flow from identity groups to repository access:

graph LR
    subgraph "Your Identity Provider"
        A[IDP Group:<br/>platform-engineering]
    end

    subgraph "GitHub"
        B[GitHub Team:<br/>Platform Engineering]
        C[Repository:<br/>platform-api]
        D[Repository:<br/>infrastructure]
    end

    A -->|Syncs to| B
    B -->|"Write" role| C
    B -->|"Admin" role| D

    style A fill:#e1f5fe
    style B fill:#e8f5e8
    style C fill:#fff3e0
    style D fill:#fff3e0

That’s it. Three simple steps:

  1. People are assigned to IDP groups (Active Directory, LDAP, etc.)
  2. IDP groups automatically map to GitHub teams (via SCIM sync)
  3. GitHub teams are given specific roles on specific repositories

When Sarah joins the platform engineering team:

  • HR adds her to the platform-engineering AD group
  • She automatically becomes a member of the GitHub Platform Engineering team
  • She immediately gets Write access to platform-api and Admin access to infrastructure
  • No manual GitHub administration required

When she leaves or changes teams, the same process works in reverse.

Custom Organization and Repository Roles

GitHub’s default roles are like off-the-rack suits – they’ll work, but custom tailoring makes all the difference. Create roles that match your actual organizational structure and security requirements.

Example custom org roles:

  • Security Reviewer: Can view security alerts and audit logs, but can’t modify settings
  • Compliance Officer: Read-only access to all repositories and org settings
  • Team Lead: Can manage teams and repositories within specific boundaries

Example custom repository roles:

  • Code Reviewer: Can review and merge PRs, but can’t modify repository settings
  • Release Manager: Can create releases and manage deployment keys
  • Security Champion: Can manage security settings and view security advisories

Personal Access Token Security: The Credentials Everyone Forgets About

Personal access tokens (PATs) are the security equivalent of leaving your house key under the doormat – convenient until somebody finds it. Long-lived, over-scoped, organization-wide PATs are one of the most common attack vectors I see in enterprise breaches, and they’re almost always preventable.

If you’re letting developers create classic PATs with repo scope and no expiration, you’re effectively handing out master keys to your entire codebase. Let’s fix that.

Prefer Fine-Grained Personal Access Tokens

GitHub’s fine-grained PATs are the modern replacement for classic tokens, and they’re not even close in terms of security posture. Classic PATs grant broad scopes across every repository the user can access, while fine-grained PATs let you scope down to specific repositories with granular permissions.

Why fine-grained PATs win:

  • Repository-specific access: Select exactly which repos the token can touch
  • Granular permissions: Read vs. write on specific resources (contents, issues, pull requests, actions, etc.)
  • Mandatory expiration: Can’t be created without an end date
  • Organization approval workflow: Admins can require approval before tokens become active
  • Better audit trail: Token usage is logged with specific resource access

Classic PATs should be:

  • Banned outright if possible (enforce via organization settings)
  • Treated as legacy credentials with an active migration plan
  • Never used for new integrations or automation

Enforce Maximum Token Lifetimes

A PAT with no expiration is a credential that will outlive the employee who created it, the project it was created for, and possibly the company itself. Set organization-wide maximum lifetimes and stick to them.

Recommended lifetime tiers:

  • Development/exploration: 7 days
  • Standard automation: 30-90 days
  • Long-running integrations: 1 year maximum (and reconsider whether a GitHub App would be better)
# Organization-level PAT policy
personal_access_tokens:
  fine_grained:
    enabled: true
    require_approval: true
    max_lifetime_days: 90
  classic:
    enabled: false  # Disable if your workflows allow
    max_lifetime_days: 30  # If you must allow them
  allowed_repositories: "selected"  # Not "all"

Limit Scope and Repository Access

The principle of least privilege applies double for credentials that can be exfiltrated in a single API call.

Scope discipline checklist:

  • Grant only the permissions actually required (read-only when possible)
  • Scope tokens to specific repositories, never “all repositories”
  • Avoid admin:org, delete_repo, and other destructive scopes unless absolutely necessary
  • Separate tokens by purpose – don’t reuse one token across multiple integrations
  • Document what each token does and who owns it

Common over-scoping mistakes:

  • Granting repo (full control) when contents:read would suffice
  • Using workflow scope on tokens that don’t need to modify Actions workflows
  • Org-wide access for tokens that only touch one or two repositories
  • Write access for read-only reporting or monitoring tools

Prefer GitHub Apps and OIDC Over PATs

Honestly, the best PAT is the one you didn’t create. Before issuing a new token, ask whether the use case can be solved with something better:

  • GitHub Apps: Better for service-to-service integrations, with short-lived installation tokens and granular permissions
  • OIDC for CI/CD: Workload identity federation eliminates long-lived secrets entirely for cloud deployments
  • GitHub Actions secrets with environment protection rules: For workflow-specific credentials

A quick word on deploy keys: they’re often suggested as a PAT alternative, but they’re really not much better. Deploy keys are shared, long-lived credentials with no individual accountability – if three services use the same deploy key and it leaks, you have no idea which one was compromised, and rotating it breaks all three at once. Treat them the same way you’d treat a classic PAT: avoid them for new integrations and migrate to GitHub Apps when you can.

PATs should be a last resort for human-driven, interactive use cases – not the default for every integration.

Enforce Organization-Wide PAT Policies

GitHub Enterprise gives you organization-level controls to enforce these practices. Use them.

Settings to lock down (Organization → Settings → Personal access tokens):

  • Restrict access via fine-grained personal access tokens: Require approval for tokens accessing your org
  • Allow access via fine-grained personal access tokens: Set maximum lifetime
  • Restrict access via personal access tokens (classic): Disable entirely, or at minimum require justification
  • Enforce SAML SSO: Require token authorization for SSO-protected orgs

Monitor and Rotate

Even with all the right policies, tokens leak. Monitor for it.

One important caveat up front: the personal_access_token and auto_approve_personal_access_token_requests audit log categories only cover fine-grained PATs – they’re tied to the organization-level approval and policy flow that classic PATs don’t participate in. Classic PATs are largely invisible in the audit log beyond org_credential_authorization events for SSO-protected orgs and indirect signals like secret scanning hits. If you want meaningful PAT telemetry, you need to be on fine-grained tokens. (Yet another reason to disable classic PATs.)

Monitoring essentials for fine-grained PATs:

For classic PATs (until you’ve migrated everyone off):

  • Watch for SSO authorization changes via org_credential_authorization.grant and org_credential_authorization.revoke
  • Use secret scanning with push protection to catch tokens accidentally committed to repositories
  • Periodically inventory active classic PATs at the user level – there is no org-wide dashboard, so this is largely a manual exercise

Rotation strategy:

  • Automate rotation for service-account tokens via your secret manager
  • Calendar reminders for human-owned tokens approaching expiration
  • Quarterly review of all active tokens in the organization
  • Immediate revocation when employees change roles or leave

PAT Security Implementation Checklist

  • Disable classic PATs at the organization level (or enforce short lifetimes)
  • Require approval for all fine-grained PATs accessing your org
  • Set maximum token lifetime to 90 days or less
  • Document approved use cases for PATs vs. GitHub Apps vs. OIDC
  • Migrate existing classic PATs to fine-grained tokens or GitHub Apps
  • Enable secret scanning with push protection across all repositories
  • Stream token-related audit events to your SIEM
  • Establish a quarterly token review and cleanup process

Repository Security: Building Walls That Actually Keep Bad Things Out

Repository Rulesets: Your First Line of Defense

Rulesets are like having a bouncer who actually checks IDs at the door. They enforce your policies automatically so you don’t have to rely on developers remembering to do the right thing. Learn more about managing repositories at scale in the GitHub Well-Architected framework.

Essential ruleset configurations:

# Example branch protection ruleset
name: "Main Branch Protection"
target: "branch"
enforcement: "active"
conditions:
  ref_name:
    include: ["refs/heads/main", "refs/heads/master"]
rules:
  - type: "required_status_checks"
    parameters:
      strict_required_status_checks_policy: true
      required_status_checks:
        - "ci/build"
        - "security/scan"
        - "compliance/check"
  - type: "required_pull_request_reviews"
    parameters:
      required_approving_review_count: 2
      dismiss_stale_reviews: true
      require_code_owner_reviews: true
  - type: "restrict_pushes"
    parameters:
      restrict_pushes: true

Additional ruleset best practices:

  • Require signed commits for sensitive repositories
  • Block force pushes to protected branches
  • Require up-to-date branches before merging
  • Mandate status checks for automated security scanning

Security Configurations: Defense in Depth

Security configurations let you set organization-wide defaults that actually stick. Think of them as your security policy enforcement mechanism. For comprehensive security controls, refer to the GitHub Well-Architected Application Security pillar.

Key configurations to implement:

  1. Dependency management:
    • Enable Dependabot alerts and security updates
    • Configure vulnerability scanning for all repositories
    • Set up dependency review for pull requests
  2. Code scanning:
    • Enable CodeQL analysis by default
    • Configure third-party security tools integration
    • Set up secret scanning across all repositories
  3. Supply chain security:
    • Require signed commits for critical repositories
    • Enable dependency graph for all repositories
    • Configure SBOM generation for releases
# Example security configuration
security_and_analysis:
  dependency_graph:
    status: "enabled"
  dependabot_alerts:
    status: "enabled"
  dependabot_security_updates:
    status: "enabled"
  secret_scanning:
    status: "enabled"
  secret_scanning_push_protection:
    status: "enabled"
  code_scanning_default_setup:
    status: "enabled"
    languages: ["javascript", "python", "java", "go"]

Automation Tools: Configuration as Code

Manually clicking through the GitHub UI to configure hundreds of repositories is a recipe for inconsistency and burnout. Treat your GitHub configuration like any other infrastructure – as code that’s version-controlled, reviewed, and automatically applied.

github/safe-settings

Safe-settings is an open source project that applies repository and organization settings from a central configuration repository. It’s like having an automated security guard that constantly ensures your settings match your policy.

Key benefits:

  • Declarative YAML configuration stored in a .github repository
  • Automatic remediation when settings drift from baseline
  • Supports rulesets, branch protection, collaborators, and security features
  • Audit trail through pull request history

Example safe-settings configuration:

# .github/repos/platform-api.yml
repository:
  name: platform-api
  description: Platform API service
  private: true
  has_issues: true
  has_wiki: false
  default_branch: main
  allow_squash_merge: true
  allow_merge_commit: false
  allow_rebase_merge: false
  delete_branch_on_merge: true
  security_and_analysis:
    secret_scanning:
      status: enabled
    secret_scanning_push_protection:
      status: enabled

branches:
  - name: main
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 2
        dismiss_stale_reviews: true
        require_code_owner_reviews: true
      required_status_checks:
        strict: true
        contexts:
          - "ci/build"
          - "security/scan"
      enforce_admins: true
      restrictions: null

Terraform with the GitHub Provider

For organizations already invested in Terraform, the GitHub Terraform provider lets you manage GitHub resources alongside your other infrastructure.

When to choose Terraform over safe-settings:

  • You’re already using Terraform for infrastructure management
  • You need to coordinate GitHub configuration with other cloud resources
  • You want to leverage Terraform’s state management and plan/apply workflow
  • You need more programmatic control (loops, conditionals, modules)

Example Terraform configuration:

# Define a secure repository with all security features enabled
resource "github_repository" "platform_api" {
  name        = "platform-api"
  description = "Platform API service"
  visibility  = "private"
  
  has_issues   = true
  has_wiki     = false
  has_projects = false
  
  allow_squash_merge = true
  allow_merge_commit = false
  allow_rebase_merge = false
  delete_branch_on_merge = true
  
  security_and_analysis {
    secret_scanning {
      status = "enabled"
    }
    secret_scanning_push_protection {
      status = "enabled"
    }
  }
}

resource "github_branch_protection" "main" {
  repository_id = github_repository.platform_api.node_id
  pattern       = "main"
  
  required_status_checks {
    strict   = true
    contexts = ["ci/build", "security/scan"]
  }
  
  required_pull_request_reviews {
    dismiss_stale_reviews           = true
    required_approving_review_count = 2
    require_code_owner_reviews      = true
  }
  
  enforce_admins = true
}

# Apply consistent settings across multiple repositories
locals {
  secure_repos = ["platform-api", "auth-service", "data-pipeline"]
}

resource "github_repository_ruleset" "security_baseline" {
  for_each = toset(local.secure_repos)
  
  name        = "security-baseline"
  repository  = each.value
  target      = "branch"
  enforcement = "active"
  
  conditions {
    ref_name {
      include = ["~DEFAULT_BRANCH"]
      exclude = []
    }
  }
  
  rules {
    required_signatures = true
    pull_request {
      required_approving_review_count = 2
      dismiss_stale_reviews_on_push   = true
      require_code_owner_review       = true
    }
  }
}

Automation implementation checklist:

  • Choose your automation approach (safe-settings, Terraform, or both)
  • Set up a dedicated configuration repository
  • Define baseline security templates for different repository classifications
  • Implement CI/CD pipeline for configuration changes
  • Configure drift detection and alerting
  • Establish review process for configuration changes

Advanced Security Configurations

Custom Properties: Metadata That Matters

Custom properties are like labels that actually do something useful. Use them to classify repositories, automate workflows, and enforce policies based on data sensitivity or compliance requirements.

Practical custom property examples:

  • Data Classification: public, internal, confidential, restricted
  • Compliance Scope: sox, hipaa, pci-dss, gdpr
  • Business Criticality: critical, high, medium, low
  • Technology Stack: nodejs, python, java, golang

A payload for setting these values on a repository via the custom properties REST API looks like:

{
  "properties": [
    { "property_name": "data_classification", "value": "confidential" },
    { "property_name": "compliance_scope", "value": ["sox", "pci-dss"] },
    { "property_name": "business_criticality", "value": "critical" },
    { "property_name": "technology_stack", "value": ["nodejs", "python"] }
  ]
}

Once values are set, you can target them from rulesets, security configurations, and safe-settings to apply policy based on what a repository is rather than maintaining hand-curated lists.

Inner Source Management: Controlled Sharing

Inner source is like open source, but with the training wheels still on. You want to encourage collaboration and code reuse without accidentally sharing sensitive information with the wrong people.

Inner source security checklist:

  • Establish clear criteria for internal repository promotion
  • Create review process for marking repositories as “internal”
  • Set up automated scanning for sensitive data before visibility changes
  • Define guidelines for acceptable inner source contributions
  • Implement monitoring for internal repository access patterns

Process workflow:

  1. Developer requests to make repository internal
  2. Automated security scan checks for secrets, sensitive data
  3. Security team reviews repository content and access patterns
  4. Business stakeholder approves based on business value
  5. Repository visibility updated with appropriate custom properties

Monitoring and Compliance

Audit Log Management

If you’re not monitoring your GitHub audit logs, you’re basically flying blind through a thunderstorm. Set up comprehensive logging and alerting for security-relevant events.

Critical events to monitor:

  • User provisioning and deprovisioning
  • Permission changes (org, team, repository)
  • Repository creation and deletion
  • Security setting modifications
  • Failed authentication attempts
  • Unusual access patterns

Implementation strategy (illustrative pseudo-config – actual destinations are configured in the enterprise admin UI under Settings → Audit log → Log streaming; credentials should come from a secrets manager, never be hardcoded):

{
  "audit_log_streaming": {
    "enabled": true,
    "destinations": [
      {
        "type": "splunk",
        "endpoint": "https://splunk.company.com/services/collector",
        "token": "${SPLUNK_HEC_TOKEN}"
      },
      {
        "type": "datadog",
        "endpoint": "https://http-intake.logs.datadoghq.com/api/v2/logs",
        "api_key": "${DATADOG_API_KEY}"
      }
    ],
    "events": [
      "repo.create",
      "repo.destroy",
      "org.update_member",
      "team.add_member",
      "team.remove_member"
    ]
  }
}

Security Dashboards and Reporting

Create dashboards that actually tell you useful information instead of pretty graphs that nobody looks at.

Key metrics to track:

  • Repository security posture scores
  • Dependency vulnerability trends
  • Code scanning alert resolution times
  • Compliance policy violations
  • User access pattern anomalies

Implementation Roadmap

Phase 1: Foundation (Weeks 1-4)

  • Plan and execute EMU migration
  • Configure base permissions to none
  • Set up IDP group synchronization
  • Create initial custom roles

Phase 2: Repository Security (Weeks 5-8)

  • Implement organization-wide security configurations
  • Deploy repository rulesets for critical repositories
  • Set up custom properties framework
  • Configure audit log streaming

Phase 3: Advanced Controls (Weeks 9-12)

  • Define and implement inner source processes
  • Create security monitoring dashboards
  • Establish compliance reporting workflows
  • Deploy advanced threat detection rules

Phase 4: Optimization (Ongoing)

  • Regular access reviews and cleanup
  • Security configuration tuning
  • Process refinement based on usage patterns
  • Continuous improvement based on new threats

Additional Security Considerations (Help Me Build This List!)

Here are some additional areas I typically discuss with customers, but I know there’s more. This is where I need your help – what am I missing?

Secret Management

  • GitHub Secret Protection (secret scanning + push protection)
  • Integration with enterprise secret management tools
  • Custom secret scanning patterns for proprietary systems
  • Automated secret remediation workflows

Network Security

  • IP allow lists for organization access
  • VPN integration for sensitive repositories
  • Network segmentation for GitHub Enterprise Server

Backup and Disaster Recovery

  • Repository backup strategies
  • Metadata and configuration backup
  • Disaster recovery testing procedures
  • Cross-region replication considerations

Third-Party Integrations

  • Security review process for GitHub Apps
  • OAuth application management
  • Webhook security configurations
  • API token lifecycle management

What else should be on this list? Drop me a line or open an issue if you’ve got battle-tested security practices that should be included here.

Summary and Action Items

Securing GitHub Enterprise isn’t about implementing every possible control – it’s about implementing the right controls in the right order to achieve your security goals without destroying developer productivity.

Your Security Implementation Checklist

Immediate Actions (Do This Week):

  • Audit your current GitHub Enterprise configuration
  • Plan your EMU migration timeline
  • Review and document your current permissions model
  • Identify repositories that need immediate protection

Short-term Goals (Next 30 Days):

  • Implement EMU or finalize migration plan
  • Set base permissions to none
  • Configure IDP group synchronization
  • Deploy repository rulesets for critical repositories
  • Set up basic security configurations

Long-term Strategy (Next 90 Days):

  • Complete custom role implementation
  • Establish inner source governance process
  • Deploy comprehensive monitoring and alerting
  • Create security reporting dashboards
  • Conduct first quarterly access review

The Reality Check

Here’s the truth: security is a journey, not a destination. Your GitHub Enterprise security posture should evolve with your organization, your threat landscape, and the platform itself. The configurations I’ve outlined here are a starting point, not a finish line.

The most important thing is to start with the fundamentals (EMU, least privilege, rulesets) and build from there. Don’t try to implement everything at once – you’ll overwhelm your team and probably break something important in the process.

Community Contribution

This post is my attempt to create a repeatable, practical guide for GitHub Enterprise security discussions. But I know I’m not covering everything, and security best practices evolve faster than my ability to update blog posts.

I need your help to make this better:

  • What security configurations am I missing?
  • What implementation gotchas should I warn people about?
  • What automation strategies have worked well in your environment?
  • What compliance requirements need additional coverage?

Let’s make this the definitive guide to GitHub Enterprise security that actually helps people build more secure development environments. Because at the end of the day, security that doesn’t get implemented isn’t security at all.

Now go forth and lock things down properly – your CISO (and your developers) will thank you.


Additional Resources:


Questions about GitHub Enterprise security? Find me on LinkedIn, Bluesky, or GitHub.

Comments