GitHub Advanced Security (GHAS) bundles three capabilities: CodeQL for SAST, secret scanning for committed credentials, and Dependabot for vulnerable dependencies. All three are free for public repositories and included in GitHub Enterprise Cloud / Team plans for private ones. The defaults are reasonable, but the defaults are not the optimal configuration. Here’s how to tune each one.
CodeQL: Configuration That Matters
CodeQL’s default setup (the checkbox in repository settings) runs a baseline query suite on every push and pull request. It catches common vulnerabilities but misses many security-extended queries that are off by default because they have higher false positive rates.
The right approach is a custom workflow with the security-extended suite:
name: CodeQL Analysis
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
schedule:
- cron: '0 6 * * 1' # Weekly full scan on Monday
permissions: {}
jobs:
analyze:
name: CodeQL
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
actions: read
strategy:
fail-fast: false
matrix:
language: [javascript-typescript, python]
# Add: java-kotlin, csharp, go, ruby, swift, cpp as needed
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Initialize CodeQL
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with:
languages: ${{ matrix.language }}
queries: security-extended
# For TypeScript projects with compiled output:
# config-file: .github/codeql/codeql-config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with:
category: /language:${{ matrix.language }}
For monorepos or projects where autobuild doesn’t work:
# .github/codeql/codeql-config.yml
name: CodeQL Config
paths:
- src
- lib
paths-ignore:
- '**/__tests__/**'
- '**/*.test.ts'
- node_modules
Triaging CodeQL Findings
Not every CodeQL finding is a real vulnerability. Common false positives:
“Incomplete URL scheme check” - CodeQL flags any URL scheme comparison that doesn’t include all possible schemes. In practice, if you’re checking for http:// and https:// specifically, that’s often intentional.
“Potentially unsafe external link” - Flags target="_blank" without rel="noopener". This is a real issue in old browsers but mitigated by modern browsers automatically. Worth fixing, but not urgent.
“Log injection” - Flags logging of user-controlled values. Evaluate whether the log destination is accessible to end users. If logs go to CloudWatch, the practical risk is low.
To dismiss a false positive with context (preserves the audit trail):
- Click the finding in the Security tab
- “Dismiss alert” → select reason → add a note explaining why
- The dismissal is recorded in audit logs with your identity and timestamp
Do not dismiss without a note. Future reviewers need to understand why.
Secret Scanning: Push Protection
Secret scanning alerts you after a secret is committed. Push protection alerts you before the commit is accepted. Enable it:
Repository settings → Security → Secret scanning → Push protection
Or via API for an entire organisation:
gh api --method PATCH /orgs/your-org \
-f secret_scanning_push_protection_enabled_for_new_repositories=true
Push protection checks the push against 200+ known secret patterns. If a match is found, the push is rejected with the specific secret identified.
Custom Patterns
Beyond the 200 built-in patterns, you can define custom patterns for your organisation’s secrets (internal API keys, service tokens, etc.):
# Custom pattern for internal API keys (format: PET-[32 hex chars])
PET-[0-9a-f]{32}
Add custom patterns in GitHub Settings → Security → Secret scanning → Custom patterns.
Handling False Positives
Push protection will occasionally flag test fixtures, example keys, or intentionally non-functional tokens. The correct response:
- If it’s a real secret: rotate it before bypassing. Then move it to Secrets Manager / environment variables.
- If it’s a test fixture: rename it to be obviously fake (
EXAMPLE_API_KEY_NOT_REAL) or store it in.gitignored files. - If it’s a known false positive pattern: add a
# secret-scanning-ignorecomment on the same line, then request a bypass with justification.
Dependabot: Configuration for Real Projects
The default Dependabot config checks npm, pip, maven, etc. for vulnerable versions. The important additional configuration is grouped updates:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
day: monday
time: "08:00"
timezone: Europe/London
groups:
minor-patch:
update-types: [minor, patch]
major:
update-types: [major]
ignore:
- dependency-name: "react"
versions: ["18.x"] # Pin until ready to test React 19
commit-message:
prefix: "deps"
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
actions:
patterns: ["*"]
commit-message:
prefix: "ci"
- package-ecosystem: terraform
directory: /infra
schedule:
interval: weekly
commit-message:
prefix: "infra"
Grouped updates reduce PR noise significantly - instead of one PR per package, you get one PR for all minor/patch updates. Review the diff, run tests, merge.
Dependabot Security Updates vs Version Updates
Dependabot has two modes:
- Security updates: Opens PRs specifically for packages with known CVEs. Enabled by default if vulnerability alerts are on. These should be reviewed urgently - they have a concrete CVE attached.
- Version updates: Opens PRs to keep dependencies current. These are optional but reduce security debt over time (older versions have more CVEs when new ones are discovered).
Both are useful. Security updates are the priority.
The April 2026 Changes
GitHub updated CodeQL defaults this month:
-
JavaScript/TypeScript now enables
security-extendedqueries by default in the auto-configuration mode. If you were using default setup, you may see new findings appear - review them before dismissing. -
Secret scanning push protection is now enabled by default for new public repositories. Existing repositories need manual enablement.
-
Dependabot grouped updates are now GA (previously beta). The
groupskey in dependabot.yml is stable and recommended. -
CodeQL query suite pinning: Actions now accept a
packs:field for custom CodeQL packs from the GitHub registry, allowing organisation-specific query libraries. Useful for teams with proprietary frameworks.
Making It Useful, Not Noisy
GHAS generates real signal. The failure mode is treating every finding as low priority because there are too many. The antidote:
- Start with
security-extendedfor new findings,security-and-qualityonly if you’re willing to triage the full set - Dismiss false positives with notes, not silently
- Set branch protection to require CodeQL passing on PRs to main
- Treat Dependabot security update PRs as P1 (24-hour review SLA)
- Review custom secret patterns quarterly - your credential formats change
The security tab in GitHub is worth spending 30 minutes in every sprint. Most findings can be addressed in minutes. The ones that can’t deserve a tracked issue.