Skip to content

feat: prefer cimd for worker oauth - #734

Merged
chrisdoc merged 8 commits into
mainfrom
feat/cimd-oauth
Jul 23, 2026
Merged

feat: prefer cimd for worker oauth#734
chrisdoc merged 8 commits into
mainfrom
feat/cimd-oauth

Conversation

@chrisdoc

@chrisdoc chrisdoc commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • advertise Client ID Metadata Documents as the preferred Worker OAuth client-identification path
  • retain Dynamic Client Registration as the compatibility fallback
  • enable Cloudflare strict-public fetch protection in production
  • add CIMD authorization, PKCE, resource, MCP, and rejection coverage

Verification

  • npx vitest run packages/worker/src/worker-oauth.test.ts
  • npm run test:unit
  • npm run test:worker-http
  • npm run check:types
  • npm run build
  • npm run worker:dry-run
  • npm run check
  • npm run check:changeset
  • live discovery smoke test confirmed CIMD support, DCR registration, none token auth, and S256 PKCE

Summary by CodeRabbit

  • New Features
    • Added support for OAuth Client ID Metadata Documents (CIMD), with Dynamic Client Registration retained as a fallback.
    • OAuth discovery metadata now reports CIMD support, and the CIMD authorization flow is enabled (including authorization-code + PKCE).
  • Bug Fixes
    • Improved validation to reject CIMD when client metadata doesn’t match or redirect URIs are missing.
    • Ensures CIMD-related credentials are not persisted to KV during the flow.
  • Tests
    • Expanded OAuth test coverage, including CIMD-specific success and rejection scenarios.
  • Chores / Automation
    • Updated preview deployment workflow to better manage the preview URL comment.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@chrisdoc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 17 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f23b5ba-900f-4863-8f79-f9a6851809fb

📥 Commits

Reviewing files that changed from the base of the PR and between 9ed59aa and a935e4c.

📒 Files selected for processing (4)
  • .github/workflows/deploy-worker.yml
  • CONTRIBUTING.md
  • wrangler.jsonc
  • wrangler.preview.jsonc
📝 Walkthrough

Walkthrough

Worker OAuth now enables Client ID Metadata Documents with Dynamic Client Registration fallback. Tests cover discovery metadata, the complete CIMD authorization flow, MCP access, and rejection of invalid metadata without persisted OAuth records. The preview workflow also creates or updates deployment comments on pull requests.

Changes

CIMD OAuth support

Layer / File(s) Summary
Enable CIMD provider support
.changeset/calm-cimd-auth.md, .changeset/sixty-hats-raise.md, wrangler.jsonc, packages/worker/src/worker-oauth.ts
The worker enables the public global-fetch compatibility flag, enables CIMD metadata generation, updates OAuth documentation, and adds changeset entries.
Update OAuth test environment
packages/worker/src/worker-oauth.test.ts
Tests stub and restore the Cloudflare compatibility global and assert that discovery metadata advertises CIMD support.
Validate CIMD authorization flow
packages/worker/src/worker-oauth.test.ts
Tests metadata retrieval, authorization-code and PKCE exchange, MCP access, and rejection of invalid CIMD metadata without persisted OAuth keys.

Worker preview comments

Layer / File(s) Summary
Publish preview details
.github/workflows/deploy-worker.yml
The preview job gains pull-request write permission and updates or creates a bot-authored comment containing the preview endpoint, alias, and commit.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OAuthClient
  participant WorkerOAuth
  participant ClientMetadataURL
  participant OAuthKV
  participant MCP
  OAuthClient->>WorkerOAuth: Start authorize request
  WorkerOAuth->>ClientMetadataURL: Fetch client metadata
  ClientMetadataURL-->>WorkerOAuth: Return client metadata
  OAuthClient->>WorkerOAuth: Approve and exchange code with PKCE
  WorkerOAuth-->>OAuthClient: Return tokens
  OAuthClient->>MCP: Send authenticated request
  MCP-->>OAuthClient: Return MCP response
