fix: stop dropping oauth.onInsufficientScope when extracting the client secret (#2018)

Copilot round 4 (one suppressed finding, and a real bug).

`extractSecretsFromStored` rebuilt the stripped `oauth` object from an
allow-list of the non-secret fields. That list had gone stale: it never named
`onInsufficientScope`, so a server carrying both a client secret and a
non-default SEP-2350 policy silently lost the policy on save, falling back to
`reauthorize`. This predates #2018 — the PR only extended the same list with
`authorizationParams` — but the list is the defect, so fix the class rather
than add a third entry to it.

The stripped entry now subtracts the one secret instead of enumerating what to
keep, so any field added to `oauth` later is preserved by construction.

Regression test verified to fail against the allow-list version:
`expected { clientId: 'cid' } to deeply equal { clientId: 'cid', onInsufficientScope: 'throw' }`.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-15 21:54:16 -04:00
commit d95a6e629c

View file

@ -642,23 +642,13 @@ export function extractSecretsFromStored(
if (stored.oauth?.clientSecret) {
secrets[SECRET_FIELD_OAUTH_CLIENT_SECRET] = stored.oauth.clientSecret;
const restOauth: {
clientId?: string;
scopes?: string;
authorizationParams?: Record<string, string>;
enterpriseManaged?: boolean;
} = {};
if (stored.oauth.clientId !== undefined)
restOauth.clientId = stored.oauth.clientId;
if (stored.oauth.scopes !== undefined)
restOauth.scopes = stored.oauth.scopes;
// Not a secret — the authorization parameters stay on the stripped entry so
// stripping the client secret doesn't silently discard them. (#2018)
if (stored.oauth.authorizationParams !== undefined)
restOauth.authorizationParams = stored.oauth.authorizationParams;
if (stored.oauth.enterpriseManaged === true) {
restOauth.enterpriseManaged = true;
}
// Keep every OAuth field except the one secret, rather than enumerating the
// non-secret ones. An allow-list here is a standing trap: it silently drops
// any field added to `oauth` afterwards, and it had already done so —
// `onInsufficientScope` was lost whenever a server carried both a client
// secret and a non-default SEP-2350 policy. Subtracting the secret instead
// means a future field is preserved by construction. (#2018)
const { clientSecret: _clientSecret, ...restOauth } = stored.oauth;
if (Object.keys(restOauth).length > 0) {
stripped.oauth = restOauth;
} else {