← Aceite MVP V1 — evidências em vídeo

Dossiê — Acesso indevido ao admin (/admin/users)

Falha crítica #1 da auditoria "Manus AI" (NO-GO). ✅ RESOLVIDO (2026-09-04): FINDING-1 fechado — validação ao vivo provou o claim email=ianffcs@neotek.wg no token+userinfo (checklist [OPERADOR] executado); deploy limpo atestado. Veredito do audit: GO CONDICIONAL (03-findings.md §Validação ao vivo). Veredito: não é uma falha de código do small-pine — é o painel admin do Open WebUI, cujo mapeamento de role depende de um merge por email que quebra quando o claim email do Keycloak não bate com o email pré-seedado.


1. O que a auditoria apontou

"Admin access indevido" em /admin/users — um usuário não-admin consegue ver o painel de administração de usuários.

2. O que /admin/users realmente é

/admin/users não é uma rota do small-pine. É o painel administrativo do Open WebUI (o frontend de chat do Chatsubo). A superfície admin do small-pine é /v1/admin/* (após o refactor reitit 22675bc), gateada pelo interceptor admin-token-gate (token compartilhado, não role).

SuperfícieRotaGate
small-pine admin/v1/admin/*admin-token-gate (Bearer token compartilhado)
Open WebUI admin/admin/usersrole admin na tabela user do OWUI

3. Mecanismo de mapeamento de role no OWUI

O role de cada usuário no Open WebUI é decidido por três env vars + uma linha pré-seedada (ansible/roles/openwebui-jail-prod/tasks/configure.yml:611–616):

Env varValorEfeito
ENABLE_OAUTH_SIGNUPtrueprimeiro login SSO cria a conta local
OAUTH_MERGE_ACCOUNTS_BY_EMAILtrueliga o login SSO a uma linha pré-seedada por email
DEFAULT_USER_ROLEuserrole de uma pessoa nova (admins pré-seedados mantêm o deles via merge)

A linha admin é pré-seedada por email (configure.yml:394–395):

INSERT INTO "user" (id, name, email, role, ...)
SELECT '<uuid>', 'ianffcs@neotek.wg', 'ianffcs@neotek.wg', 'admin', ...
WHERE NOT EXISTS (SELECT 1 FROM "user" WHERE email = 'ianffcs@neotek.wg');

openwebui_admin_email: "ianffcs@neotek.wg" (defaults/main.yml:205).

4. Causa raiz do F5 (admin vê roster vazio)

O modo de falha está documentado em scripts/operator/t3-keycloak-realm.sh:337–342:

A federação LDAP é criada via REST, que não auto-cria os user-attribute mappers que a UI criaria. Sem um mapper email <- mail, o atributo email do usuário federado fica vazio, o claim OIDC email não é ianffcs@neotek.wg, e o OAUTH_MERGE_ACCOUNTS_BY_EMAIL do Open WebUI falha em ligar o login SSO à linha pré-seedada role='admin' — criando uma segunda conta role='user' e deixando o admin sem roster (F5).

Ou seja: o admin não ganha acesso indevido — ele perde o acesso (vira user), e o painel /admin/users aparece vazio. A correção é o mapper idempotente email <- mail:

{ "name": "email",
  "providerId": "user-attribute-ldap-mapper",
  "config": { "user.model.attribute": ["email"],
              "ldap.attribute": ["mail"],
              "read.only": ["true"],
              "always.read.value.from.ldap": ["true"] } }

5. Fatores agravantes (identidade estável)

a cada deploy (I38 "re-render / prune unmanaged DNs"), então entryUUID muda e orfana o LDAP_ID do Keycloak, quebrando o login com invalid_user_credentials (t3-keycloak-realm.sh:291–315).

roster ianffcs|admin pcms|admin ctni.teste|user evandro|user é aplicado via /api/v1/users/update/role; qualquer conta fora do roster é "parked" como pending (nunca deletada).

6. Gap estrutural (invariante de deploy)

O mapper email <- mail e a federação LDAP são configurados por um script manual de operador (t3-keycloak-realm.sh), que o deploy ansible não executa. Isso é a mesma classe de falha do ctni.teste (2026-08-28/29): a correção existe no repo mas nunca chega ao Keycloak vivo. O requisito durável do dono é que o loop SSO funcione desde o deploy ansible, sem fixes manuais.

7. E2E admin test — correção do harness (2026-09-06)

Após o mapper email <- mail ser promovido ao deploy, o teste E2E a-normal-account-is-refused-the-admin-surface ainda retornava nil para ambos os usuários. Três bugs no harness de teste foram corrigidos (e2e_mvp_v1_admin.clj, commit d49605f no small-pine):

  1. js-execute não aguarda Promises. e/js-execute mapeia para

:execute :sync do WebDriver, que retorna imediatamente sem resolver fetch().then(). A sonda session-email recebia nil para todo usuário. Corrigido para e/js-async com callback — o único modo WebDriver que aguarda um Promise antes de retornar.

  1. Falta de barra final na rota FastAPI. A sonda fetched /api/v1/auths

(sem / final). Rotas router do FastAPI exigem a barra final; sem ela o request cai no fallback SPA e retorna HTML, não JSON. Corrigido para /api/v1/auths/.

  1. localStorage.token persistiu o JWT admin entre trocas de usuário.

delete-cookies limpa cookies mas não localStorage. OWUI guarda o JWT em localStorage.token, então o segundo browser manteve a sessão admin e a metade negativa viu o roster mesmo após trocar para ctni.teste. Corrigido navegando à origem OWUI e chamando localStorage.clear() entre os logins admin e regular-user (localStorage é per-origin).

Após a correção o teste passou contra prod: 1/1 PASSED, 0/1 FAILED (run 20260906T022327Z-689a252, 108s). Evidência: docs/mvp-v1/evidence/20260906T022327Z-689a252/.

8. Conclusão

por role na tabela user do OWUI.

falha quando o claim email do Keycloak não bate com ianffcs@neotek.wg.

script T3, mas precisa ser promovida a passo do deploy ansible para cumprir o invariante de deploy.