Repository navigation
fix(auth): exigir reautenticação para as chaves de identidade OIDC e não importar estado de sessão no import-json - #16491
Merged
Conversation
Contributor
CI Coverage Report
Coverage artifact was not available for this run. |
Contributor
|
Olá Diego — obrigado novamente pela correção.
Uma dúvida rápida: os advisories GHSA-hcwm-v74f-pfvh e GHSA-7gv8-3qvx-rrvj estão
previstos para serem publicados junto com a 3.8.52? No momento eles aparecem como
rascunhos fechados, e publicá-los daria aos usuários downstream o sinal do
Dependabot / npm audit para o release.
Sem pressa nenhuma — só queria sinalizar caso a publicação tenha passado batido.
曹麟一
***@***.***
…---Original---
From: "Diego Rodrigues de Sa e ***@***.***>
Date: Tue, Oct 13, 2026 20:43 PM
To: ***@***.***>;
Cc: ***@***.******@***.***>;
Subject: [diegosouzapw/OmniRoute] fix(auth): require password re-auth to change the OIDC issuer and client (PR #16491)
Depende da #16477 (merge dela primeiro). Esta branch foi cortada sobre fix/ghsa-hcwm-spawn-bypass-prefixes para reaproveitar src/shared/constants/securityImpactingSettings.ts; até a #16477 entrar, o diff mostra também o commit dela (b5c2b04). O commit desta PR é só o último.
Problema
PATCH /api/settings exige reautenticação por senha (currentPassword) para oidcEnabled, oidcDisablePasswordLogin e oidcClientSecret, mas não para o restante da configuração de identidade OIDC. Quem tiver uma sessão do dashboard roubada ou uma chave com escopo manage podia, sem saber a senha:
trocar oidcIssuer + oidcClientId e apontar o login do dashboard para um IdP controlado pelo atacante (o callback valida issuer/audience contra esses mesmos valores);
colocar o próprio sub/e-mail em oidcAllowedSubjects;
alterar oidcRedirectPath, que é concatenado à origem (${origin}${redirectPath}), então um valor como @attacker.example/cb manda o authorization code para outro host;
mudar oidcScopes.
Com isso o atacante mantém acesso ao dashboard mesmo depois de a senha ser trocada. Como POST /api/settings/import-json remove só as chaves da lista compartilhada, essas também passavam pelo import.
Correção
oidcIssuer, oidcClientId, oidcAllowedSubjects, oidcRedirectPath e oidcScopes entram em SECURITY_IMPACTING_SETTINGS_KEYS. Com isso o PATCH passa a exigir currentPassword e o import-json passa a removê-las (IMPORT_PROTECTED_SETTINGS_KEYS já deriva dessa lista).
Um teste fixa que toda chave oidc* do updateSettingsSchema está no conjunto de reauth, para que uma chave OIDC nova não nasça desprotegida.
docs/architecture/AUTHZ_GUIDE.md documenta o gate.
UI: nenhum impacto. Não há formulário de configuração OIDC no dashboard (grep -rn oidc src --include=*.tsx só encontra src/app/login/page.tsx, que apenas lê oidcEnabled/oidcDisablePasswordLogin). O OIDC é configurado pela API, e quem chama já precisa mandar currentPassword para oidcClientSecret/oidcEnabled.
OIDC com login por senha desativado (oidcDisablePasswordLogin): segue o que a rota já fazia para as chaves protegidas antes. A flag só recusa o login por senha (POST /api/auth/login); o hash da senha continua salvo e a reauth compara currentPassword com ele. O operador que entra por OIDC continua informando a senha de gestão para mexer nessas chaves. A exceção de cold boot (sem hash e requireLogin === false) também não mudou. Nenhum mecanismo novo foi criado.
Prova TDD (RED → GREEN)
node --import tsx/esm --test tests/unit/oidc-settings-reauth.test.ts
RED (sem o fix): tests 18 · pass 6 · fail 12. Falharam todos os casos "sem currentPassword → 400 PASSWORD_REQUIRED" e "senha errada → 401 PASSWORD_MISMATCH" para as 5 chaves, o teste "toda chave oidc* está protegida" e o "import-json não grava as chaves OIDC".
GREEN (com o fix): tests 18 · pass 18 · fail 0.
tests/unit/oidc-settings-allowlist-guard.test.ts: os dois casos que aceitam a gravação de oidcAllowedSubjects agora semeiam um hash de senha e mandam currentPassword. É alinhamento ao novo gate, sem asserção removida nem enfraquecida: continuam exigindo 200 e o valor gravado. Os casos de rejeição (OIDC_ALLOWED_SUBJECTS_REQUIRED) não mudaram, porque essa checagem roda antes da reauth.
Validação
19 arquivos de teste que importam a rota de settings / import-json / securityImpactingSettings, mais api-auth.test.ts: tests 245 · pass 245 · fail 0
npm run typecheck:core: limpo
eslint + prettier nos arquivos alterados: limpos
npm run check:docs-all: ok
Crédito: relatado por @123abab123789 (trailer Co-authored-by no commit).
|
…of import-json The tip's #16479 put requireLogin, the manage-scope bypass keys and oidcEnabled / oidcDisablePasswordLogin / oidcClientSecret behind password re-auth and out of the legacy JSON import. Two gaps remained: 1. The rest of the OIDC identity config was still writable without the management password. A stolen dashboard session or manage-scope key could repoint dashboard login at an attacker-controlled IdP (oidcIssuer + oidcClientId), allowlist its own subject (oidcAllowedSubjects), or send the authorization code to another host (oidcRedirectPath "@attacker.example/cb"), and keep access after the password rotates. 2. import-json still wrote currentPassword, sessionsValidAfter and revokedDashboardSessions, so importing an older backup revived dashboard sessions that a password change or a sign-out had already ended. - Add oidcIssuer, oidcClientId, oidcAllowedSubjects, oidcRedirectPath and oidcScopes to SECURITY_IMPACTING_SETTINGS_KEYS (PATCH -> 400 PASSWORD_REQUIRED / 401 PASSWORD_MISMATCH without the right password). - Export DASHBOARD_SESSION_STATE_SETTINGS_KEYS and IMPORT_STRIPPED_SETTINGS_KEYS next to it; import-json strips the latter (password, currentPassword, every re-auth key, the session state). - Tests: every oidc* schema key is guarded; per-key 400/401/200; import-json drops the OIDC keys and the session state; a signed-out session stays dead after importing a pre-sign-out backup; the session-state names are pinned to the dashboardSessionToken constants. - Align oidc-settings-allowlist-guard: its accepted writes now send the management password (assertions unchanged). Co-authored-by: 123abab123789 <123abab123789@users.noreply.github.com>
diegosouzapw
force-pushed
the
fix/oidc-settings-reauth
branch
from
October 14, 2026 16:33
030082f to
82186fd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Esta PR agora parte do tip de
release/v3.8.52, que já tem a correção #16479 para o mesmo advisory:SECURITY_IMPACTING_SETTINGS_KEYScobrerequireLogin, as chaves do bypass manage-scope eoidcEnabled/oidcDisablePasswordLogin/oidcClientSecret, e oimport-jsonremovepassworde essas chaves. A branch foi refeita sobre o tip, com um commit só. Ela cobre as duas brechas que a #16479 deixou abertas.Brecha 1: chaves de identidade OIDC sem reautenticação
PATCH /api/settingsainda aceitava semcurrentPasswordo restante da configuração de identidade OIDC. Com uma sessão do dashboard roubada ou uma chave de escopomanage, e sem saber a senha, dava para:oidcIssuer+oidcClientIde apontar o login do dashboard para um IdP do atacante (o callback validaissuer/audiencecontra esses mesmos valores);sub/e-mail emoidcAllowedSubjects;oidcRedirectPath, concatenado à origem (${origin}${redirectPath}):@attacker.example/cbmanda o authorization code para outro host;oidcScopes.O atacante continuava com acesso mesmo depois de a senha ser trocada.
Correção:
oidcIssuer,oidcClientId,oidcAllowedSubjects,oidcRedirectPatheoidcScopesentram emSECURITY_IMPACTING_SETTINGS_KEYS. Sem senha o PATCH responde400 PASSWORD_REQUIRED, com senha errada401 PASSWORD_MISMATCH, e o import-json passa a removê-las. Um teste confere que toda chaveoidc*doupdateSettingsSchema(são 8) está no conjunto de reauth, para que uma chave OIDC nova não entre desprotegida.Brecha 2: import-json reabria sessões encerradas
O import-json ainda gravava
currentPassword,sessionsValidAftererevokedDashboardSessions. Importar um backup anterior a uma troca de senha ou a um logout (comsessionsValidAfter: 0/revokedDashboardSessions: []) reabria as sessões do dashboard que esses eventos tinham encerrado.Correção:
src/shared/constants/securityImpactingSettings.tspassa a exportar, ao lado deSECURITY_IMPACTING_SETTINGS_KEYS:DASHBOARD_SESSION_STATE_SETTINGS_KEYS:sessionsValidAftererevokedDashboardSessions. Ficam como literais para o módulo de constantes não importarjose, e um teste amarra os nomes aSESSIONS_VALID_AFTER_SETTING/REVOKED_SESSIONS_SETTINGdedashboardSessionToken.ts;IMPORT_STRIPPED_SETTINGS_KEYS:password,currentPassword, todas as chaves de reauth e o estado de sessão. É a única lista que ostripSecurityImpactingSettings()do import-json usa.docs/architecture/AUTHZ_GUIDE.mddocumenta as duas regras.UI: sem impacto. O dashboard não tem formulário de configuração OIDC (só
src/app/login/page.tsxlêoidcEnabled/oidcDisablePasswordLogin). ComoidcDisablePasswordLoginativo, o comportamento é o mesmo que a rota já tinha para as chaves protegidas: a flag recusa só o login por senha, o hash continua salvo e a reauth comparacurrentPasswordcom ele. A exceção de cold boot não mudou.Prova TDD (RED → GREEN)
Testes escritos primeiro e rodados contra o código do tip (
9ecddd15):tests/unit/oidc-settings-reauth.test.ts(5 chaves × sem senha → 400 / senha errada → 401 / senha certa → 200, mais "toda chaveoidc*protegida", "chave não sensível segue sem senha" e "import-json não grava as chaves OIDC"):tests 18 · pass 6 · fail 12tests 18 · pass 18 · fail 0tests/unit/settings-import-json-session-state.test.ts(nomes amarrados às constantes, lista de remoção, cutoff e lista de revogação preservados, e o teste ponta a ponta "sessão que fez logout continua morta depois de importar um backup antigo"):tests 4 · pass 0 · fail 4. No ponta a ponta, o token revogado voltava a verificar esessionsValidAfterera sobrescrito por0.tests 4 · pass 4 · fail 0tests/unit/oidc-settings-allowlist-guard.test.ts: os dois casos que aceitam gravaroidcAllowedSubjectsagora semeiam um hash de senha e enviamcurrentPassword. É só alinhamento ao novo gate: nenhuma asserção foi removida ou enfraquecida, e eles continuam exigindo 200 e o valor gravado. Os casos de rejeição (OIDC_ALLOWED_SUBJECTS_REQUIRED) ficaram iguais, porque essa checagem roda antes da reauth.Validação
securityImpactingSettings,dashboardSessionToken,tests/unit/settings/*,tests/unit/api/settings*,api-auth,auth-login-route,local-only-bypass-spawn-denylistda fix(security): harden the LOCAL_ONLY manage-scope bypass deny-list and settings import #16479):tests 346 · pass 346 · fail 0npm run typecheck:core: limpoeslint+prettiernos arquivos alterados: limposnpm run check:docs-all: okCrédito: relatado por @123abab123789 (trailer
Co-authored-byno commit).