Loading

Possibly related PRs

Suggested labels: review-pending

Poem

A rabbit found metadata bright,
And OAuth hopped through the night.
PKCE codes danced,
Preview comments advanced,
While invalid clients took flight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: preferring CIMD for Worker OAuth.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cimd-oauth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

MCP tool token cost

Measured with o200k_base over the complete json-serialized mcp tools/list result payload: { tools }.
Targets are advisory and never fail CI.

Metric Current Target Status
Tools 25 ≤ 20 Above target
Total tokens 12973
Average tokens/tool 518.92 < 600 Within target

Change from baseline

Metric Baseline Current Delta
Tools 25 25 0
Total tokens 12973 12973 0
Average tokens/tool 518.92 518.92 0

Per-tool changes

Tool Baseline Current Delta
create-body-measurement 669 669 0
create-exercise-template 421 421 0
create-routine 564 564 0
create-routine-folder 166 166 0
create-workout 655 655 0
get-body-measurement 628 628 0
get-body-measurements 680 680 0
get-exercise-history 808 808 0
get-exercise-template 276 276 0
get-exercise-templates 359 359 0
get-routine 552 552 0
get-routine-folder 245 245 0
get-routine-folders 321 321 0
get-routines 637 637 0
get-training-summary 923 923 0
get-user-info 244 244 0
get-workout 522 522 0
get-workout-count 195 195 0
get-workout-events 721 721 0
get-workouts 591 591 0
search-exercise-templates 426 426 0
search-routines 465 465 0
update-body-measurement 667 667 0
update-routine 562 562 0
update-workout 672 672 0

Per-tool breakdown

Tool Tokens Share of total
get-training-summary 923 7.11%
get-exercise-history 808 6.23%
get-workout-events 721 5.56%
get-body-measurements 680 5.24%
update-workout 672 5.18%
create-body-measurement 669 5.16%
update-body-measurement 667 5.14%
create-workout 655 5.05%
get-routines 637 4.91%
get-body-measurement 628 4.84%
get-workouts 591 4.56%
create-routine 564 4.35%
update-routine 562 4.33%
get-routine 552 4.25%
get-workout 522 4.02%
search-routines 465 3.58%
search-exercise-templates 426 3.28%
create-exercise-template 421 3.25%
get-exercise-templates 359 2.77%
get-routine-folders 321 2.47%
get-exercise-template 276 2.13%
get-routine-folder 245 1.89%
get-user-info 244 1.88%
get-workout-count 195 1.5%
create-routine-folder 166 1.28%

Per-tool counts encode each complete tool object independently. The total encodes the complete { tools } envelope, so punctuation and separators mean the per-tool values need not sum exactly to the total.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bundle Report

Bundle size has no change ✅

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Prefer CIMD for Worker OAuth with DCR fallback

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Prefer CIMD client metadata for Worker OAuth, retaining Dynamic Client Registration fallback.
• Enable Cloudflare global_fetch_strictly_public compatibility flag for stricter production fetch.
• Add end-to-end CIMD OAuth + MCP tests, including rejection cases and discovery assertions.
Diagram

graph TD
  A{{"OAuth client"}} --> B["Worker OAuth provider"] --> D["MCP handler"] --> E{{"Hevy API"}}
  B --> C[(OAUTH_KV)]
  B --> F{{"CIMD document"}}
  B --> G["DCR registration"]
  subgraph Legend
    direction LR
    _ext{{External}} ~~~ _svc["Service"] ~~~ _kv[(Storage)]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. DCR-only client identification
  • ➕ No dependency on fetching external CIMD documents
  • ➕ Single consistent onboarding path for all clients
  • ➖ Extra KV writes/maintenance for registered clients
  • ➖ Incompatible with clients that prefer/expect CIMD
  • ➖ Slower/fragile if registration fails for transient reasons
