PHASE 7 OF 14

Security: Repository & Code Security

The full GitHub security stack — Dependabot alerts vs security updates vs version updates, dependabot.yml configuration, secret scanning with push protection, dependency review in PRs, security advisories, and minimising blast radius with scoped secrets

Dependabot Secret Scanning Push Protection CVE CVSS Security Advisories
7.1

GitHub Security Features Overview

GitHub's security tooling covers four distinct threat categories. Understanding which feature addresses which threat is the starting point for building a coherent security posture.

Dependabot alerts Threat: known vulnerabilities in your dependencies. GitHub compares your dependency graph against the GitHub Advisory Database (GHSA) and alerts when a match is found. Passive — alerts you, doesn't fix anything.
Dependabot security updates Threat: same as above. GitHub automatically opens a PR that bumps the vulnerable dependency to a non-vulnerable version. Reactive — responds to newly published CVEs.
Dependabot version updates Threat: dependency staleness and future vulnerability exposure. Proactively opens PRs to keep dependencies current on a schedule. Configured in dependabot.yml.
Secret scanning Threat: accidentally committed credentials (API keys, tokens, passwords). Scans every push and every historical commit for known secret patterns. Push protection blocks commits containing secrets before they land.
Code scanning (CodeQL) Threat: code-level vulnerabilities (SQL injection, XSS, path traversal, etc.). Static analysis that runs on every PR. Covered in detail in Phase 8.
Dependency review Threat: introducing a new vulnerable dependency in a PR. Shows a diff of dependency changes and blocks merge if any new dependency has a known CVE above your threshold.
Security advisories Threat: vulnerabilities in your own code that need coordinated disclosure. Private space to draft, collaborate on, and publish security advisories; can request a CVE ID from GitHub.
Private vulnerability reporting Threat: security researchers reporting bugs publicly instead of privately. Gives researchers a private channel to submit reports directly into a draft advisory without your email address.
AVAILABILITY
Most features are free for public repos. For private repos: Dependabot alerts and secret scanning are free on all plans. Secret scanning push protection, code scanning, and private vulnerability reporting require GitHub Advanced Security (GHAS) — included in GitHub Enterprise and available as an add-on for Teams.
7.2

Dependabot: Alerts vs Security Updates vs Version Updates

Dependabot AlertsDependabot Security UpdatesDependabot Version Updates
What triggers it A CVE is published that matches your dependency graph Same — plus GitHub can find a non-vulnerable version to upgrade to A newer version of a dependency is available (regardless of CVEs)
What it does Creates a security alert in the Security tab; optionally emails/Slacks Opens a PR bumping the vulnerable package to the minimum safe version Opens a PR bumping the package to the latest (or configured) version on a schedule
Where configured Settings → Security → Dependabot alerts (on/off toggle) Settings → Security → Dependabot security updates (on/off toggle) .github/dependabot.yml file
Requires dependabot.yml? No No Yes
Typical team policy Always on; review critical/high within 48 hours Always on; auto-merge patches, review minors manually On for all dependency ecosystems; group patch updates; require review for majors
SECURITY UPDATES ≠ VERSION UPDATES
A Dependabot security update PR only bumps to the minimum version that fixes the CVE — not the latest version. A version update PR bumps to the latest. Both types can coexist; they serve different purposes. Teams that only rely on security updates can still accumulate months of version lag, increasing future upgrade friction.
7.3

dependabot.yml: Ecosystems, Groups, Ignore Rules & Reviewers

A well-configured dependabot.yml keeps your dependencies fresh without drowning the team in PR noise. The key levers are scheduling, grouping, and ignore rules.

# .github/dependabot.yml version: 2 updates: # ── npm / Node.js ────────────────────────────────────────────── - package-ecosystem: npm directory: / # where package.json lives schedule: interval: weekly day: monday time: '09:00' timezone: Asia/Kolkata open-pull-requests-limit: 10 reviewers: - acme/platform-leads labels: - dependencies - area/frontend commit-message: prefix: chore # conventional commit prefix include: scope # Group all patch and minor updates into a single weekly PR groups: non-major: update-types: - minor - patch major: update-types: - major # Never auto-update these — they require manual testing ignore: - dependency-name: webpack update-types: [version-update:semver-major] - dependency-name: typescript versions: ['>=6.0.0'] # hold below TS 6.x until we're ready # ── Maven / Java ──────────────────────────────────────────────── - package-ecosystem: maven directory: / schedule: interval: weekly groups: spring-boot: patterns: ['org.springframework.boot:*'] other-deps: patterns: ['*'] exclude-patterns: ['org.springframework.boot:*'] # ── GitHub Actions ────────────────────────────────────────────── - package-ecosystem: github-actions directory: / schedule: interval: weekly groups: actions: patterns: ['*'] # ── Docker base images ────────────────────────────────────────── - package-ecosystem: docker directory: / schedule: interval: monthly # base images change less often

