Skip to content

fix(oauth): resolve the app client secret at refresh time, not from the stored copy (#13630) - #13757

Merged
mrveiss merged 10 commits into
Dev_new_guifrom
issue-13630
Aug 8, 2026
Merged

mrveiss merged 10 commits into
Dev_new_guifrom
issue-13630

Conversation

@mrveiss

@mrveiss mrveiss commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Thinking Path

_oauth_bundle() copies the OAuth app client_id/client_secret — operator-owned, one per provider — into every user's credential bundle, and get_access_token refreshed from that copy.

The copy is a snapshot taken when the user connected, and nothing rewrites it. So rotating the app secret silently strands every existing connection: refresh keeps presenting the retired secret and starts failing at the next refresh, not at rotation time.

oauth_flow.resolve_client_credentials(provider) already resolves app credentials from operator config. The refresh path simply wasn't using it.

What Changed

_resolve_app_credentials(creds) prefers the operator's current app config and falls back to the stored copy when the provider is unknown or unconfigured.

That fallback is what makes this safe to land alone: no migration, and it cannot make a working credential worse. A bundle whose provider was since removed from config keeps refreshing exactly as it does today. Rotation starts working immediately for every credential whose provider is still configured — including ones stored long before this change.

Deliberately not in this PR — #13630 stays open

The issue's full scope is larger and the rest is not safe to do unprompted:

  1. Splitting operator client config from the user credential, with a migration that rewrites every stored bundle. That is a secrets migration — the "never lose data" category — and wants its own PR and review. This PR removes the urgency (rotation works now) without touching stored data.
  2. Extending OAuthProvider with the declarative deviation fields (separate refresh URL, tokenEndpointAuthMethod, form-vs-JSON, per-grant field renames, response envelopes, scope separator). All optional and cheap, but adding fields no provider currently needs is speculative until one does — worth doing when the first real provider forces it, which is exactly when the shape will be known.

Verification

knowledge/connectors/                      375 passed
test_credential_store.py                    36 passed
flake8                                       clean

Mutation-checked — reverting to the stored snapshot must fail the rotation test:

1 failed, 35 deselected     # stored-copy behaviour restored
375 passed                  # fixed
Test Asserts
..._rotated_app_secret_is_used... a rotated secret reaches the refresh, not the retired one
..._unknown_provider_falls_back... no migration needed; unresolvable provider keeps working
..._bundle_without_a_provider... pre-provider bundles do not regress
..._unconfigured_provider_falls_back... an unregistered app falls back instead of raising

Model Used

Opus 5

Part of #13630

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No hardcoded values detected that have SSOT config equivalents!

@mrveiss
mrveiss merged commit f76e780 into Dev_new_gui Aug 8, 2026
61 checks passed
@mrveiss
mrveiss deleted the issue-13630 branch August 8, 2026 22:48
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