2. CIMD-only client identification
  • ➕ Simpler state model (no registered-client persistence)
  • ➕ Aligns with modern OAuth client identification flows
  • ➖ Breaks older clients that only support DCR
  • ➖ Availability/security tied to fetching the client_id URL at runtime
3. Make CIMD vs DCR preference configurable (env/flag + allowlist)
  • ➕ Operational control for rollout and incident mitigation
  • ➕ Can restrict CIMD fetches to trusted origins
  • ➖ More configuration surface and documentation burden
  • ➖ Harder to reason about behavior across environments

Recommendation: The chosen approach (prefer CIMD, fall back to DCR) is the best default for broad interoperability: it supports modern clients (e.g., ChatGPT) without regressing older clients, while avoiding unnecessary KV client records when CIMD works. If future security posture requires it, add an allowlist for CIMD document origins or a rollout flag, but the current change is a pragmatic baseline.

Files changed (4) +174 / -3

Enhancement (1) +3 / -2
worker-oauth.tsEnable CIMD client identification in OAuth provider configuration +3/-2

Enable CIMD client identification in OAuth provider configuration

• Updates provider documentation and enables CIMD support via OAuthProvider configuration (clientIdMetadataDocumentEnabled), keeping DCR as fallback behavior provided by the upstream library.

packages/worker/src/worker-oauth.ts

Tests (1) +165 / -1
worker-oauth.test.tsAdd CIMD end-to-end OAuth flow coverage and strict-public fetch stubs +165/-1

Add CIMD end-to-end OAuth flow coverage and strict-public fetch stubs

• Stubs Cloudflare compatibility flags in tests and asserts discovery metadata advertises CIMD support. Adds an end-to-end CIMD authorization-code+PKCE flow test that exercises token exchange and MCP request authorization, plus parameterized rejection tests for invalid CIMD metadata.

packages/worker/src/worker-oauth.test.ts

Documentation (1) +5 / -0
calm-cimd-auth.mdAdd changeset describing CIMD-first Worker OAuth behavior +5/-0

Add changeset describing CIMD-first Worker OAuth behavior

• Introduces a patch changeset for hevy-mcp documenting that Worker OAuth now prefers Client ID Metadata Documents with DCR fallback.

.changeset/calm-cimd-auth.md

Other (1) +1 / -0
wrangler.jsoncEnable global_fetch_strictly_public compatibility flag +1/-0

Enable global_fetch_strictly_public compatibility flag

• Turns on Cloudflare's strict-public fetch protection via Wrangler compatibility_flags for production deployments.

wrangler.jsonc

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.50%. Comparing base (6ea2a7a) to head (a935e4c).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #734      +/-   ##
==========================================
+ Coverage   85.46%   85.50%   +0.03%     
==========================================
  Files          52       52              
  Lines        2573     2573              
  Branches      725      725              
==========================================
+ Hits         2199     2200       +1     
+ Misses        203      202       -1     
  Partials      171      171              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Unit Test Results

  1 files   53 suites   4s ⏱️
629 tests 629 ✅ 0 💤 0 ❌
633 runs  633 ✅ 0 💤 0 ❌

Results for commit a935e4c.

♻️ This comment has been updated with latest results.

@mergify

mergify Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 74 rules

Grey Divider


Remediation recommended

1. Uncaught client lookup errors 🐞 Bug ☼ Reliability
Description
With CIMD enabled, client resolution during /authorize now involves outbound metadata fetches, but
handleAuthorizeGet/handleAuthorizePost call helpers.lookupClient() without any error handling. If
lookupClient rejects (e.g., metadata fetch/KV failure), the exception propagates into
createWorkerFetchHandler which rethrows, resulting in an uncontrolled Worker error instead of a
deterministic OAuth failure response.
Code

packages/worker/src/worker-oauth.ts[448]

+		clientIdMetadataDocumentEnabled: true,
Relevance

⭐⭐⭐ High