Supported ecosystems

npm maven gradle pip poetry gomod cargo nuget bundler composer docker github-actions terraform hex pub
GROUPING STRATEGY
Without groups, Dependabot opens one PR per package. A large project can generate 30+ PRs per week — impossible to review meaningfully. Group patch + minor updates into a single "maintenance" PR. Keep major updates separate so they get deliberate attention. Pin framework families (Spring Boot, React, Angular) into their own groups since their packages must be updated together.
7.4

Secret Scanning: Default Patterns, Custom Patterns & Push Protection

Secret scanning watches for credentials in your code. It operates in two modes: retroactive scanning (after a secret is committed) and push protection (blocking the commit before it lands).

Default patterns

GitHub maintains a list of 200+ patterns for credentials from major providers — AWS access keys, GitHub tokens, Stripe keys, Slack webhooks, GCP service accounts, Twilio keys, and dozens more. These are detected automatically without any configuration.

When a match is found on a private repo, GitHub notifies the repo admins and the commit author. For public repos, GitHub also notifies the affected service provider (e.g., AWS, Stripe) who may revoke the credential automatically.

Push protection

Push protection intercepts a push before it reaches the remote. When a secret pattern is detected, the push is rejected with a message identifying the file and pattern type:

$ git push origin main remote: error: GH013: Repository rule violations found for refs/heads/main. remote: remote: - Push cannot contain secrets remote: —— AWS Access Key ——————————————————————————— remote: locations: remote: - commit: a3f8d12 remote: path: src/config.js:14 remote: remote: To push, remove the secret from your commit history remote: and try again. Alternatively, to skip this check remote: use the GitHub web UI to allow this secret.

Removing a committed secret

# Option 1: interactive rebase to edit the offending commit $ git rebase -i HEAD~3 # Mark the offending commit as 'edit', then: $ git restore --staged src/config.js # Remove the secret from the file, then: $ git add src/config.js $ git commit --amend $ git rebase --continue # Option 2: git-filter-repo (faster for deep history) $ pip install git-filter-repo $ git filter-repo --path src/config.js --invert-paths # Then add the file back without the secret and re-commit # ALWAYS: revoke the secret immediately — before/during/after removal # A secret that touched GitHub's servers should be treated as compromised
REVOKE FIRST
Removing the secret from Git history does not make it safe. The secret was exposed during the push — bots scan GitHub in real-time and can capture secrets within seconds of exposure. Revoke the credential immediately, then clean the history. Treat the secret as compromised regardless of how quickly you removed it.

Bypass policies

Sometimes push protection generates false positives (a test fixture that looks like a real token). The bypass options are:

  • It's used in tests — the value is a test or placeholder, not a real credential
  • It's a false positive — the pattern matched but it's not actually a secret
  • I'll fix it later — acknowledge and push (creates an audit event; admin can see this)

All bypasses are logged in the audit log. Org admins can configure which bypass reasons are allowed and which require explicit org-level approval.

Custom secret scanning patterns

For internal credentials (internal API keys, custom token formats), add custom patterns:

# Settings → Security → Secret scanning → Custom patterns → New pattern Pattern name: Acme Internal API Key Pattern: acme_[a-z0-9]{32} Test string: acme_k7f2m9x1q3w8e5r4t6y0u2i1o9p3a5s7 # Custom patterns support PCRE regex syntax # Push protection can be enabled per custom pattern
7.5

Security Policies, Private Vulnerability Reporting & Coordinated Disclosure

SECURITY.md

A SECURITY.md file tells security researchers how to report vulnerabilities. GitHub surfaces it in the Security tab and adds a "Report a vulnerability" button when private vulnerability reporting is enabled.

