Cyber Intelligence
Cloud Security16 min read

GitHub Advanced Security: Complete Enterprise Setup and Optimization Guide

Most GHAS deployments fail within 90 days due to alert backlog, not lack of features. The rollout sequence matters more than configuration: secret scanning first, code scanning with the default query suite, then dependency review. This guide covers enterprise-scale deployment from license accounting through Defender for DevOps integration and alert triage that actually works.

I
Microsoft Cloud Solution Architect
GitHub Advanced SecurityGHASCodeQLSecret ScanningDependabotDevSecOpsSupply Chain SecurityGitHub Enterprise

The Alert Backlog That Kills Every GHAS Deployment

Three months after enabling GitHub Advanced Security across a 500-developer GitHub Enterprise Cloud org, the AppSec team stares at 47,000 open code scanning alerts. 12,000 fired in the first week because someone toggled the security-extended query suite on every repository without testing alert volume on representative codebases. The security program has regressed: the team now spends its time managing alert queues instead of doing security work.

This is not a GHAS capability problem. It is a rollout sequencing problem. GitHub Advanced Security gives you three distinct tools: code scanning with CodeQL, secret scanning with push protection, and dependency review with Dependabot. Each has a different signal-to-noise profile, different remediation latency, and different blast radius if you enable it wrong. The sequence matters more than any individual configuration setting.

---

GHAS License Accounting Before You Enable Anything

GHAS is priced per active committer in GitHub Enterprise. "Active" means any unique author who committed to a private or internal repository in the trailing 90 days. The billing trap: if you enable GHAS org-wide on day one, every developer who touched any private repo in the past 90 days counts against your license immediately. For an org licensed for 300 seats with 500 active contributors, that is an instant overage.

Audit your active committer count before enabling:

# Check org-level active committer count and per-repository breakdown
gh api \
  -H "Accept: application/vnd.github+json" \
  /orgs/{org}/settings/billing/advanced-security \
  --jq '{
    total_seats: .total_advanced_security_committers,
    purchased_seats: .purchased_advanced_security_committers,
    repos: [.repositories[] | {
      name: .name,
      committers: .advanced_security_committers
    }] | sort_by(-.committers) | .[0:20]
  }'

Use this data to prioritize: enable GHAS first on your highest-risk repositories (customer-facing APIs, repositories with PII processing, financial systems) and expand to lower-risk repositories as your remediation capacity scales.

For GitHub Enterprise Server 3.10+, use the license API endpoint at /api/v1/enterprise/settings/license and the management console committer report. GHES charges are reconciled quarterly, not in real time, so the overage risk is a budget issue rather than an immediate access block.

---

Phase 1: Secret Scanning with Push Protection

Secret scanning delivers the highest ROI per hour of configuration effort. The detection coverage spans 200+ partner patterns covering major cloud providers, payment processors, source control tokens, and communication APIs. The false positive rate for high-confidence patterns is under 5% and detection requires zero developer workflow changes.

Push protection upgrades the posture from reactive (find secrets after commit) to preventive (block secrets at push). A developer pushing a commit containing a covered secret gets:

remote: error: GH013: Repository rule violations found for refs/heads/main.
remote: — GITHUB PUSH PROTECTION
remote:   Detected secrets for the following ids:
remote:   0-github_token -> .env (line 3)
remote:   To push, please remove the detected secrets or use a bypass.

Enable both at org level:

gh api \
  --method PATCH \
  -H "Accept: application/vnd.github+json" \
  /orgs/{org} \
  -f "security_and_analysis[secret_scanning][status]=enabled" \
  -f "security_and_analysis[secret_scanning_push_protection][status]=enabled"

The bypass workflow allows developers to push through a detected secret with a justification: test credential, false positive, or will fix later. Every bypass creates an auditable event. Review bypasses weekly for any bypass of production-format credentials:

# Find all recent push protection bypasses
gh api \
  -H "Accept: application/vnd.github+json" \
  "/orgs/{org}/secret-scanning/alerts?state=open&per_page=100" \
  --paginate \
  --jq '[.[] | select(.push_protection_bypassed == true) | {
    number: .number,
    repo: .repository.full_name,
    type: .secret_type_display_name,
    bypassed_at: .push_protection_bypassed_at,
    bypasser: .push_protection_bypassed_by.login,
    url: .html_url
  }]'