Repo recently accepted wrapping OAuth helper calls in try/catch to avoid uncontrolled Worker 500s;
lookupClient is analogous.

PR-#682

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR enables CIMD, and the repo’s CIMD test proves authorization now triggers an outbound fetch.
The authorize handlers then call lookupClient without try/catch, and the Worker-level fetch wrapper
rethrows errors it catches—so any lookupClient rejection can escape as an uncaught runtime error.

packages/worker/src/worker-oauth.ts[254-308]
packages/worker/src/worker.ts[463-466]
packages/worker/src/worker-oauth.test.ts[817-929]
PR-#682

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Enabling CIMD (`clientIdMetadataDocumentEnabled: true`) makes client resolution depend on outbound fetches during authorization. `handleAuthorizeGet` and `handleAuthorizePost` currently `await helpers.lookupClient(...)` without a `try/catch`, so any rejection can escape the handler and become an uncaught Worker error.

## Issue Context
- `createWorkerFetchHandler` logs and then rethrows caught errors, so uncaught exceptions from the OAuth layer are not converted into a safe HTTP response.
- CIMD tests stub `globalThis.fetch` and assert it is called during `/authorize`, demonstrating that authorization now depends on outbound fetch.

## Fix Focus Areas
- packages/worker/src/worker-oauth.ts[254-308]
- packages/worker/src/worker.ts[463-466]
- packages/worker/src/worker-oauth.test.ts[817-929]

## Implementation notes
- Wrap `helpers.lookupClient(authRequest.clientId)` in both `handleAuthorizeGet` and `handleAuthorizePost` with `try/catch`.
- On failure:
 - For GET: return a deterministic error page (ideally a 502-style message via `authorizeErrorResponse(...)` rather than a generic crash).
 - For POST: re-render the consent page with a generic error (or return `authorizeErrorResponse(..., 502)`), consistent with other transient failures.
- Optionally log a sanitized diagnostic (similar to the existing `oauth-complete-authorization` logging) so operational failures are observable without leaking sensitive details.
- Add/extend a test that stubs `fetch` to reject during CIMD authorization and asserts the response is controlled (not an unhandled exception).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

authorizeEndpoint: AUTHORIZE_PATH,
tokenEndpoint: TOKEN_PATH,
clientRegistrationEndpoint: REGISTER_PATH,
clientIdMetadataDocumentEnabled: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Uncaught client lookup errors 🐞 Bug ☼ Reliability

With CIMD enabled, client resolution during /authorize now involves outbound metadata fetches, but
handleAuthorizeGet/handleAuthorizePost call helpers.lookupClient() without any error handling. If
lookupClient rejects (e.g., metadata fetch/KV failure), the exception propagates into
createWorkerFetchHandler which rethrows, resulting in an uncontrolled Worker error instead of a
deterministic OAuth failure response.
Agent Prompt
## Issue description
Enabling CIMD (`clientIdMetadataDocumentEnabled: true`) makes client resolution depend on outbound fetches during authorization. `handleAuthorizeGet` and `handleAuthorizePost` currently `await helpers.lookupClient(...)` without a `try/catch`, so any rejection can escape the handler and become an uncaught Worker error.

## Issue Context
- `createWorkerFetchHandler` logs and then rethrows caught errors, so uncaught exceptions from the OAuth layer are not converted into a safe HTTP response.
- CIMD tests stub `globalThis.fetch` and assert it is called during `/authorize`, demonstrating that authorization now depends on outbound fetch.

## Fix Focus Areas
- packages/worker/src/worker-oauth.ts[254-308]
- packages/worker/src/worker.ts[463-466]
- packages/worker/src/worker-oauth.test.ts[817-929]

## Implementation notes
- Wrap `helpers.lookupClient(authRequest.clientId)` in both `handleAuthorizeGet` and `handleAuthorizePost` with `try/catch`.
- On failure:
  - For GET: return a deterministic error page (ideally a 502-style message via `authorizeErrorResponse(...)` rather than a generic crash).
  - For POST: re-render the consent page with a generic error (or return `authorizeErrorResponse(..., 502)`), consistent with other transient failures.