# SECURITY.md — production-ready template ## Security Policy ### Supported Versions | Version | Supported | |---------|--------------------| | 3.x | ✅ Actively supported | | 2.x | ✅ Security fixes only | | < 2.x | ❌ End of life | ### Reporting a Vulnerability **Please do not file a public GitHub issue for security vulnerabilities.** #### Option 1 — GitHub Private Reporting (preferred) Use the "Report a vulnerability" button in the Security tab. We'll respond within 48 hours. #### Option 2 — Email security@acme.com (PGP key at https://acme.com/pgp.asc) ### Our Process 1. Acknowledge receipt within 48 hours 2. Confirm the vulnerability and assess severity within 5 business days 3. Develop and test a patch 4. Coordinate disclosure timing with the reporter 5. Release the patch and publish a security advisory 6. Credit the reporter (unless they prefer anonymity) We follow a 90-day coordinated disclosure policy.

Enabling private vulnerability reporting

Settings → Security → Private vulnerability reporting → Enable. Once enabled:

  • A "Report a vulnerability" button appears on the Security tab
  • Reports land in a private draft security advisory, visible only to repo admins and collaborators you invite
  • GitHub automatically suggests CVSS scores and CWE categories
  • The reporter can collaborate on the draft advisory before it's published
WHY THIS MATTERS
Without private reporting, security researchers who discover a vulnerability in your project have two bad options: email an unknown address (often ignored) or open a public issue (exposes the vulnerability to everyone). Private reporting gives them a proper channel, which means more responsible disclosures and fewer public zero-days in your code.
7.6

Repository Security Settings Checklist

A hardened private repo should have all of these enabled. Walk through Settings → Security for each repo or automate via the REST API.

SettingWhereRecommendedNotes
Dependabot alerts Security → Dependabot ✅ On Free on all plans; no reason to leave off
Dependabot security updates Security → Dependabot ✅ On Automatically opens fix PRs for known CVEs
Secret scanning Security → Secret scanning ✅ On Free on all plans for private repos
Secret scanning push protection Security → Secret scanning ✅ On (GHAS) Blocks secrets before they land — much better than post-commit alerts
Private vulnerability reporting Security → Private vulnerability reporting ✅ On Free; gives researchers a responsible disclosure channel
Dependency graph Security → Dependency graph ✅ On Required for Dependabot alerts to work; enables dependency review in PRs
Private fork policy Settings → General Consider Allow forking privately within your org for internal contributors — disable public forking if the code is sensitive

Auditing security settings across all repos in an org

$ gh api orgs/acme/repos --paginate \ --jq '.[] | select(.private == true) | { name: .name, secret_scanning: .security_and_analysis.secret_scanning.status, push_protection: .security_and_analysis.secret_scanning_push_protection.status, dependabot_alerts: .security_and_analysis.dependabot_security_updates.status }'
7.7

Dependency Review: Blocking PRs on New Vulnerable Dependencies

The dependency review action compares the dependency manifest changes in a PR against the GitHub Advisory Database. It blocks the PR if any newly added dependency has a known CVE above your severity threshold.

# .github/workflows/dependency-review.yml on: pull_request: branches: [main, 'release/**'] permissions: contents: read jobs: dependency-review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/dependency-review-action@v4 with: fail-on-severity: high # critical | high | moderate | low allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC deny-licenses: GPL-2.0, GPL-3.0, AGPL-3.0 comment-summary-in-pr: always # post a summary comment on the PR warn-only: false # set true to surface warnings without blocking

When the action finds a problem it posts a comment on the PR showing:

  • The vulnerable package name and version being added
  • The CVE ID, CVSS score, and severity
  • The vulnerable version range and the minimum safe version
  • A direct link to the GitHub Advisory

License compliance gate

The allow-licenses / deny-licenses inputs add a licence compatibility check at the PR level — catching GPL contamination or AGPL obligations before they reach your codebase. This is particularly valuable for commercial software that can't adopt copyleft dependencies.

COMPLEMENTARY TO DEPENDABOT
Dependabot alerts tell you about vulnerabilities in existing dependencies after they're committed. Dependency review catches vulnerabilities in new dependencies being introduced via a PR — before they merge. Use both together.
7.8

Security Advisories: Drafting, CVSS, CVE IDs & Publishing