Any bypass of an AWS secret access key, Azure service principal credential, or GitHub personal access token that is marked as "false positive" but has a format consistent with a production service warrants immediate investigation.

Custom Secret Patterns for Internal Tokens

GitHub's built-in patterns detect third-party service credentials. They will not detect your organization's proprietary tokens: internal API keys with custom formats, internal service account credentials, HMAC signing secrets with org-specific prefix patterns.

Define custom patterns at org level using regex. Navigate to Org Settings > Code security and analysis > Custom patterns > New pattern. The pattern editor validates your regex against sample strings before publishing.

Example: internal API key format CORP-[A-Z]{8}-[0-9A-F]{32}:

  • Pattern: CORP-[A-Z]{8}-[0-9A-F]{32}
  • Test string: CORP-ABCDEFGH-0123456789ABCDEF0123456789AB

Limitation as of GHEC May 2026: custom patterns do not support push protection. They scan committed content retroactively. Track the GitHub public roadmap for this capability. For the highest-risk internal token formats, consider implementing pre-receive hooks on GitHub Enterprise Server or pre-commit hooks deployed via your developer tooling repository.

---

Phase 2: Code Scanning with CodeQL

Default Setup vs. Advanced Setup

Default setup auto-detects repository languages, selects an appropriate query suite, and runs CodeQL on every push and pull request with no workflow YAML file required. Advanced setup deploys a codeql.yml workflow that you control, enabling custom query packs, build configuration flags, and third-party scanner integration.

For most enterprise repositories: use default setup. Reserve advanced setup for repositories with complex build requirements (native code, monorepos with non-standard build tools) or where you need to run custom CodeQL queries from an internal query pack.

Enable default setup across a repository via API:

gh api \
  --method PATCH \
  -H "Accept: application/vnd.github+json" \
  /repos/{owner}/{repo}/code-scanning/default-setup \
  -f state="configured" \
  -f query_suite="default" \
  -f languages='["javascript","typescript","python","java","csharp"]'

Script this against your prioritized repository list for bulk enablement. The API returns the new configuration state and the workflow run that was triggered.

Query Suite Selection

SuiteAlert Volume (per 100K LOC)False Positive RateRecommended For
default50 to 200Under 5%All repositories at initial rollout
security-extended200 to 80015 to 25%Repositories with dedicated AppSec review bandwidth
security-and-quality500 to 200020 to 35%Security champion programs; targeted quality initiatives
The default suite covers OWASP Top 10 categories with high-confidence queries: SQL injection, XSS, command injection, path traversal, unsafe deserialization. The security-extended suite adds medium-confidence queries for CWEs including CWE-338 (weak PRNG), CWE-730 (ReDoS), and broader injection variants.

Start every repository on default. Upgrade to security-extended only after you have demonstrated consistent remediation velocity on default findings. Enabling security-extended org-wide before remediation capacity is established generates a backlog that immediately exceeds what your team can triage, and those alerts then become noise that desensitizes developers to real findings.

Suppression Discipline

Inline CodeQL suppressions (language-specific comment annotations) can silence alerts without fixing the underlying issue. Without enforcement, suppressions accumulate silently. A suppressed critical finding in a production repository is a compliance liability.

Enforce suppression documentation with a PR workflow check:

# .github/workflows/codeql-suppression-audit.yml
name: CodeQL Suppression Audit
on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs: check-suppressions: runs-on: ubuntu-latest steps: <ul class="list-disc pl-6 mb-4 space-y-2"> <li class="text-gray-600 ml-6">uses: actions/checkout@v4</li> </ul> with: fetch-depth: 0 <ul class="list-disc pl-6 mb-4 space-y-2"> <li class="text-gray-600 ml-6">name: Reject undocumented CodeQL suppressions</li> </ul> run: | CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '.(java|js|ts|py|cs|go|rb)$' || true) if [ -z "$CHANGED_FILES" ]; then exit 0; fi