- Optionally log a sanitized diagnostic (similar to the existing `oauth-complete-authorization` logging) so operational failures are observable without leaking sensitive details.
- Add/extend a test that stubs `fetch` to reject during CIMD authorization and asserts the response is controlled (not an unhandled exception).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Worker preview

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.changeset/sixty-hats-raise.md (1)

1-3: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add release metadata for the runtime OAuth feature.

This changeset is empty, but CIMD support is a user-facing runtime change. Replace it with the affected package name and an appropriate patch, minor, or major bump.

As per coding guidelines: “user-facing runtime changes require a patch/minor/major changeset.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/sixty-hats-raise.md around lines 1 - 3, Replace the empty
changeset front matter in sixty-hats-raise.md with release metadata for the
package affected by the runtime OAuth/CIMD feature, selecting the appropriate
patch, minor, or major bump and adding a concise user-facing summary.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy-worker.yml:
- Around line 27-29: Update the workflow’s preview job permissions to retain
only contents read access, then move the PR comment step into a separate job
that depends on preview via needs and grants only pull-requests write
permission. Preserve the existing deployment steps in preview and ensure the new
job receives the outputs or context required by the comment step.

---

Outside diff comments:
In @.changeset/sixty-hats-raise.md:
- Around line 1-3: Replace the empty changeset front matter in
sixty-hats-raise.md with release metadata for the package affected by the
runtime OAuth/CIMD feature, selecting the appropriate patch, minor, or major
bump and adding a concise user-facing summary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 24b08352-a1d6-4cde-9bd0-336f6aa50de4

📥 Commits

Reviewing files that changed from the base of the PR and between c7c0abc and 9ed59aa.

📒 Files selected for processing (2)
  • .changeset/sixty-hats-raise.md
  • .github/workflows/deploy-worker.yml

Comment on lines +27 to +29
permissions:
contents: read
pull-requests: write

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Repository files matching deploy-worker:\n'
fd -a 'deploy-worker\.yml|deploy-worker\.yaml' . || true

printf '\nWorkflow file outline/contents length:\n'
if [ -f .github/workflows/deploy-worker.yml ]; then
  wc -l .github/workflows/deploy-worker.yml
  sed -n '1,260p' .github/workflows/deploy-worker.yml | cat -n
fi