When a vulnerability is found in your code (by your team or a researcher), GitHub Security Advisories give you a private workspace to draft the advisory, coordinate with the reporter, develop a patch in a private fork, and publish when ready.

Advisory lifecycle

  1. Create a draft — Security tab → Advisories → New draft security advisory
  2. Fill in details — affected package, vulnerable version range, severity (CVSS), CWE category, description
  3. Invite collaborators — add the reporter and your security team; all communication is private
  4. Request a CVE — GitHub is a CVE Numbering Authority (CNA); request a CVE ID directly from the advisory
  5. Create a private fork — GitHub creates a temporary private fork where you can develop and review the fix without exposing it
  6. Publish — when the fix is released, publish the advisory. It enters the GitHub Advisory Database and Dependabot begins alerting anyone who uses your package.

CVSS scoring basics

CVSS (Common Vulnerability Scoring System) v3.1 produces a score from 0–10 based on base metrics:

Score rangeSeverityTypical response SLA
9.0–10.0 Critical Patch within 24–48 hours; emergency release if needed
7.0–8.9 High Patch within 7 days; include in next planned release
4.0–6.9 Medium Patch within 30 days; schedule in next sprint
0.1–3.9 Low Patch within 90 days; best-effort

GitHub's advisory UI has a CVSS calculator — fill in the attack vector (network/local), privileges required, user interaction, and impact (confidentiality/integrity/availability) and it produces the score. Don't agonise over precision; a score accurate to ±1 point is sufficient for triage.

CWE categories (common ones)

CWE-79 Cross-Site Scripting (XSS) CWE-89 SQL Injection CWE-22 Path Traversal CWE-352 Cross-Site Request Forgery (CSRF) CWE-287 Improper Authentication CWE-200 Exposure of Sensitive Information CWE-400 Uncontrolled Resource Consumption (DoS) CWE-502 Deserialization of Untrusted Data CWE-611 XML External Entity (XXE) Injection CWE-918 Server-Side Request Forgery (SSRF)
7.9

Minimising Blast Radius with Environment-Scoped Secrets

The goal of blast radius minimisation is ensuring that if any single credential is compromised — through a leaked secret, a compromised runner, or a malicious dependency — the attacker can only reach a bounded subset of your systems.

The problem with repo-level secrets

Bad: one DEPLOY_KEY secret at repo level → Any workflow in the repo has access to DEPLOY_KEY → A compromised third-party action in your CI workflow accesses DEPLOY_KEY → Attacker can deploy to production
Better: environment-scoped secrets + branch protection → DEPLOY_KEY_STAGING in "staging" environment → DEPLOY_KEY_PROD in "production" environment (requires manual approval) → A compromised CI action can only reach STAGING credentials → PRODUCTION deployment is gated by human approval in the environment

Credential scoping strategy

Credential typeScopeRationale
Docker registry read token (to pull base images) Org-level All repos need it; read-only, low blast radius
npm publish token Repo-level Scoped to one package; only the publish job needs it
Staging deploy key Environment: staging Only the deploy job targeting staging gets access
Production deploy key Environment: production (with required reviewers) Human approval gate + scoped credential = two-factor deploy
Database admin password Never in GitHub secrets Use OIDC + cloud IAM or Vault dynamic secrets instead

OIDC as the ultimate blast radius minimiser

The best approach is to eliminate long-lived credentials from GitHub entirely using OIDC (Phase 6.9). When there are no stored secrets, there are no stored secrets to steal. The OIDC token is short-lived, scoped to the run, and can only be exchanged for credentials by a job that matches your IAM trust policy claims (specific repo + specific branch).

SECURITY POSTURE CHECKLIST
For each repo, ask:
1. Do we use OIDC for cloud auth where possible?
2. Are secrets scoped to environments rather than the repo?
3. Does the production environment require a human approval?
4. Does the CI workflow run with minimum necessary permissions:?
5. Are all third-party actions pinned to a SHA?
If the answer to any of these is no, you have identified a concrete security improvement.

Up Next — Phase 8: Supply Chain Security

CodeQL and SAST integration, SARIF uploads, artifact attestations, SBOM generation, Sigstore/cosign container signing, SHA-pinned actions with Dependabot, and OpenSSF Scorecard.

Continue to Phase 8 → Back to Hub