Skip to content

Implement Client Secret Expiry and Renewal for Dynamic Client Registration#5377

Open
Sanskarzz wants to merge 8 commits into
stacklok:mainfrom
Sanskarzz:autosecretDCR
Open

Implement Client Secret Expiry and Renewal for Dynamic Client Registration#5377
Sanskarzz wants to merge 8 commits into
stacklok:mainfrom
Sanskarzz:autosecretDCR

Conversation

@Sanskarzz
Copy link
Copy Markdown
Contributor

Summary

ToolHive already persisted DCR client_id and client_secret, but did not preserve the RFC 7591 / RFC 7592 metadata needed to renew expiring client secrets. Long-running remote-auth workloads could therefore keep using cached credentials until an upstream such as Keycloak rejected token refreshes after the DCR client secret expired.

This PR adds the missing DCR client-secret lifecycle:

  • Persists client_secret_expires_at, registration_access_token, registration_client_uri, token endpoint auth method, and the callback port actually registered during DCR.
  • Checks cached client-secret expiry before token restore and when resolving cached DCR credentials.
  • Renews expiring secrets with RFC 7592 client update (PUT registration_client_uri) using the stored registration access token.
  • Handles renewal failures softly while the existing secret is still valid, and hard-fails when the secret is already expired.
  • Reuses existing secret-manager keys for rotated client secrets and registration access tokens so renewals do not accumulate stale secrets.

This also addresses the previous PR review feedback:

  • Fixed stale DCR resolver metadata threading and removed duplicate metadata assignment.
  • Prevented double-renewal by updating in-memory cached expiry after successful renewal.
  • Replaced successful/diagnostic slog.Info calls with slog.Debug.
  • Used oauthproto.ToolHiveMCPClientName and the actual registered callback port in the RFC 7592 update body.
  • Removed unnecessary []byte -> string -> reader allocation.
  • Removed the invalid gosec suppression on the Authorization header.
  • Reused an injectable HTTP client and drained response bodies on error paths.
  • Tightened registration_client_uri validation to reject root-path URLs.
  • Replaced hand-written test mocks with generated gomock mocks.
  • Moved in-memory config mutation after durable SaveState in DCR credential persistence.

Fixes #3631

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation
  • Other (describe):

Test plan

  • Unit tests (task test)
  • E2E tests (task test-e2e)
  • Linting (task lint-fix)
  • Manual testing (describe below)

Targeted package tests run:

go test ./pkg/auth/remote ./pkg/auth/discovery ./pkg/auth/oauth ./pkg/runner ./pkg/oauthproto ./pkg/auth/dcr

Result:

ok  	github.com/stacklok/toolhive/pkg/auth/remote
ok  	github.com/stacklok/toolhive/pkg/auth/discovery
ok  	github.com/stacklok/toolhive/pkg/auth/oauth
ok  	github.com/stacklok/toolhive/pkg/runner
ok  	github.com/stacklok/toolhive/pkg/oauthproto
ok  	github.com/stacklok/toolhive/pkg/auth/dcr

Targeted lint command run:

golangci-lint run ./pkg/auth/remote ./pkg/auth/discovery ./pkg/runner

Result:

0 issues.

Manual Keycloak validation is documented in manual-testing-ratelimit-vmcp.md and was used to validate the full renewal flow locally:

  • Created a kind cluster and deployed the local ToolHive operator.
  • Deployed Keycloak and patched its development hostname to localhost for browser-based OAuth.
  • Created a confidential DCR client through Keycloak's OpenID Connect client registration endpoint.
  • Assigned the mcp-server-audience default client scope to the DCR client.
  • Completed an initial ToolHive remote-auth OAuth login against the authenticated MCP server.
  • Seeded cached DCR metadata in the saved run config, including cached_reg_token_ref, cached_reg_client_uri, cached_token_auth_method, and cached_dcr_callback_port.
  • Forced cached_secret_expiry into the renewal window and verified startup performed exactly one RFC 7592 renewal before restoring cached OAuth tokens.
  • Verified the same ToolHive secret references remained in the run config after renewal.
  • Verified Keycloak's client_secret_expires_at: 0 response clears cached_secret_expiry back to the zero time value.