printf '\nChangeset references:\n'
git ls-files .changeset 2>/dev/null || true
fd -a .changeset .github . 2>/dev/null | sed -n '1,100p'
if [ -d .changeset ]; then
  git ls-files .changeset | sed -n '1,100p'
  for f in .changeset/*; do
    [ -f "$f" ] && { echo "--- $f"; cat "$f"; echo; }
  done
fi

printf '\nRelated workflow permissions usages:\n'
rg -n "pull-requests:\s*(write|read)|deploy-worker|on:\s*$" .github/workflows || true

Repository: chrisdoc/hevy-mcp

Length of output: 16012


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

p = Path(".github/workflows/deploy-worker.yml")
if not p.exists():
    print("missing .github/workflows/deploy-worker.yml")
    raise SystemExit

text = p.read_text()
lines = text.splitlines()

# Light YAML-like extraction for top-level jobs and permissions inside first job context.
in_jobs = False
current_job = None
for i, line in enumerate(lines, 1):
    if line.startswith("jobs:"):
        in_jobs = True
        continue
    if in_jobs and re.match(r"^\S.*:\s*$", line):
        current_job = line.rstrip(":").strip()
        print(f"job {current_job} starts line {i}")
    if line.startswith("permissions:"):
        print(f"permissions block at line {i} in job {current_job}")
        # read next non-comment and non-empty indented blocks until next top job/section.
        start = i
        block_lines = []
        for j in range(i, min(len(lines), i+20)):
            if re.match(r"^\w+\s*:", lines[j]) or (j > i and re.search(r"\bpermissions:\s*$", lines[j-1]) is None and re.match(r"^[a-zA-Z_][\w-]*[:!?-]?\\w*:?$", lines[j])):
                break
            block_lines.append(lines[j])
        print("\n".join(f"{k}: {block_lines[k-i]}" for k in range(start, min(len(lines), start+20))) if block_lines else "")

print("\nRelevant lines around permissions:")
for i in range(max(1,20), min(len(lines), 45)+1):
    if "pull-requests" in lines[i-1] or "permissions" in lines[i-1] or "pull_request" in lines[i-1]:
        print(f"{i}: {lines[i-1]}")

print("\nSequential dependency/comment deployment order:")
for i, line in enumerate(lines):
    if any(tok in line for tok in ["needs:", "uses:", "run:", "pull-requests:", "pull_request_comment", "issue_comment", "actions/github-script", "azure-cloud-sdk", "azure-functions", "`@cloudflare/actions-deploy`"]):
        print(f"{i+1}: {line}")
PY

Repository: chrisdoc/hevy-mcp

Length of output: 346


🌐 Web query:

GitHub Actions permissions pull-requests write PR-based workflow job-scoped permissions security actions using pull_request checkout npm ci

💡 Result:

To secure GitHub Actions workflows involving pull requests, you must apply the principle of least privilege, especially when using triggers that grant elevated access [1][2][3]. Security Best Practices 1. Default to read-only: Set all repository and job-level GITHUB_TOKEN permissions to read-only [1][2][4]. Only explicitly grant 'write' permissions to individual jobs that require them [1][3]. 2. Use pull_request, not pull_request_target: Prefer the pull_request trigger, which is inherently safer because it runs with restricted permissions (no secrets access, read-only token) for forks [2][5]. 3. Secure pull_request_target: Only use pull_request_target when you require access to repository secrets or a write-enabled token [5]. Because this trigger runs in the context of the base branch, it is highly sensitive [6][7]. Never check out and execute code from an untrusted PR head while using this trigger unless you have carefully mitigated the risks [6][8][9]. 4. Protect against pwn requests: The actions/checkout action (v7+) now blocks common pwn request patterns—such as checking out untrusted fork code during a pull_request_target or workflow_run—by default [8][10][9]. Avoid using the allow-unsafe-pr-checkout: true input unless absolutely necessary [6][10]. Permissions Configuration Example For jobs requiring interaction with PRs or npm, scope permissions narrowly at the job level [3][11]: jobs: job-name: permissions: contents: read pull-requests: write # Only if needed to comment on PRs steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 - run: npm ci When using npm ci, the setup-node action generally only requires contents: read to check out code and install dependencies [12]. If you are performing operations like publishing or updating dependencies that require writing to the repository, grant write access only to the specific job performing those tasks [3][11]. Always prefer OIDC for sensitive authentication (e.g., to npm registries) rather than long-lived secrets [4][11].

Citations:


Isolate PR-write access from the deployment job.

pull-requests: write is set on the preview job, so every earlier step in that job—including actions/checkout, npm ci, and the Cloudflare deployment steps—runs with a write-enabled PR token. Move the PR comment step into a separate needs job that owns only pull-requests: write, and keep this job read-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy-worker.yml around lines 27 - 29, Update the
workflow’s preview job permissions to retain only contents read access, then
move the PR comment step into a separate job that depends on preview via needs
and grants only pull-requests write permission. Preserve the existing deployment
steps in preview and ensure the new job receives the outputs or context required
by the comment step.

Source: MCP tools

@chrisdoc
chrisdoc merged commit ed44ec0 into main Jul 23, 2026
23 checks passed
@chrisdoc
chrisdoc deleted the feat/cimd-oauth branch July 23, 2026 19:24
@github-actions github-actions Bot mentioned this pull request Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant