Skip to content
DevOpsSociety

Two GitHub Changes to Turn On This Week: Secret-Scanning Merge Blocks and Actions Cache-Mode

GitHub can now block pull requests that contain exposed secrets from merging, and Actions workflows can restrict cache access with cache-mode. How each works and a rollout you can finish by Friday.

Published your local timeupdated

Two GitHub Changes to Turn On This Week: Secret-Scanning Merge Blocks and Actions Cache-Mode

Two GitHub releases from this month close gaps most platform teams have been covering with convention and hope. Secret-scanning merge blocks let a ruleset refuse a pull request that introduced an exposed secret. Actions cache-mode lets you decide, per workflow or per job, whether a run can read the cache, write to it, or neither. Both shut a route by which something untrusted in a pull request ends up somewhere trusted.

Here's what each control does according to GitHub's changelog and docs, where the edges are, and a rollout you can finish by Friday. For broader gating policy, see the security hub.

Control

What it stops

Where you configure it

Status and plans

Secret-scanning merge block

A PR merging with an open secret scanning alert it introduced

Ruleset rule "Require secret scanning alerts are resolved" (repo, org or enterprise rulesets)

Public preview; requires GitHub Secret Protection or GitHub Advanced Security

Actions cache-mode

Jobs saving to, or restoring from, the cache when they shouldn't

cache-mode at workflow level, or jobs.<job_id>.cache-mode

Generally available on all plans

Secret-scanning merge blocks: from alert to enforcement

Until now, a secret scanning alert on a pull request was information. Someone might act on it, or the branch might land on main and the credential ship with the next release. The merge-block rule announced on 9 September makes resolving it a condition of merging.

The rule blocks a merge when either:

  1. a commit in the PR introduced an open secret scanning alert matching the secret types selected in the ruleset, or

  2. secret scanning hasn't finished scanning the PR's head commit.

By default it covers provider patterns. You can extend it to custom and generic patterns; AI-detected secrets aren't supported. It's also available through the REST API as the require_secret_scanning_alert_resolution rule type, and through GraphQL, so it can live in your rulesets-as-code.

What a blocked PR looks like

A developer without bypass permission has one way forward: get each blocking alert resolved. Deleting the secret in a follow-up commit doesn't do it. The rule asks whether a commit introduced an open alert, and secret scanning doesn't close alerts automatically when the token disappears. Someone has to close the alert, and then the merge unblocks.

The scan-completion condition also means a brief window after each push where the PR can't merge. Mostly invisible, but worth watching if you run a tightly timed merge queue.

False positives and bypass

A false positive clears the same way as a real leak: the alert is closed with a reason and the block lifts. That's the weak point. The rule checks alert state, not credential state. A developer under deadline pressure who closes a live-token alert as a false positive has satisfied the ruleset and shipped the secret anyway.

So decide who can close alerts before you enable the rule. Our position: the PR author shouldn't close an alert on a provider pattern. Those patterns are precise enough that a second reviewer costs almost nothing.

Bypass uses the standard ruleset bypass list, which can hold admins and owners, write or maintain roles, teams, GitHub Apps and Dependabot. Keep it short. "Repository admins" across a large org means hundreds of people who can wave a live credential through.

Why push protection isn't enough on its own

Push protection stops the secret at git push, before it reaches the repository, and remains the better place to catch it. GitHub positions the merge block as a second layer for cases push protection "isn't able to or configured to catch": a developer who bypassed the push-protection prompt, a pattern you haven't enabled for push protection, a repo where it was never switched on. Run both.

A pull request containing an exposed secret stopped at a merge gate while clean changes continue to the main branch

Cache poisoning, and how cache-mode closes the path

Here's the chain. A repository has a workflow triggered by pull_request_target, perhaps a build that needs repository secrets. Unlike pull_request, that trigger runs in the context of the base branch, usually the default branch. If the workflow checks out and runs anything from the PR, whether an install script, a test or a build step, the attacker's code is executing in a job attached to the default branch.

If that job can write to the cache, the attacker saves an entry under a key a trusted workflow will request: the same npm-<lockfile hash> key your release job uses, say. The next push to main triggers the release workflow, which restores the poisoned entry and runs it with production credentials in scope. Nobody approved anything malicious. The trusted workflow simply trusted its cache.

