GitHub Advanced Security: Hardened Apps

GitHub Advanced Security: Hardened Apps
GitHub Advanced Security (GHAS) is a suite of security features built into GitHub that catches vulnerabilities before they reach production. CodeQL finds code-level security flaws through semantic analysis, Dependabot surfaces and patches vulnerable dependencies, secret scanning blocks accidentally committed credentials, and security policies enforce review requirements. Together they shift security left—catching issues in pull requests, not production incidents.
GHAS is free for public repositories. For private repositories, it requires GitHub Enterprise or GitHub Team with GHAS add-on.
CodeQL: Semantic Code Analysis
CodeQL compiles your source code into a database and runs queries against it. Unlike regex-based linters, CodeQL understands data flow—it can trace a value from a user-supplied HTTP parameter through three function calls to a SQL query and flag the injection.
Enable CodeQL in Actions
# .github/workflows/codeql.yml
name: CodeQL Analysis
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * 1' # Weekly full scan on Monday 2am
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
security-events: write # required to upload results
actions: read
contents: read
strategy:
fail-fast: false
matrix:
language: [javascript-typescript, python]
# Supported: c-cpp, csharp, go, java-kotlin, javascript-typescript, python, ruby, swift
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: security-extended # security-and-quality for broader coverage
- name: Build (auto-detect or manual)
uses: github/codeql-action/autobuild@v3
# For compiled languages, replace with your actual build command:
# run: |
# mvn package -DskipTests
# ./gradlew build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:${{ matrix.language }}
upload: alwaysCustom CodeQL Queries
You can write custom queries for application-specific patterns:
// .github/codeql/custom-queries/hardcoded-password.ql
/**
* @name Hardcoded password in variable assignment
* @description Detects password-like variable names assigned string literals
* @kind problem
* @problem.severity error
* @security-severity 9.0
* @id js/hardcoded-password
* @tags security
*/
import javascript
from AssignExpr assign, StringLiteral str
where
assign.getRhs() = str and
str.getStringValue().length() > 6 and
assign.getLhs().(PropAccess).getPropertyName().toLowerCase().matches([
"password", "passwd", "secret", "apikey", "api_key", "token"
])
select assign, "Possible hardcoded credential: " + assign.getLhs().(PropAccess).getPropertyName()# Register custom queries in codeql-config.yml
name: Custom CodeQL Config
queries:
- uses: ./.github/codeql/custom-queries
disable-default-queries: false
query-filters:
- exclude:
tags: experimentalUnderstanding CodeQL Results
CodeQL results appear in the Security → Code scanning tab. Each alert shows:
Alert: SQL Injection
Severity: Critical
File: src/api/users.ts:47
Description: This query depends on a user-provided value.
Data flow:
req.params.id (line 23, user input)
→ userId (line 27, assignment)
→ `SELECT * FROM users WHERE id = ${userId}` (line 47, SQL sink)Fix pattern:
// Flagged: string interpolation in SQL
const result = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
// Fixed: parameterized query
const result = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);Dependabot: Automated Dependency Updates
Dependabot Alerts vs Dependabot Security Updates
- Alerts: GitHub detects a CVE in your dependencies and notifies you. Informational only.
- Security Updates: Dependabot automatically opens a PR to bump the vulnerable dependency to a patched version.
- Version Updates: Dependabot opens PRs to keep all dependencies up to date on a schedule (not just security fixes).
Configure Dependabot
# .github/dependabot.yml
version: 2
updates:
# npm/yarn
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: monday
time: "08:00"
timezone: "America/New_York"
open-pull-requests-limit: 5
labels:
- dependencies
- javascript
ignore:
# Pin major versions manually
- dependency-name: react
update-types: [version-update:semver-major]
groups:
# Bundle minor/patch updates to reduce PR noise
dev-dependencies:
dependency-type: development
update-types:
- minor
- patch
# Python
- package-ecosystem: pip
directory: /backend
schedule:
interval: weekly
labels:
- dependencies
- python
# Docker base images
- package-ecosystem: docker
directory: /
schedule:
interval: monthly
# GitHub Actions
- package-ecosystem: github-actions
directory: /
schedule:
interval: weeklyAuto-Merge Dependabot PRs
For patch-level dependency updates, auto-merge after CI passes:
# .github/workflows/dependabot-auto-merge.yml
name: Auto-merge Dependabot PRs
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: write
pull-requests: write
jobs:
auto-merge:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Fetch Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Auto-merge patch and minor dev-dependency updates
if: |
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
(steps.metadata.outputs.update-type == 'version-update:semver-minor' &&
steps.metadata.outputs.dependency-type == 'direct:development')
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Secret Scanning and Push Protection
Secret scanning detects over 200 token patterns from major service providers (AWS, GitHub, Slack, Stripe, etc.) committed to repositories.
Push Protection
Push protection blocks pushes containing detected secrets before they ever reach the repository:
$ git push origin feature/payment-integration
remote: Resolving deltas: 100% (3/3), done.
remote: error: GH013: Repository rule violations found for refs/heads/feature/payment-integration.
remote:
remote: - Push cannot contain secrets
remote: —————————————————————————————————————————
remote: GITHUB PUSH PROTECTION
remote: —————————————————————————————————————————
remote: Detected secrets in commits:
remote: - Stripe Secret Key in src/config/payment.ts (line 4)
remote:
remote: To skip push protection use:
remote: --no-verify (if you are certain this is a false positive)
remote: or use the GitHub interface at https://github.com/.../security/secret-scanning/unblock-secret/...
To https://github.com/org/repo.git
! [remote rejected] feature → feature (push declined due to repository rule violations)Enable via Repository Settings
Settings → Security → Code security and analysis:
☑ Dependabot alerts
☑ Dependabot security updates
☑ CodeQL analysis
☑ Secret scanning
☑ Push protectionOr via API for bulk organization enablement:
# Enable GHAS for all repos in an organization
gh api \
-X PATCH \
/orgs/{org}/repos \
--field security_and_analysis='{"advanced_security":{"status":"enabled"},"secret_scanning":{"status":"enabled"},"secret_scanning_push_protection":{"status":"enabled"}}'Custom Secret Patterns
Add patterns for internal tokens your org uses:
Settings → Security → Code security and analysis → Custom patterns → New pattern
Name: Internal API Token
Secret format: (MYAPP-[A-Z0-9]{32})
Test string: MYAPP-K9X2M4L8N1R5T7W3Q6Y0P2V4J8H6F1A3Security Policy
A SECURITY.md file tells researchers how to responsibly disclose vulnerabilities:
<!-- SECURITY.md -->
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2.x | :white_check_mark: |
| 1.x | :x: |
## Reporting a Vulnerability
**Do not open a public GitHub issue for security vulnerabilities.**
Please report security issues to security@yourcompany.com. Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (optional)
We will acknowledge receipt within 48 hours and provide a fix timeline within 7 days.
## Security Advisories
Published security advisories are available at:
https://github.com/org/repo/security/advisoriesGitHub's Private Vulnerability Reporting feature (enabled in Settings → Security) lets researchers submit reports directly in GitHub without disclosing publicly:
Settings → Security → Private vulnerability reporting: EnabledBranch Protection with Security Requirements
Settings → Branches → Add rule → Branch name pattern: main
☑ Require a pull request before merging
☑ Require approvals: 2
☑ Dismiss stale pull request approvals when new commits are pushed
☑ Require review from Code Owners
☑ Require status checks to pass before merging
Required checks:
- CodeQL Analysis (javascript-typescript)
- Dependabot check
- Security scan
☑ Require conversation resolution before merging
☑ Require signed commits
☑ Include administratorsCODEOWNERS for Security Files
# .github/CODEOWNERS
# Security-sensitive files require security team review
/.github/workflows/ @org/security-team @org/platform-team
/src/auth/ @org/security-team
/src/payment/ @org/security-team @org/payments-team
SECURITY.md @org/security-team
docker-compose.yml @org/platform-teamSecurity Dashboard and Metrics
Security tab → Overview:
- Dependabot: 3 critical, 7 high, 12 medium
- Code scanning: 2 high (CodeQL), 0 medium
- Secret scanning: 0 active secrets
Organization Security Overview (Enterprise):
- View all repos with open alerts
- Filter by alert type, severity, language
- Track mean time to remediationUseful GitHub CLI commands for security monitoring:
# List all Dependabot alerts for a repo
gh api repos/{owner}/{repo}/dependabot/alerts --jq '.[] | select(.state=="open") | {package: .dependency.package.name, severity: .security_advisory.severity, fixed_in: .security_vulnerability.first_patched_version.identifier}'
# List code scanning alerts
gh api repos/{owner}/{repo}/code-scanning/alerts --jq '.[] | select(.state=="open") | {rule: .rule.id, severity: .rule.severity, file: .most_recent_instance.location.path}'
# Dismiss a false-positive alert
gh api -X PATCH repos/{owner}/{repo}/code-scanning/alerts/{alert_number} \
-f state=dismissed \
-f dismissed_reason=false_positive \
-f dismissed_comment="This is an internal test endpoint not reachable from production"Frequently Asked Questions
Q: Does CodeQL slow down pull request checks significantly?
For most projects CodeQL adds 5–15 minutes to the CI pipeline. You can optimize by running CodeQL only on PRs targeting main (not feature branches), using autobuild for compiled languages, and enabling query caching. The security-extended query suite is slower than security-and-quality but catches more issues. For very large monorepos, run CodeQL as a separate parallel job with its own job-level concurrency group so it does not block the main CI feedback loop.
Q: Can I configure Dependabot to ignore certain CVEs?
Yes. Use the ignore key in .github/dependabot.yml to skip specific packages or CVE IDs:
ignore:
- dependency-name: lodash
versions: [">= 4.0.0"] # ignore all 4.x updates
- dependency-name: "*"
update-types: [version-update:semver-major] # no major bumps for anythingYou can also dismiss individual Dependabot alerts in the Security tab with a reason (tolerable risk, not applicable, or false positive). Dismissed alerts do not re-open unless a new advisory is published for the same package.
Q: What happens if a developer bypasses push protection with --no-verify?
The push still goes through but GitHub logs the bypass event. Secret scanning continues to run on the repository content and will create an alert for the exposed secret. The alert appears in the Security tab and triggers email notifications. To prevent bypasses, you can restrict the bypass permission to security team members only under Settings → Security → Push protection → Who can bypass.
Q: Is GHAS useful for internal tools and private repositories without external users?
Absolutely. Internal tools are frequent targets in supply chain attacks—compromising a developer's internal tooling is how attackers pivot to production systems. Dependabot alerts on internal repos catch vulnerabilities in libraries used by internal tools before they become pivot points. CodeQL on internal repos prevents security-insecure patterns (SQL injection, SSRF) from being copy-pasted into customer-facing systems. Secret scanning on internal repos is where push protection provides the most value, since developers are more likely to commit secrets to "private" repos assuming no one is watching.
