Secret Scanning and Dependency Updates

Secret scanning and dependency updates close two of the most common gaps in a software delivery pipeline: credentials that leak into source control, and third-party packages that go unpatched after a vulnerability is disclosed. Both problems are largely automatable, but wiring the automation into a pipeline safely takes care around permissions, trust boundaries, and merge gating. This lesson covers GitHub’s built-in secret scanning and push protection, Dependabot version and security updates, and the workflow YAML needed to enforce these checks before code reaches production.

Overview / How it works

Secret scanning inspects commits, issues, and pull requests for patterns that match known credential formats, such as cloud provider access keys or API tokens issued by supported partners. When a match is found in an existing commit, GitHub raises a secret scanning alert visible to repository administrators. Push protection goes further: it blocks a push at the moment a recognized secret pattern is detected, before the commit ever reaches the remote history, unless the author explicitly bypasses the block and records a reason. Secret scanning alerts are free on public repositories; push protection and scanning of private repositories require GitHub Advanced Security. For private repositories without that license, or as a defense-in-depth layer alongside it, teams commonly add a scanning step to CI using an open-source tool such as gitleaks or trufflehog.

Dependency updates work through two related but distinct Dependabot features. Version updates are configured explicitly in .github/dependabot.yml: on a schedule you define, Dependabot checks each configured ecosystem for newer releases and opens pull requests to bump the version. Security updates are automatic and unscheduled: when GitHub’s Advisory Database records a new vulnerability affecting a dependency your repository uses, visible through the dependency graph and Dependabot alerts, Dependabot opens a pull request to the first patched version immediately, regardless of your configured schedule. Security updates require the dependency graph and Dependabot alerts to be enabled; version updates require only the configuration file.

Both features intersect with CI/CD workflow security. Workflow runs triggered by Dependabot pull requests execute with a read-only GITHUB_TOKEN and no access to repository secrets by default, specifically to prevent a malicious or compromised package manifest from exfiltrating credentials through a triggered build. Any workflow that needs elevated permissions to act on a Dependabot PR, such as auto-merging, must request that access deliberately and scope it narrowly.

Syntax or workflow structure

Dependabot version updates are declared once per repository in .github/dependabot.yml. The file lists one updates entry per package ecosystem, for example npm, pip, docker, or github-actions, each with its own directory, schedule, and optional grouping, labeling, and reviewer settings. Workflows that react to Dependabot activity, or that run a secret scan, follow the same structure as any other Actions workflow: an on trigger, an explicit permissions block scoped to only what the job needs, and one or more jobs. Because Dependabot pull requests are not from a fork, they can safely use pull_request_target for metadata-only steps, since the risk that trigger carries, running attacker-controlled code with elevated privileges, does not apply as long as the workflow never checks out and executes the dependency PR’s own code changes.

Examples

The following three examples build from configuring updates, to scanning for secrets in CI, to safely automating merges.

Example 1: Grouped version updates and github-actions updates

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
      day: "monday"
    groups:
      minor-and-patch:
        applies-to: version-updates
        update-types:
          - "minor"
          - "patch"
    open-pull-requests-limit: 10
    labels:
      - "dependencies"
    reviewers:
      - "platform-team"

  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    labels:
      - "dependencies"
      - "actions"

Expected behavior: every Monday, Dependabot opens a single grouped pull request for all eligible minor and patch npm updates instead of one PR per package, plus separate pull requests for outdated actions referenced in your workflow files. Major npm version bumps are excluded from the group and arrive as individual PRs, since they are more likely to contain breaking changes and deserve isolated review. Security updates for any severity still arrive immediately, outside this weekly schedule.

Example 2: Defense-in-depth secret scanning in CI

name: Secret Scan

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout with full history
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Run gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expected behavior: on every pull request and every push to main, the job scans the full commit history fetched with fetch-depth: 0 for patterns matching known secret formats. If gitleaks matches a pattern, such as a cloud access key shape, the job fails and the check shows as failed on the pull request. With branch protection configured to require this check, the pull request cannot merge until the match is resolved: the credential is rotated at its source and removed from history, not merely edited in a follow-up commit. The permissions block grants only read access to repository contents, since this job does not need to write anything.

Example 3: Gated auto-merge for Dependabot pull requests

name: Dependabot Auto-Merge

on:
  pull_request_target:
    branches: [main]

permissions:
  contents: write
  pull-requests: write