Cache poisoning: an untrusted workflow fills a cache from outside, and a trusted workflow later opens it inside the secure environment

GitHub's caching docs set the platform default. Only push, workflow_dispatch, repository_dispatch, delete, registry_package, page_build and schedule can create or overwrite caches in the default branch's scope. Other triggers that resolve to the default branch get read-only access, including pull_request_target, issue_comment and workflow_run. Ordinary pull_request runs are scoped to the PR's merge ref and can't write into the default branch's scope at all.

The four modes

The cache-mode release on 10 September makes that access explicit and lets you tighten it. The cache service enforces the mode, so it doesn't depend on which action a step uses.

  • read: restore, no save. Default for low-trust events such as pull_request_target.

  • write: restore and save. Default for trusted events such as push.

  • write-only: save, no restore.

  • none: no cache access.

Job-level settings override the workflow value. On a job that calls a reusable workflow, the mode is a ceiling: the called workflow can't get more cache access than its caller granted. If your platform team ships shared reusable workflows, set the ceiling at the call site.

The trap: opting back in

An explicit cache-mode overrides the read-only default for low-trust events. That's how the hole reopens: someone's pull_request_target build is slow, caches don't save, and they add cache-mode: write. GitHub adds a warning annotation when a low-trust run declares write or write-only. Treat it as a failed review.

The shape we'd standardise on:

on:
  push:
    branches: [main]
  pull_request:

cache-mode: read            # workflow default: restore only, nothing saves

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-${{ hashFiles('**/package-lock.json') }}
      - run: npm ci && npm test

  warm-cache:
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    cache-mode: write-only  # builds from clean and saves; never restores an old entry
    steps:
      - uses: actions/checkout@v6
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: npm-${{ hashFiles('**/package-lock.json') }}
      - run: npm ci

A pull_request_target workflow that only labels or comments should carry cache-mode: none. It's the same thinking you'd apply to scoping IAM roles for production workloads, applied to a resource few teams treated as a permission.

Also this month: npm's recovery-code hold

npm has extended its recovery-code security hold to all accounts. After a recovery-code sign-in, publishing and other sensitive writes, including creating access tokens, pause for 72 hours; signing in and installing still work, and the hold lifts on its own. If releases depend on one maintainer's personal account, a lost second factor now means three days without publishing.

The turn-it-on-this-week sequence

Monday: inventory. Search every repo for pull_request_target, workflow_run and issue_comment triggers, and for any existing cache-mode: write. Each hit on a low-trust trigger gets an owner.

Tuesday: set cache modes. none on low-trust workflows that don't need a cache, read as the default elsewhere, write access for one push-triggered job per cache key, and an explicit mode on every job calling a shared reusable workflow. Watch run times; a job moved to none that relied on a warm cache will slow down, and that's a cost you should choose deliberately.

Wednesday: pilot the merge block. Confirm secret scanning is on for five to ten active repos, then add a ruleset on their default branch with provider patterns only and your security team as the sole bypass. Only alerts a PR introduces count, so the old backlog won't block anyone.

Thursday and Friday: check, then widen. Count blocked PRs, time to unblock, and who closed each alert. If closures look sane, move the rule to an organization ruleset (Team or Enterprise plan) and add custom patterns you trust. Leave generic patterns off until you've measured their false-positive rate on your code.

Then keep two alarms permanently: any warning annotation about write-capable cache modes on low-trust triggers, and any alert closed by the author of the PR that introduced it. The first means the poisoning path is open again. The second means the merge block has become a formality.

Written by

DevOpsSociety Editorial Team

Editorial Team

The DevOpsSociety Editorial Team covers DevOps, cloud infrastructure, Kubernetes, AI infrastructure, platform engineering, cybersecurity, FinOps, and modern engineering practices. We publish practical insights, technical guides, architecture analysis, and research for engineers and technology leaders.

More from DevOpsSociety
The Infrastructure Briefing

Get the infrastructure briefing.

Practical DevOps, cloud, AI infrastructure and engineering insights, delivered weekly. Read by engineers and engineering leaders.

No spam. Unsubscribe anytime.