BARE=$(echo "$CHANGED_FILES" | xargs grep -l "codeql[" 2>/dev/null | xargs grep -E "codeql[" | grep -v -E "(reason|justif|false.pos|intentional|approved|ticket)" || true)
if [ -n "$BARE" ]; then
            echo "Found CodeQL suppressions without documented justification:"
            echo "$BARE"
            echo "Add a reason comment: # codeql[rule-id] reason: <explanation>"
            exit 1
          fi
          echo "All suppressions are documented."

This does not prevent suppressions, but it requires that the suppression comment includes one of the justification keywords. Adjust to match your team's convention.

---

Phase 3: Dependency Review and Supply Chain Security

Dependency Review Action in PRs

Dependabot alerts fire when a dependency already in your repository has a known CVE. The dependency review action operates at PR time and blocks merges that introduce new vulnerable dependencies. Both are needed; the PR-time check is operationally more valuable because it prevents vulnerabilities from entering the default branch rather than tracking them after the fact.

# .github/workflows/dependency-review.yml
name: Dependency Review
on:
  pull_request:
    branches: [main, master, 'release/**']

permissions: contents: read pull-requests: write

jobs: dependency-review: runs-on: ubuntu-latest steps: <ul class="list-disc pl-6 mb-4 space-y-2"> <li class="text-gray-600 ml-6">uses: actions/checkout@v4</li> <li class="text-gray-600 ml-6">uses: actions/dependency-review-action@v4</li> </ul> with: fail-on-severity: high deny-licenses: GPL-3.0, AGPL-3.0, SSPL-1.0 comment-summary-in-pr: always base-ref: ${{ github.event.pull_request.base.sha }} head-ref: ${{ github.event.pull_request.head.sha }}

The deny-licenses parameter is a legal compliance control, not just a security control. AGPL-3.0 and SSPL-1.0 have aggressive copyleft requirements that affect commercial software. Catching these at PR time prevents the situation where a library is deep in your dependency tree for six months before legal review flags it.

Dependabot Auto-Merge for Patch Security Updates

Without grouping and auto-merge, Dependabot generates one PR per vulnerable dependency. In a large JavaScript project with 300 direct dependencies, a batch advisory publication generates 40 simultaneous PRs. Auto-merge patch-level security updates to reduce remediation latency from "whenever someone reviews it" to under 24 hours:

# .github/dependabot.yml
version: 2
updates:
<ul class="list-disc pl-6 mb-4 space-y-2">
<li class="text-gray-600 ml-2">package-ecosystem: "npm"</li>
</ul>
    directory: "/"
    schedule:
      interval: "daily"
      time: "06:00"
      timezone: "UTC"
    open-pull-requests-limit: 5
    groups:
      patch-security:
        applies-to: security-updates
        patterns: ["*"]
        update-types: ["patch"]
      minor-security:
        applies-to: security-updates
        patterns: ["*"]
        update-types: ["minor"]
<ul class="list-disc pl-6 mb-4 space-y-2">
<li class="text-gray-600 ml-2">package-ecosystem: "pip"</li>
</ul>
    directory: "/"
    schedule:
      interval: "weekly"
    groups:
      security-all:
        applies-to: security-updates
        patterns: ["*"]
# .github/workflows/dependabot-auto-merge.yml
name: Dependabot Auto-Merge
on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

permissions: contents: write pull-requests: write

jobs: auto-merge: if: github.actor == 'dependabot[bot]' runs-on: ubuntu-latest steps: <ul class="list-disc pl-6 mb-4 space-y-2"> <li class="text-gray-600 ml-6">uses: actions/checkout@v4</li> <li class="text-gray-600 ml-6">name: Get Dependabot metadata</li> </ul> id: meta uses: dependabot/fetch-metadata@v2 with: github-token: "${{ secrets.GITHUB_TOKEN }}" <ul class="list-disc pl-6 mb-4 space-y-2"> <li class="text-gray-600 ml-6">name: Auto-merge patch security updates</li> </ul> if: | steps.meta.outputs.update-type == 'version-update:semver-patch' && steps.meta.outputs.dependency-type == 'direct:production' run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Scope auto-merge to direct:production dependencies only. Transitive dependency updates have a higher regression risk. Minor-level updates should go through standard PR review.