jobs:
  auto-merge:
    if: github.actor == 'dependabot[bot]'
    runs-on: ubuntu-latest
    steps:
      - name: Fetch Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Enable auto-merge for patch and minor updates
        if: steps.metadata.outputs.update-type != 'version-update:semver-major'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Expected behavior: the job only runs for pull requests authored by dependabot[bot]. It reads the update type from PR metadata without checking out the dependency PR’s own file changes, so it never executes code proposed by the update itself. For minor and patch bumps, gh pr merge --auto tells GitHub to merge automatically once all required status checks, including the secret scan and your test suite from earlier lessons, report success; it does not bypass them. Major version bumps are left out of the condition, so they still require a human reviewer.

Step by step

  1. In repository or organization settings, enable secret scanning and push protection under Code security. On private repositories this requires GitHub Advanced Security; on public repositories, alerting is available without it.
  2. Enable the dependency graph and Dependabot alerts so security updates can be generated automatically.
  3. Add .github/dependabot.yml covering every package ecosystem actually used in the repository, including github-actions so your workflow files themselves stay patched.
  4. Add a branch protection rule on your default branch requiring the test suite and, if you are not on GitHub Advanced Security, a CI secret-scan job as required status checks before merge.
  5. Add an auto-merge workflow scoped by if: github.actor == 'dependabot[bot]' with the minimum permissions needed, and exclude major version updates from automatic merging.
  6. Monitor the Security tab regularly. Treat every secret scanning alert as a live incident: rotate the credential at its source immediately, then clean up history if needed.

Common Mistakes

Mistake 1: assuming a deleted secret is a fixed secret. Removing a leaked API key in a follow-up commit does not remove it from git history; anyone who clones the repository, or any cached fork, can still read it from an earlier commit. The correction is to rotate or revoke the credential at the provider first, then treat history cleanup as a separate, optional step; the working credential must stop working regardless of whether history is rewritten.

Mistake 2: over-scoping the auto-merge workflow. A common misconfiguration grants contents: write and pull-requests: write to a workflow that also checks out and runs steps from the pull request’s own branch under pull_request_target. That combination lets a manipulated dependency manifest or lockfile run arbitrary code with write access to the repository. The correction, shown in Example 3, is to use metadata-only actions like dependabot/fetch-metadata that never execute the dependency PR’s code, and to keep the elevated permissions limited to the merge action itself.

Mistake 3: auto-merging every Dependabot PR without gating on checks. Enabling auto-merge only sets intent to merge; if branch protection does not actually require your test suite and security checks as status checks, a broken or malicious transitive dependency can merge straight to main with nothing blocking it. The correction is to confirm those checks are marked required in branch protection settings before relying on auto-merge, and to exclude major version bumps from automation entirely.

Best Practices

  • Rotate and revoke first, clean up history second. A working credential is the actual risk, not its presence in an old commit.
  • Enable push protection organization-wide where available so leaked secrets are blocked before they are ever pushed, not just detected afterward.
  • Pin third-party actions such as gitleaks/gitleaks-action to a commit SHA if you need reproducibility guarantees against a tag being moved; pinning to a maintained major-version tag is a reasonable trade-off when you trust the publisher and want to receive fixes automatically.
  • Keep the permissions block on every security-related workflow as narrow as the job allows; a secret-scan job needs contents: read, nothing more.
  • Separate cadence from urgency: let routine version updates batch weekly, but let security updates merge on their own accelerated path once checks pass.
  • Require review on changes to .github/dependabot.yml and any auto-merge workflow through CODEOWNERS, since both files control what gets into production automatically.
  • Never rely on automation alone for anything that touches deploy credentials or infrastructure code; route those changes through a protected environment with required reviewers even when the change originates from a routine dependency bump.

Practice Exercises

  1. Write a .github/dependabot.yml for a repository that uses both pip and docker, grouping patch-level Python updates into one weekly pull request while leaving Docker base image updates ungrouped.
  2. Modify the secret scan workflow in Example 2 so it also runs on a nightly schedule against the full main branch history, independent of pull request activity, and explain why fetch-depth: 0 matters for that job.
  3. Extend the auto-merge workflow in Example 3 so it posts a comment on the pull request explaining why a major version update was left for manual review, without granting the workflow any additional write permissions beyond what commenting requires.

Summary

Secret scanning and push protection catch leaked credentials before or shortly after they land in history, but only rotation actually closes the exposure. Dependabot version updates keep dependencies current on a schedule you control, while security updates react immediately to newly disclosed vulnerabilities regardless of that schedule. Combining both with a tightly scoped auto-merge workflow, gated by required status checks and excluding major version bumps, lets routine patches flow through the pipeline safely while keeping a human in the loop for anything higher risk.