API Compatibility

  • This PR does not break the v1beta1 API, OR the api-break-allowed label is applied and the migration guidance is described above.

Changes

File Change
pkg/auth/discovery/discovery.go Threads DCR renewal metadata from resolver result into OAuthFlowResult, including the registered callback port.
pkg/auth/remote/config.go Adds cached registered DCR callback port and clears it with cached DCR credentials.
pkg/auth/remote/handler.go Checks expiry before cached credential use, uses DEBUG diagnostics, and supports HTTP client injection.
pkg/auth/remote/secret_renewal.go Implements RFC 7592 renewal with stricter URI validation, reusable HTTP client, response draining, and in-memory state refresh.
pkg/runner/runner.go Persists DCR renewal metadata, reuses existing secret keys, and saves durable state before memory mutation.
pkg/auth/remote/secret_renewal_test.go Covers expiry checks, renewal success/failure, URI validation, callback port preservation, and double-renewal prevention with gomock.

Does this introduce a user-facing change?

Yes. Remote-auth workloads that use DCR can now continue operating when an OAuth provider issues expiring client secrets, provided the provider also returns RFC 7592 registration management metadata.

Providers that return client_secret_expires_at: 0 or omit RFC 7592 metadata remain backward compatible: ToolHive treats the secret as non-expiring or logs a warning and falls back to the existing secret until re-authentication is required.

Special notes for reviewers

  • The ClientCredentialsPersister callback is internal to this repository and now carries DCR renewal metadata plus the registered callback port.
  • Registration access tokens and client secrets are stored through the secret manager. Renewal reuses existing secret names where present to avoid stale secret accumulation.
  • registration_client_uri is stored in config because it is endpoint metadata, not a secret.

Manual Testing: DCR Client Secret Expiry and RFC 7592 Renewal

This file is intentionally named manual-testing-ratelimit-vmcp.md because that was the requested path. The steps below validate the autosecretDCR branch, not vMCP rate limiting.

What This Validates

  • DCR metadata is persisted:
    • cached_secret_expiry
    • cached_reg_token_ref
    • cached_reg_client_uri
    • cached_token_auth_method
    • cached_dcr_callback_port
  • Cached DCR credentials are checked before token restore.
  • A cached secret inside the 24 hour renewal buffer triggers one RFC 7592 PUT.
  • Keycloak registration access token rotation is persisted back into the same ToolHive secret reference.
  • The stored callback port is echoed in the renewal request.
  • Renewal failures are soft while the secret is only expiring soon and hard when already expired.

Reference: Keycloak documents its client registration endpoint as /realms/<realm>/clients-registrations/<provider> and the OpenID Connect DCR provider as /realms/<realm>/clients-registrations/openid-connect[/<client id>]: https://www.keycloak.org/securing-apps/client-registration

Prerequisites

  • docker or podman
  • kind
  • kubectl
  • helm
  • ko
  • task
  • jq
  • a browser on the host machine

Use separate terminals for long-running port-forward and thv run commands.

1. Create a Kind Cluster and Deploy ToolHive Operator

task kind-setup
task operator-install-crds
task operator-deploy-local

Wait for the operator:

kubectl -n toolhive-system get pods --kubeconfig kconfig.yaml

Expected result:

  • The ToolHive operator pod is Running.

2. Deploy Keycloak

task keycloak:deploy-dev

For this local browser-based OAuth flow, make Keycloak advertise localhost instead of the in-cluster keycloak hostname. Without this, Keycloak redirects the browser to keycloak:<port> and can lose its restart-login cookie.

kubectl -n keycloak patch keycloak keycloak-dev \
  --type merge \
  -p '{"spec":{"hostname":{"hostname":"localhost"}}}' \
  --kubeconfig kconfig.yaml

kubectl -n keycloak wait --for=condition=Ready --timeout=600s keycloaks.k8s.keycloak.org/keycloak-dev --kubeconfig kconfig.yaml

If the hostname patch restarts Keycloak, the development H2 realm data can be reset. Re-run the realm setup once after the patched Keycloak is ready. The setup script currently expects Keycloak on local port 8080, so start a temporary 8080 port-forward in another terminal while this script runs:

kubectl -n keycloak port-forward \
  --address 127.0.0.1 \
  service/keycloak-dev-service \
  8080:8080 \
  --kubeconfig kconfig.yaml

Then run:

deploy/keycloak/setup-realm.sh

Start a persistent Keycloak port-forward in a separate terminal and keep it running.
The example uses local port 8081 because 8080 is often already occupied:

kubectl -n keycloak port-forward \
  --address 127.0.0.1 \
  service/keycloak-dev-service \
  8081:8080 \
  --kubeconfig kconfig.yaml

In another terminal, fetch an admin token. Keycloak admin tokens are short-lived, so re-run this block whenever a later admin API command returns 401 or an empty response:

KC_PORT=8081
KC="http://localhost:${KC_PORT}"
test -n "$KC"
curl -fsS "$KC/realms/master/.well-known/openid-configuration" >/dev/null

ADMIN_USER="$(kubectl get secret keycloak-dev-initial-admin -n keycloak -o jsonpath='{.data.username}' --kubeconfig kconfig.yaml | base64 --decode)"
ADMIN_PASS="$(kubectl get secret keycloak-dev-initial-admin -n keycloak -o jsonpath='{.data.password}' --kubeconfig kconfig.yaml | base64 --decode)"
ADMIN_TOKEN="$(curl -s \
  -d client_id=admin-cli \
  -d username="$ADMIN_USER" \
  -d password="$ADMIN_PASS" \
  -d grant_type=password \
  "$KC/realms/master/protocol/openid-connect/token" | jq -r '.access_token')"

test "$ADMIN_TOKEN" != "null" -a -n "$ADMIN_TOKEN"

Expected result:

  • ADMIN_TOKEN is non-empty.
  • Use localhost consistently for Keycloak admin, DCR, and ToolHive OAuth calls. Mixing localhost and keycloak in browser flows can lose Keycloak's restart-login cookie.

3. Deploy an Authenticated Remote MCP Server

kubectl apply -f deploy/keycloak/mcpserver-with-auth.yaml --kubeconfig kconfig.yaml
kubectl -n toolhive-system patch mcpoidcconfig keycloak-oidc \
  --type merge \
  -p '{"spec":{"inline":{"issuer":"http://localhost:8081/realms/toolhive"}}}' \
  --kubeconfig kconfig.yaml
kubectl -n toolhive-system wait --for=condition=Available deployment/fetch-server-keycloak --timeout=180s --kubeconfig kconfig.yaml

Start the MCP server port-forward in a separate terminal:

kubectl -n toolhive-system port-forward svc/mcp-fetch-server-keycloak-proxy 9090:9090 --kubeconfig kconfig.yaml

Expected result:

  • The authenticated MCP proxy is reachable on http://localhost:9090.
  • Both Keycloak and MCP port-forwards stay running while you execute the remaining commands from a third terminal.

4. Create a Confidential DCR Client in Keycloak

ToolHive's normal CLI DCR path registers a public PKCE client. To exercise client-secret renewal with Keycloak, seed ToolHive with a confidential DCR client created through Keycloak's OpenID Connect client registration endpoint.

Create a Keycloak client-registration initial access token:

KC_PORT="${KC_PORT:-8081}"
KC="${KC:-http://localhost:${KC_PORT}}"
test -n "$KC"
curl -fsS "$KC/realms/master/.well-known/openid-configuration" >/dev/null

ADMIN_TOKEN="$(curl -s \
  -d client_id=admin-cli \
  -d username="$ADMIN_USER" \
  -d password="$ADMIN_PASS" \
  -d grant_type=password \
  "$KC/realms/master/protocol/openid-connect/token" | jq -r '.access_token')"

IAT_RESPONSE="$(curl -fsS -X POST \
  "$KC/admin/realms/toolhive/clients-initial-access" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"count": 1, "expiration": 3600}')"

INITIAL_ACCESS_TOKEN="$(echo "$IAT_RESPONSE" | jq -r '.token')"
test "$INITIAL_ACCESS_TOKEN" != "null" -a -n "$INITIAL_ACCESS_TOKEN"

Use that initial access token for DCR:

DCR_RESPONSE="$(curl -fsS -X POST \
  "$KC/realms/toolhive/clients-registrations/openid-connect" \
  -H "Authorization: Bearer $INITIAL_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "ToolHive MCP Client",
    "redirect_uris": ["http://localhost:8666/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "client_secret_basic"
  }')"

echo "$DCR_RESPONSE" | jq .

CLIENT_ID="$(echo "$DCR_RESPONSE" | jq -r '.client_id')"
CLIENT_SECRET="$(echo "$DCR_RESPONSE" | jq -r '.client_secret')"
REG_TOKEN="$(echo "$DCR_RESPONSE" | jq -r '.registration_access_token')"
REG_URI="$(echo "$DCR_RESPONSE" | jq -r '.registration_client_uri')"

test "$CLIENT_ID" != "null" -a -n "$CLIENT_ID"
test "$CLIENT_SECRET" != "null" -a -n "$CLIENT_SECRET"
test "$REG_TOKEN" != "null" -a -n "$REG_TOKEN"
test "$REG_URI" != "null" -a -n "$REG_URI"

Expected result:

  • Keycloak returns client_id, client_secret, registration_access_token, and registration_client_uri.
  • registration_client_uri uses http://localhost:8081/.... If it still uses http://keycloak:8081/..., Keycloak is still advertising the old hostname; recheck the hostname patch above before continuing.

If this returns 403, rerun without -f to see Keycloak's error body:

curl -i -sS -X POST \
  "$KC/realms/toolhive/clients-registrations/openid-connect" \
  -H "Authorization: Bearer $INITIAL_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "ToolHive MCP Client",
    "redirect_uris": ["http://localhost:8666/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "token_endpoint_auth_method": "client_secret_basic"
  }'

5. Give the DCR Client the MCP Server Audience

The sample mcpserver-with-auth.yaml requires the mcp-server audience. Assign the existing mcp-server-audience client scope to the DCR client.

ADMIN_TOKEN="$(curl -s \
  -d client_id=admin-cli \
  -d username="$ADMIN_USER" \
  -d password="$ADMIN_PASS" \
  -d grant_type=password \
  "$KC/realms/master/protocol/openid-connect/token" | jq -r '.access_token')"

CLIENT_UUID="$(curl -s \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  "$KC/admin/realms/toolhive/clients?clientId=$CLIENT_ID" | jq -r '.[0].id')"

SCOPE_ID="$(curl -s \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  "$KC/admin/realms/toolhive/client-scopes" | jq -r '.[] | select(.name=="mcp-server-audience") | .id')"

curl -s -X PUT \
  "$KC/admin/realms/toolhive/clients/$CLIENT_UUID/default-client-scopes/$SCOPE_ID" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Expected result:

  • The command exits successfully.
  • A later 401 Unauthorized from this admin endpoint means ADMIN_TOKEN expired; rerun this step's first command to refresh it.

6. Configure ToolHive Secrets

Use the encrypted provider for writable local secrets.

bin/thv secret provider encrypted

printf '%s' "$CLIENT_SECRET" | bin/thv secret set OAUTH_CLIENT_SECRET_dcr_manual
printf '%s' "$REG_TOKEN" | bin/thv secret set OAUTH_REG_TOKEN_dcr_manual

Expected result:

  • Both secrets are stored.

7. Create an Initial OAuth Session

Run a local ToolHive proxy against the authenticated remote MCP server.

TOOLHIVE_DEBUG=true bin/thv run http://localhost:9090/mcp \
  --name dcr-renew-keycloak \
  --transport streamable-http \
  --remote-auth \
  --remote-auth-authorize-url "$KC/realms/toolhive/protocol/openid-connect/auth" \
  --remote-auth-token-url "$KC/realms/toolhive/protocol/openid-connect/token" \
  --remote-auth-client-id "$CLIENT_ID" \
  --remote-auth-client-secret "$CLIENT_SECRET" \
  --remote-auth-callback-port 8666 \
  --remote-auth-timeout 2m \
  --remote-auth-scopes openid,profile,email

Complete the browser login:

  • Username: toolhive-user
  • Password: user123

After authentication succeeds and the workload starts, stop it without deleting the saved run config:

bin/thv stop dcr-renew-keycloak

Expected result:

  • The OAuth flow completes.
  • A run config exists at:
STATE_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/toolhive/runconfigs/dcr-renew-keycloak.json"
jq '.remote_auth_config' "$STATE_FILE"

8. Seed Cached DCR Renewal Metadata

Force the cached secret into the proactive renewal window by setting the expiry to one hour from now.

Linux:

EXPIRY="$(date -u -d '+1 hour' +%Y-%m-%dT%H:%M:%SZ)"

macOS:

EXPIRY="$(date -u -v+1H +%Y-%m-%dT%H:%M:%SZ)"

Patch the saved run config:

tmp="$(mktemp)"
jq \
  --arg cid "$CLIENT_ID" \
  --arg secret_ref "OAUTH_CLIENT_SECRET_dcr_manual" \
  --arg reg_ref "OAUTH_REG_TOKEN_dcr_manual" \
  --arg reg_uri "$REG_URI" \
  --arg expiry "$EXPIRY" \
  '
    .remote_auth_config.cached_client_id = $cid |
    .remote_auth_config.cached_client_secret_ref = $secret_ref |
    .remote_auth_config.cached_reg_token_ref = $reg_ref |
    .remote_auth_config.cached_reg_client_uri = $reg_uri |
    .remote_auth_config.cached_secret_expiry = $expiry |
    .remote_auth_config.cached_token_auth_method = "client_secret_basic" |
    .remote_auth_config.cached_dcr_callback_port = 8666
  ' "$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"

jq '.remote_auth_config | {
  cached_client_id,
  cached_client_secret_ref,
  cached_reg_token_ref,
  cached_reg_client_uri,
  cached_secret_expiry,
  cached_token_auth_method,
  cached_dcr_callback_port
}' "$STATE_FILE"

Expected result:

  • The cached DCR fields are populated.
  • cached_secret_expiry is about one hour in the future.

9. Trigger Automatic Renewal

Save the old registration token:

OLD_REG_TOKEN="$(bin/thv secret get OAUTH_REG_TOKEN_dcr_manual)"

Start the saved workload again so ToolHive reads the patched run config:

TOOLHIVE_DEBUG=true bin/thv start dcr-renew-keycloak

Expected debug logs:

  • Cached client secret is expiring or expired; attempting renewal before token restore
  • Attempting RFC 7592 client secret renewal
  • Successfully renewed client secret via RFC 7592

Inspect proxy logs:

bin/thv logs dcr-renew-keycloak --proxy

After startup succeeds, stop it before patching the config again:

bin/thv stop dcr-renew-keycloak

Validate persistence:

NEW_REG_TOKEN="$(bin/thv secret get OAUTH_REG_TOKEN_dcr_manual)"

if [ "$OLD_REG_TOKEN" = "$NEW_REG_TOKEN" ]; then
  echo "Registration token did not rotate. Check Keycloak policy, but renewal may still have succeeded."
else
  echo "Registration token rotated and was persisted."
fi

jq '.remote_auth_config | {
  cached_client_id,
  cached_client_secret_ref,
  cached_reg_token_ref,
  cached_reg_client_uri,
  cached_secret_expiry,
  cached_token_auth_method,
  cached_dcr_callback_port
}' "$STATE_FILE"

Expected result:

  • The same secret names are still referenced. There should not be a new OAUTH_CLIENT_SECRET_* or OAUTH_REG_TOKEN_* reference in the run config.
  • If Keycloak rotates registration access tokens, NEW_REG_TOKEN differs from OLD_REG_TOKEN.
  • cached_secret_expiry is no longer within the forced one-hour renewal window, or is cleared if Keycloak omits client_secret_expires_at.

10. Check for Double Renewal

Search the debug output from step 9.

Expected result:

  • Only one Attempting RFC 7592 client secret renewal log appears for a single startup.
  • If two renewal attempts appear during one startup, CachedSecretExpiry was not updated in memory correctly.

11. Validate Soft Failure While Expiring Soon

Patch the registration URI to a failing non-root path and keep the expiry in the future:

