Kyverno corrigiu 6 falhas de segurança de uma vez — uma delas dá cluster-admin a qualquer tenant
Em 10 de setembro de 2026, o projeto Kyverno lançou o v1.19.1 corrigindo seis advisories de segurança de uma vez — a maior leva desde que o projeto virou incubating na CNCF. Uma delas tem CVSS 9.9 (crítica) e permite que um tenant com permissão apenas para criar Policy no próprio namespace escale até cluster-admin. As outras cinco são bypasses de isolamento entre namespaces, de SSRF e de verificação de assinatura de imagem.
Se você usa Kyverno só pra bloquear latest como tag de imagem, o impacto prático é baixo. Mas se você delega a criação de políticas pra times de plataforma ou tenants — o caso de uso mais comum de um policy engine multi-tenant — vale ler com atenção, porque o requisito de exploração em quase todas é justamente esse: alguém que só devia poder escrever política no próprio namespace.
A crítica: cluster-admin a partir de uma Policy no seu próprio namespace
GHSA-5qq8-67g6-4h2w (CVSS 9.9) nasce de uma discrepância boba de aparência, grave na prática: o Kyverno valida o urlPath de um apiCall com path.Clean() — que normaliza sequências .. literais — mas executa a requisição com o path bruto, sem decodificar. Uma sequência %2e%2e (.. percent-encoded) passa pela validação como se estivesse dentro do namespace do tenant, e só é decodificada de volta pra .. quando o client-go do Kubernetes monta a requisição real — aí sim resolvendo pra fora do namespace.
Com isso, um tenant com permissão de criar Policy (e de criar workloads, no papel de editor) tem dois caminhos documentados até cluster-admin:
- Via
MutatingWebhookConfiguration: registra um webhook mutante cluster-wide, intercepta criação de pods nokube-system, sequestra a ServiceAccount doclusterrole-aggregation-controllere injeta um sidecar que dá permissão total à ClusterRolesystem:basic-user. - Via
PolicyException: cria umPolicyExceptiondentro do namespacekyverno— que o tenant não deveria conseguir acessar diretamente — desativando políticas enforcing só pro próprio namespace e admitindo workloads que estavam bloqueados.
Não existe CVE atribuído ainda, só o GHSA. O achado é de Artem Cherezov.
As duas de isolamento entre namespaces (mesmo bug, dois lugares)
GHSA-c5qq-7g2q-cpqp (CVSS 7.7) é a mesma classe de bug da crítica acima, só que aplicada à leitura de recursos em vez de escalada de privilégio: um path como /api/v1/namespaces/atacante-ns/%2e%2e/vitima-ns/configmaps/config passa pela validação lexical (path.Clean() não decodifica percent-encoding) mas é resolvido de verdade pelo url.Parse() na hora de montar a chamada, que decodifica %2e%2e em .. e atravessa pro namespace vizinho — com a ServiceAccount do controller do Kyverno, não a do tenant. Em instalação default, isso já dá leitura de ConfigMaps de qualquer namespace; se a ServiceAccount do controller tiver permissão sobre Secrets, o alcance é maior.
GHSA-59v6-2x73-wfg4 (CVSS 7.7) é um isolamento quebrado por omissão, não por bug de parsing: a biblioteca CEL globalcontext.Lib — ao contrário de resource.Lib e http.Lib — nunca recebeu parâmetro de namespace no registro. Um NamespacedValidatingPolicy (ou as variantes Mutating/Deleting/Generating/ImageValidating) escrita por um tenant pode chamar globalContext.get("<entry>", "") e ler o conteúdo completo de um GlobalContextEntry cluster-scoped, mesmo de dados vindos de outros namespaces. Só afeta quem já usa GlobalContextEntry cruzando namespaces — cluster sem isso não é afetado.
SSRF e vazamento de token via o executor antigo de apiCall
GHSA-q825-p383-r9v5 (CVSS 7.6) mostra o risco de proteção parcial: em abril de 2026 o Kyverno adicionou blocklist de SSRF e controle de token pro caminho novo baseado em CEL (pkg/cel/compiler/http.go). O executor legado (pkg/engine/apicall/executor.go), usado por ClusterPolicy/Policy clássicas e por GlobalContextEntry, nunca recebeu o mesmo tratamento. Ele monta a requisição com a URL bruta definida pelo autor da política, sem qualquer filtro, e anexa Authorization: Bearer <token> da ServiceAccount sempre que o autor não define header próprio. Resultado: quem tem permissão de criar política pode apontar apiCall.service.url pro metadata endpoint da cloud (169.254.169.254), loopback ou serviço interno, e o Kyverno faz a requisição com o próprio token embutido — sem confirmar se o destino é legítimo.
Bypass de verificação de assinatura de imagem via PolicyException
GHSA-5cjf-wwfg-pj4c (CVSS 7.7) atinge quem usa ImageValidatingPolicy com Notary ou Cosign pra checar assinatura de imagem antes do deploy. O PolicyException deveria isentar só as imagens listadas em spec.images — é assim que ValidatingPolicy, MutatingPolicy e GeneratingPolicy já se comportam. Só que no compilador do ImageValidatingPolicy (pkg/image/verification/evaluator/compiler.go), apenas os matchConditions da exceção são compilados; os campos images e allowedValues nunca são processados. Na prática, qualquer PolicyException que dê match na política desliga a verificação de assinatura inteira — não só pras imagens que deveriam estar isentas. Quem tem permissão de criar PolicyException desativa verificação de supply chain cluster-wide, mesmo sem essa intenção.
O que fazer
Todas as seis (mais duas CVEs de dependência — CVE-2026-39821 e CVE-2026-56853, resolvidas com bump de Go 1.26.6 e x/net) estão corrigidas no v1.19.1. Não há mitigação parcial documentada pra nenhuma delas — atualizar é o caminho.
Priorize pela combinação que te afeta:
- Multi-tenant com criação de Policy delegada (times de plataforma que dão namespace + permissão de
Policypra squads): a crítica e as duas de isolamento são motivo pra não esperar a janela de manutenção normal. É exatamente esse modelo de uso que os três bugs exploram. - Usa
apiCallemClusterPolicy/Policyclássica ouGlobalContextEntryapontando pra serviço externo: o bypass de SSRF é seu — confira se o executor legado ainda está em uso antes de assumir que a blocklist de abril te protege. - Usa
ImageValidatingPolicycom Notary/Cosign e permitePolicyException: valide se alguma exceção existente hoje está isentando mais imagens do que deveria — o comportamento errado já pode estar em produção silenciosamente. - Usa
GlobalContextEntrycompartilhado entre namespaces: revise quem tem permissão de escreverNamespacedValidatingPolicyantes de assumir isolamento entre tenants.
O padrão que conecta quatro das seis falhas é o mesmo: validação de path feita numa representação (limpa, decodificada) e execução feita em outra (bruta, ou decodificada de novo mais adiante). Vale como lição pra qualquer controller seu que valida uma string e delega a interpretação real dela pra uma lib de terceiro — path.Clean() e url.Parse() não concordam sobre o que %2e%2e significa, e essa divergência é exatamente onde bypass de autorização mora.
Fontes
- Kyverno v1.19.1 — release notes
- GHSA-5qq8-67g6-4h2w — Privilege escalation to cluster admin via Policy apiCall urlPath (CVSS 9.9)
- GHSA-c5qq-7g2q-cpqp — Namespace isolation bypass via percent-encoded path segments
- GHSA-59v6-2x73-wfg4 — globalcontext.Lib CEL library namespace isolation bypass
- GHSA-q825-p383-r9v5 — Legacy apiCall service executor bypasses SSRF and token controls
- GHSA-5cjf-wwfg-pj4c — ImageValidatingPolicy exceptions ignore PolicyException specifications
- Kyverno — Security Advisories (lista completa)
- Kyverno — README (status CNCF Incubating)