---

Enterprise Scale: Organization Security Configurations and Rulesets

Organization Security Configurations

GitHub Enterprise Cloud lets you define Security Configurations: preset bundles of GHAS settings that apply to repositories and override repository-level settings. Once applied, repository administrators cannot disable code scanning or push protection.

Create and apply a security configuration:

# Create org security configuration
CONFIG_ID=$(gh api \
  --method POST \
  -H "Accept: application/vnd.github+json" \
  /orgs/{org}/code-security/configurations \
  -f name="Enterprise Security Baseline" \
  -f description="Non-overridable GHAS baseline for all repositories" \
  -f advanced_security="enabled" \
  -f secret_scanning="enabled" \
  -f secret_scanning_push_protection="enabled" \
  -f dependabot_alerts="enabled" \
  -f dependabot_security_updates="enabled" \
  -f code_scanning_default_setup="enabled" \
  -f private_vulnerability_reporting="enabled" \
  --jq '.id')

# Apply to all existing repositories gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ /orgs/{org}/code-security/configurations/${CONFIG_ID}/attach \ -f scope="all" \ -f default_for_new_repos=true

Branch Rulesets with Required Security Checks

Repository rulesets enforce required status checks at the org level. CodeQL, dependency review, and secret scanning results become blocking conditions before merging to main:

{
  "name": "security-baseline",
  "target": "branch",
  "enforcement": "active",
  "conditions": {
    "ref_name": {
      "include": ["refs/heads/main", "refs/heads/master", "refs/heads/release/*"],
      "exclude": []
    }
  },
  "rules": [
    {
      "type": "required_status_checks",
      "parameters": {
        "strict_required_status_checks_policy": true,
        "required_status_checks": [
          {"context": "CodeQL"},
          {"context": "Dependency Review"},
          {"context": "CodeQL Suppression Audit"}
        ]
      }
    },
    {
      "type": "required_pull_request_reviews",
      "parameters": {
        "required_approving_review_count": 1,
        "dismiss_stale_reviews_on_push": true,
        "require_code_owner_review": true
      }
    },
    {"type": "non_fast_forward"},
    {"type": "deletion"}
  ]
}

Deploy via API for consistent application across repositories:

gh api \
  --method POST \
  -H "Accept: application/vnd.github+json" \
  /orgs/{org}/rulesets \
  --input ruleset.json

The strict_required_status_checks_policy flag is important: it prevents merging if the required check has not run at all, not just if it has failed. A developer cannot bypass CodeQL by only modifying files that do not trigger the scan workflow.

---

Integrating GHAS with Azure: Defender for DevOps

Defender for DevOps (part of Defender for Cloud) pulls GHAS findings into Azure security posture alongside your cloud resource misconfigurations. The integration uses a GitHub App installed on your org, configured through the Defender for Cloud connectors page.

Once connected, GHAS findings appear as Defender for Cloud recommendations:

  • "Repositories should have code scanning enabled"
  • "Code scanning findings should be resolved"
  • "Repositories should have secret scanning enabled"

The operational value: your Azure SOC team sees AppSec posture in the same dashboard as cloud resource misconfigurations without needing direct GitHub access. Security findings from code scanning in a production service repository appear alongside the Azure Defender alerts for that service's cloud resources.

Defender for DevOps also writes findings to the Log Analytics workspace connected to Defender for Cloud. Query GHAS-sourced alerts directly in Sentinel:

SecurityAlert
| where ProviderName == "Microsoft Defender for DevOps"
| extend Properties = parse_json(ExtendedProperties)
| project
    TimeGenerated,
    AlertName,
    AlertSeverity,
    RepositoryName = Properties.repositoryName,
    Branch = Properties.branch,
    RuleId = Properties.ruleId,
    FilePath = Properties.filePath,
    RemediationSteps
| where AlertSeverity in ("Critical", "High")
| order by TimeGenerated desc

Create a Sentinel analytics rule that fires on new critical GHAS findings in production repositories. The rule condition: AlertSeverity == "Critical" and RepositoryName contains "prod". Detection latency with this setup is under 15 minutes from the time CodeQL posts the finding to GitHub.

---

Alert Triage at Scale

Without a defined SLA, GHAS alerts accumulate indefinitely. Teams with no triage process see 80% of critical alerts age past 30 days within 90 days of GHAS deployment. The alerts become background noise.

Define remediation SLAs:

SeverityTarget RemediationEscalation
Critical7 calendar daysEngineering VP + CISO notification
High30 calendar daysEngineering Director
Medium90 calendar daysTeam lead
LowNext sprint or dismiss with written justificationNone
Track SLA compliance with a weekly API script:
# Find critical alerts breaching the 7-day SLA
CUTOFF=$(date -d '7 days ago' --iso-8601=seconds 2>/dev/null || date -v-7d +%Y-%m-%dT%H:%M:%SZ)

gh api \ -H "Accept: application/vnd.github+json" \ "/orgs/{org}/code-scanning/alerts?severity=critical&state=open&per_page=100" \ --paginate \ --jq --arg cutoff "$CUTOFF" \ '[.[] | select(.created_at < $cutoff) | { repo: .repository.full_name, rule: .rule.id, description: .rule.description, created: .created_at, age_days: (now - (.created_at | fromdateiso8601) / 86400 | floor), url: .html_url }]' | jq 'sort_by(-.age_days)'

The pre-GHAS backlog deserves a separate treatment. Alerts older than 180 days from repositories with no active commits or no active deployments can be bulk-dismissed with an organizational justification: "Pre-GHAS baseline; triaged against current attack surface; repository inactive." This is not security theater — it reflects that an unmaintained repository with no running deployment has a different risk profile from an actively deployed service. Do not let historical backlog crowd out actionable new findings in production systems.

---

Hardening Checklist

  • [ ] Active committer count audited before enabling GHAS broadly: billing API checked for license headroom vs. org-wide committer count
  • [ ] Secret scanning enabled at org level for all private and internal repositories
  • [ ] Push protection enabled at org level: no developer can push a covered secret without creating an audit event
  • [ ] Custom secret patterns defined for internal token formats: proprietary API keys, service account credentials, signing secrets
  • [ ] Push protection bypass events reviewed weekly: any bypass of a production-format credential triggers an investigation
  • [ ] Code scanning default setup deployed org-wide: query_suite=default at rollout, not security-extended
  • [ ] security-extended query suite applied only to repositories with consistent AppSec review bandwidth demonstrated on default findings first
  • [ ] Suppression hygiene enforced: PR workflow rejects bare suppression annotations without documented justification keywords
  • [ ] Dependency review action deployed on all PR workflows: fail-on-severity: high, copyleft license deny list (GPL-3.0, AGPL-3.0, SSPL-1.0)
  • [ ] Dependabot alerts enabled on all repositories
  • [ ] Dependabot security updates grouped: one PR per severity level per ecosystem, not one PR per package
  • [ ] Dependabot auto-merge configured for patch-level security updates on direct production dependencies that pass all CI checks
  • [ ] Org Security Configuration created and applied: teams cannot disable scanning controls at the repository level
  • [ ] Branch rulesets deployed at org scope: CodeQL and dependency review as required status checks for main and master
  • [ ] Defender for DevOps connector configured: GHAS findings visible in Defender for Cloud posture and queryable in Sentinel
  • [ ] Sentinel analytics rule deployed: alert on new critical GHAS findings in production repositories within 15 minutes
  • [ ] Remediation SLA defined and tracked: critical (7 days), high (30 days), medium (90 days) with named escalation paths
  • [ ] Pre-GHAS alert backlog age-gated: alerts older than 180 days from inactive repositories reviewed and bulk-dismissed with documented justification
N

Recommended tool: Nordpass

Up to 40% commission

Get weekly security insights

Cloud security, zero trust, and identity guides — straight to your inbox.

I

Microsoft Cloud Solution Architect

Cloud Solution Architect with deep expertise in Microsoft Azure and a strong background in systems and IT infrastructure. Passionate about cloud technologies, security best practices, and helping organizations modernize their infrastructure.

Share this article

Questions & Answers

Related Articles

Need Help with Your Security?

Our team of security experts can help you implement the strategies discussed in this article.

Contact Us