EXPIRY="$(date -u -d '+1 hour' +%Y-%m-%dT%H:%M:%SZ)"
tmp="$(mktemp)"
jq \
  --arg expiry "$EXPIRY" \
  '.remote_auth_config.cached_secret_expiry = $expiry |
   .remote_auth_config.cached_reg_client_uri = "http://localhost:8081/bad-registration-endpoint"' \
  "$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"

Start the workload again:

TOOLHIVE_DEBUG=true bin/thv start dcr-renew-keycloak
bin/thv logs dcr-renew-keycloak --proxy

Expected result:

  • Logs include Client secret renewal failed.
  • Startup continues to token restore because the secret is only expiring soon.

Stop the workload before restoring the config:

bin/thv stop dcr-renew-keycloak

Restore the valid registration URI:

tmp="$(mktemp)"
jq --arg reg_uri "$REG_URI" '.remote_auth_config.cached_reg_client_uri = $reg_uri' "$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"

12. Validate Hard Failure After Expiry

Patch the expiry into the past and use the failing URI again:

EXPIRY="$(date -u -d '-1 hour' +%Y-%m-%dT%H:%M:%SZ)"
tmp="$(mktemp)"
jq \
  --arg expiry "$EXPIRY" \
  '.remote_auth_config.cached_secret_expiry = $expiry |
   .remote_auth_config.cached_reg_client_uri = "http://localhost:8081/bad-registration-endpoint"' \
  "$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"

Start the workload again:

TOOLHIVE_DEBUG=true bin/thv start dcr-renew-keycloak

Expected result:

  • Startup fails.
  • The error contains client secret expired at and renewal failed.

Restore the valid registration URI if you want to keep testing:

tmp="$(mktemp)"
jq --arg reg_uri "$REG_URI" '.remote_auth_config.cached_reg_client_uri = $reg_uri' "$STATE_FILE" > "$tmp"
mv "$tmp" "$STATE_FILE"

13. Cleanup

Stop all port-forwards, then remove the test cluster:

task kind-destroy

Optional local cleanup:

rm -f "${XDG_STATE_HOME:-$HOME/.local/state}/toolhive/runconfigs/dcr-renew-keycloak.json"
bin/thv secret delete OAUTH_CLIENT_SECRET_dcr_manual || true
bin/thv secret delete OAUTH_REG_TOKEN_dcr_manual || true

Notes and Limitations

  • The normal ToolHive CLI DCR path currently registers public PKCE clients, so Keycloak may not issue a client_secret or client_secret_expires_at in that path.
  • The seeded confidential-client setup above is used to exercise the RFC 7592 renewal path with Keycloak.
  • For a provider that naturally returns client_secret_expires_at during ToolHive-managed DCR, step 9 is not needed. Wait until the cached expiry is within 24 hours, then restart the workload.

Sanskarzz added 8 commits May 24, 2026 01:19
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
Signed-off-by: Sanskarzz <sanskar.gur@gmail.com>
@github-actions github-actions Bot added the size/XL Extra large PR: 1000+ lines changed label May 23, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Large PR Detected

This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.

How to unblock this PR:

Add a section to your PR description with the following format:

## Large PR Justification

[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformation

Alternative:

Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.

See our Contributing Guidelines for more details.


This review will be automatically dismissed once you add the justification section.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 23, 2026

Codecov Report

❌ Patch coverage is 49.79920% with 125 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.77%. Comparing base (08baf2d) to head (5347790).

Files with missing lines Patch % Lines
pkg/runner/runner.go 0.00% 73 Missing ⚠️
pkg/state/runconfig.go 5.26% 18 Missing ⚠️
pkg/auth/remote/secret_renewal.go 87.38% 7 Missing and 7 partials ⚠️
pkg/auth/remote/handler.go 58.06% 12 Missing and 1 partial ⚠️
pkg/auth/discovery/discovery.go 41.66% 6 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5377      +/-   ##
==========================================
+ Coverage   68.74%   68.77%   +0.02%     
==========================================
  Files         626      627       +1     
  Lines       63496    63711     +215     
==========================================
+ Hits        43650    43815     +165     
- Misses      16602    16639      +37     
- Partials     3244     3257      +13     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra large PR: 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Client Secret Expiry and Renewal for Dynamic Client Registration

1 participant