Rails 8.0 perde o suporte de segurança em 7 de novembro: como migrar para o 8.1 antes do prazo
TL;DR
O Rails 8.0 recebe suas últimas correções de segurança em 7 de novembro de 2026. Ir para o 8.1 é um upgrade pequeno: mesmo Ruby 3.2 mínimo, poucas remoções, um schema.rb em ordem alfabética e sete novos defaults. Faça dual boot, suba o 8.1 com os defaults antigos, vire-os em lotes e guarde a imagem 8.0 para rollback.
O Rails 8.0 para de receber patches de segurança em 7 de novembro de 2026. Veja o que muda entre o 8.0 e o 8.1, os sete novos defaults e quais podem morder, e um plano com dual boot para terminar antes do prazo.
O Rails 8.0 recebe suas últimas correções de segurança em 7 de novembro de 2026. Se sua aplicação ainda estiver no 8.0 depois disso, a próxima CVE do nível da que atingiu o Active Storage sai corrigida para o 8.1, e não para você. A boa notícia: ir do 8.0 para o 8.1 é um dos menores upgrades de versão minor que o Rails já teve. Aqui está o que muda de verdade, quais novos defaults podem morder e um plano com dual boot que dá para terminar nas três semanas que restam.
Por que agora
A política de manutenção do Rails dá a cada série minor um ano de correções de bugs e dois anos de correções de segurança, contados a partir do primeiro release. O Rails 8.0.0 saiu em 7 de novembro de 2024. As correções de bugs do 8.0 acabaram em maio; as de segurança acabam em 7 de novembro de 2026.
Este ano essa data não é abstrata. O release mais recente do 8.0, o 8.0.5.1 (29 de julho de 2026), foi o patch da CVE-2026-66066, a leitura arbitrária de arquivos e RCE no processamento de variants do Active Storage que atacantes transformaram em campanha ativa no fim de agosto. O próximo bug dessa classe que aparecer depois de 7 de novembro será corrigido só no 8.1. "A gente atualiza quando precisar" vira "a gente atualiza no meio de um incidente".
O alvo é o Rails 8.1.4 (24 de setembro de 2026), o patch release atual. Ainda não existe 8.2 no RubyGems.
Uma nota sobre as datas do 8.1: a página de manutenção lista 10 de outubro de 2026 como fim das correções de bugs do 8.1, uma data que já passou, enquanto não existe série mais nova. A mesma política estende o suporte do release anterior quando nenhum novo sai em um ano. De qualquer forma o 8.1 é a linha suportada, e as correções de segurança dele vão até 10 de outubro de 2027.
Qual o tamanho desse upgrade?
Pequeno. Rails 8.0 e 8.1 exigem a mesma versão mínima de Ruby (3.2.0), então o Ruby não faz parte deste trabalho. Se você também quer o Ruby 4.0, faça como um passo separado; o guia do Ruby 3.4 para o 4.0 cobre isso. O guia oficial de upgrade lista exatamente um item do 8.0 para o 8.1 (a ordem das colunas no schema.rb). O trabalho real são três listas:
- Remoções de coisas que foram depreciadas no 8.0.
- Sete novos defaults do framework, que você pode adotar um de cada vez.
- Novas depreciações para resolver agora, porque elas viram remoções na próxima versão.
Para uma aplicação Rails 8.0 típica, reserve um dia de trabalho focado para a troca, mais alguns dias de observação para cada lote de defaults.
O que quebra: as remoções
Se a sua suíte no 8.0.5.1 roda sem avisos de depreciação, você já está quase coberto. Faça o grep mesmo assim, porque os avisos só aparecem para o código que os testes executam.
Active Support
Benchmark.mssaiu do Active Support. Ele vive na gembenchmark, que é uma bundled gem no Ruby 4.0, então adicionegem "benchmark"se você o usa.to_timeagora sempre preserva o fuso horário do receptor. Apagueconfig.active_support.to_time_preserves_timezoneda sua configuração; a própria opção está depreciada.- Passar um objeto
TimeparaTime#since, e somar umTimea umActiveSupport::TimeWithZone, foram removidos.
Active Job
config.active_job.enqueue_after_transaction_commitfoi removido, eActiveJob::Base.enqueue_after_transaction_commitnão aceita mais:never,:alwaysou:default. Usetrueoufalsenas classes de job que precisam disso.- O adapter interno do SuckerPunch foi removido; a gem
sucker_punchtraz o próprio.
Action Pack
- Ponto e vírgula não separa mais parâmetros na query string.
foo=bar;baz=quuxagora vira um único parâmetrofoocom valorbar;baz=quux. Clientes legados e webhooks que montam URLs na mão são onde isso aparece. - O parser de parâmetros não ignora mais um colchete no início do nome:
[foo]=baragora é a chave"[foo]", e não"foo". - Uma rota não pode mais apontar para vários paths.
Active Record e Active Storage
- A opção
:retriesdo adapter SQLite3 foi removida. - Os métodos de coluna
:unsigned_floate:unsigned_decimaldo MySQL foram removidos. Migrations antigas que os usam vão falhar se você um dia rodar todas as migrations do zero em vez de carregar o schema. - O serviço
:azuredo Active Storage foi removido. Se você ainda o usa, migre para um adapter mantido antes do upgrade, e não durante.
Railties
bin/rake statseSTATS_DIRECTORIESforam removidos; usebin/rails stats.
Uma passada encontra a maioria:
grep -rnE "Benchmark\.ms|to_time_preserves_timezone|enqueue_after_transaction_commit|unsigned_(float|decimal)|STATS_DIRECTORIES|rake stats" \
app lib config db spec test
grep -n "Azure" config/storage.yml
O diff que todo mundo vê: schema.rb
O Active Record agora ordena as colunas das tabelas em ordem alfabética no schema.rb, então o dump fica idêntico entre máquinas em vez de oscilar conforme a ordem das migrations. O custo é pago uma vez: o primeiro dump depois do upgrade reescreve boa parte do arquivo.
Coloque essa reescrita num commit próprio, sem nenhuma mudança real de schema, e avise o time antes do merge, senão toda branch aberta que mexe no schema ganha um conflito:
bin/rails db:schema:dump
git add db/schema.rb
git commit -m "Rails 8.1: alphabetize schema.rb columns (no schema change)"
Se você depende da ordem física das colunas, o structure.sql continua preservando essa ordem.
Os sete novos defaults
bin/rails app:update gera config/initializers/new_framework_defaults_8_1.rb com cada novo default comentado. Até você descomentar uma linha ou subir o config.load_defaults, a aplicação mantém o comportamento do 8.0. Em ordem de quanto podem doer:
| Configuração | Novo valor | Risco | O que procurar |
|---|---|---|---|
action_controller.action_on_path_relative_redirect | :raise | Médio | Redirects para strings sem barra inicial |
active_record.raise_on_missing_required_finder_order_columns | true | Médio | first/last em models sem chave primária |
action_view.render_tracker | :ruby | Baixo | Possíveis misses pontuais no cache de fragmentos |
action_controller.escape_json_responses | false | Baixo | Respostas JSON embutidas em HTML |
active_support.escape_js_separators_in_json | false | Baixo | Consumidores JavaScript anteriores a 2019 |
action_view.remove_hidden_field_autocomplete | true | Baixo | Testes que comparam HTML exato |
config.yjit | !Rails.env.local? | Baixo | YJIT desligado em development e test |
O último não está no initializer gerado; ele só chega quando você define config.load_defaults 8.1.
Redirects relativos ao path agora levantam erro
redirect_to "example.com" ou redirect_to "@attacker.com" é tratado como um path relativo à URL atual, um bloco clássico de construção de open redirects. Com o novo default, o Rails levanta ActionController::Redirecting::UnsafeRedirectError para URLs relativas sem barra inicial. redirect_to "/safe/path" continua funcionando.
O default antigo é :log, então o primeiro deploy com 8.1 já mostra onde estão os problemas. Se você prefere ver isso no APM em vez de fazer grep nos logs, troque para :notify por alguns dias e assine o evento:
# config/initializers/new_framework_defaults_8_1.rb
Rails.configuration.action_controller.action_on_path_relative_redirect = :notify
# config/initializers/unsafe_redirect_audit.rb (temporary)
ActiveSupport::Notifications.subscribe("unsafe_redirect.action_controller") do |event|
Rails.logger.warn("[unsafe_redirect] #{event.payload.inspect}")
end
Os suspeitos de sempre: redirect_to params[:return_to], URLs de "voltar para" guardadas, e callbacks de OAuth ou SSO que fazem ida e volta com uma localização. Quando o log ficar vazio por alguns dias, mude para :raise.
Finders dependentes de ordem sem ordem
Chamar first, second, last e companhia numa relation sem order está depreciado quando o model não tem nada para usar como fallback: nem implicit_order_column, nem query_constraints, nem chave primária. A maioria dos models tem id, então isso só pega os casos diferentes: views do banco, tabelas de junção criadas com id: false, tabelas legadas. Com o novo default, essas chamadas levantam erro. O arquivo gerado avisa que a própria opção de configuração some no Rails 8.2, então levantar erro passa a ser o único comportamento.
A correção é um order explícito, ou uma coluna de fallback no model:
class MonthlyRevenue < ApplicationRecord # backed by a SQL view, no primary key
self.implicit_order_column = "month"
end
Escape de JSON, duas vezes
escape_json_responses = false faz o renderer de JSON parar de escapar <, >, & e os separadores U+2028/U+2029 em respostas render json:. É mais rápido e inofensivo para tudo que interpreta a resposta como JSON, ou seja, todo cliente de API e todo fetch. O único risco é código que pega o corpo de uma resposta JSON renderizada e embute dentro de uma tag <script> no HTML. Isso é raro; faça um grep antes de virar a chave.
escape_js_separators_in_json = false para de escapar U+2028 e U+2029 no encoding de JSON do Active Support. Esses caracteres eram inválidos dentro de strings literais de JavaScript até o ECMAScript 2019, então isso só importa se algo ainda entrega seu JSON para um motor anterior a 2019 via eval ou JSONP. O to_json continua escapando <, > e & pela configuração separada de HTML entities do Active Support, então <%= raw @data.to_json %> dentro de uma tag script continua tão seguro quanto antes.
Um parser Ruby para dependências de templates
render_tracker = :ruby faz o Action View rastrear dependências entre templates com um parser Ruby em vez de expressões regulares. Ele encontra chamadas render que a regex não via. Como os digests de cache são calculados a partir dessas dependências, algumas chaves de cache de fragmentos podem mudar quando você ativa a opção. Trate como uma mudança de chave de cache: faça num dia tranquilo, não antes de um pico de tráfego, e acompanhe a taxa de acerto do cache.
Inputs hidden perdem o autocomplete="off"
Inputs hidden gerados por form_tag, token_tag, method_tag e button_to deixam de renderizar autocomplete="off". Os navegadores se comportam igual. Testes que comparam HTML exato talvez não.
YJIT desligado em development e test
Com load_defaults 8.1, o YJIT passa a ter como default !Rails.env.local?: ligado em produção, desligado em development e test. Produção não muda. Se você mediu que o YJIT acelera uma suíte de testes longa, mantenha ligado lá:
# config/environments/test.rb
config.yjit = true
Depreciações para resolver de uma vez
Estas ainda funcionam no 8.1, com avisos. Corrija na mesma passada para que o próximo upgrade seja tão pequeno quanto este:
- Adapter interno do Sidekiq. O adapter agora vem da própria gem
sidekiq. Atualize o Sidekiq para uma versão que o inclua;queue_adapter = :sidekiqcontinua igual. String#mb_charseActiveSupport::Multibyte::Chars. Os próprios métodos deStringdo Ruby lidam com Unicode há anos:name.mb_chars.upcaseviraname.upcase.ActiveSupport::Configurable. Troque porclass_attributeou accessors simples.ActiveRecord::Base.signed_id_verifier_secret. Migre paraRails.application.message_verifiers, ouModel.signed_id_verifierse o segredo for específico do model.- Escritas em lote.
insert_all/upsert_allcom registros não persistidos em associações, eupdate_allcomWITH,WITH RECURSIVEouDISTINCT. - Serializers customizados do Active Job precisam expor um método público
#klass. config.action_dispatch.ignore_leading_bracketsestá depreciado junto com o comportamento que ele controlava.
Para garantir que nada escape, faça o ambiente de teste levantar erro em depreciações enquanto você trabalha:
# config/environments/test.rb (during the upgrade)
config.active_support.deprecation = :raise
Upgrade passo a passo
Fase 1: deixar o 8.0 limpo (sem subir o Rails ainda)
- Vá para o último patch do 8.0, o 8.0.5.1. Você precisa dele de qualquer jeito: é a correção da CVE-2026-66066.
- Suíte verde, zero avisos de depreciação.
- Rode os greps de remoções acima e corrija cada ocorrência.
- Encontre gems que travam o Rails abaixo do 8.1:
bundle exec ruby -e '
Bundler.load.specs.each do |s|
s.dependencies.each do |d|
next unless d.name.match?(/\A(rails|railties|active|action)/)
puts "#{s.name}: #{d}" if d.requirement.to_s.match?(/< ?8\.1|~> ?8\.0\./)
end
end'
Atualize ou substitua cada gem que aparecer antes de seguir.
Fase 2: dual boot
Rode as duas versões a partir da mesma branch, para que o CI mostre exatamente o que o 8.1 quebra sem travar quem ainda está entregando no 8.0:
# Gemfile
def next?
File.basename(__FILE__) == "Gemfile.next"
end
if next?
gem "rails", "~> 8.1.4"
else
gem "rails", "~> 8.0.5"
end
ln -s Gemfile Gemfile.next
cp Gemfile.lock Gemfile.next.lock
BUNDLE_GEMFILE=Gemfile.next bundle update rails
BUNDLE_GEMFILE=Gemfile.next bin/rails test # or bundle exec rspec
A gem next_rails automatiza essa configuração se você preferir uma ferramenta. No GitHub Actions, uma matrix sobre o Gemfile basta; o ruby/setup-ruby respeita o BUNDLE_GEMFILE no cache do bundle:
strategy:
matrix:
gemfile: [Gemfile, Gemfile.next]
env:
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
Fase 3: a troca
- Quando o
Gemfile.nextestiver verde, deixe o 8.1 como única versão: aponte o Gemfile para~> 8.1.4, rodebundle update railse apague a estrutura de dual boot. - Rode
bin/rails app:updatee revise cada conflito em vez de aceitar tudo às cegas. Mantenhaconfig.load_defaults 8.0por enquanto. DefinaTHOR_MERGEse quiser usar sua própria ferramenta de merge nos conflitos. - Atualize os pacotes npm
@rails/*nopackage.jsonpara a versão correspondente, seja com jsbundling ou com Vite. - Faça o commit do
schema.rbem ordem alfabética separadamente.
Fase 4: deploy, depois virar os defaults em lotes
- Deploy 1: Rails 8.1 com
load_defaults 8.0e o arquivo de novos defaults intocado. Observe por 48 horas. Acompanhe os logs atrás de avisos de redirect relativo e de depreciações de ordem nos finders. - Deploy 2: o lote de baixo risco: as duas opções de JSON, o autocomplete dos campos hidden, o YJIT.
- Deploy 3:
render_tracker = :ruby, numa janela tranquila. - Deploy 4: ordem nos finders e redirects relativos, quando os avisos do deploy 1 estiverem zerados.
- Apague
new_framework_defaults_8_1.rbe definaconfig.load_defaults 8.1.
Fase 5: Kamal e rollback
Com Kamal, cada passo é um build com tag e um kamal deploy. Guarde a imagem anterior para que o kamal rollback seja um comando só (o guia do Kamal 2 tem a configuração completa). Acompanhe taxa de erros, latência p95 e taxa de falha de jobs por 48 horas depois de cada deploy.
O rollback é barato porque nenhum dos defaults do 8.1 mexe no formato do cache, na serialização de cookies ou nos message verifiers, então uma imagem 8.0 consegue ler tudo que uma imagem 8.1 escreveu. A reordenação do schema.rb é cosmética. O único jeito de deixar o rollback caro é adotar APIs exclusivas do 8.1 cedo demais: um worker 8.0 não consegue rodar uma classe de job que inclui ActiveJob::Continuable. Segure as novidades até passar do ponto em que você faria rollback.
O que você ganha em troca
Depois de estabilizar no 8.1, vale olhar:
- Continuations no Active Job. Inclua
ActiveJob::Continuable, divida operformemsteps e salve um cursor comstep.advance!. Um job interrompido retoma do último passo concluído em vez de começar do zero, o que importa com o default do Kamal de trinta segundos para os containers de jobs encerrarem. - Eventos estruturados.
Rails.event.notify("user.signup", user_id: 123)com tags, contexto e subscribers plugáveis, para logs que o seu pipeline consegue processar. - CI local.
config/ci.rbebin/cideclaram os passos de setup, lint, segurança e testes numa DSL Ruby, com integração opcional comgh signoff. - Renderização de Markdown.
format.md { render markdown: @page }para qualquer objeto comto_markdown. - Associações depreciadas.
has_many :posts, deprecated: truereporta cada uso, incluindo preloads e nested attributes, nos modos:warn,:raiseou:notify. É a ferramenta certa para a próxima limpeza como esta. - Segredos do Kamal a partir das credentials.
KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)em.kamal/secrets. - Logs de redirect detalhados em development:
config.action_dispatch.verbose_redirect_logs = true.
Checklist
- No 8.0.5.1, suíte verde, zero avisos de depreciação
- Greps de remoções limpos, storage
:azuremigrado se usado - Nenhuma gem travando o Rails abaixo do 8.1
-
Gemfile.nextverde no CI -
app:updaterevisado,load_defaults 8.0mantido -
schema.rbem ordem alfabética num commit próprio - Deploy 1 observado, avisos de redirect e de finders zerados
- Defaults virados em lotes, depois
load_defaults 8.1 - Imagem anterior guardada para
kamal rollback - Tudo isso antes de 7 de novembro
Resumo
Ir do Rails 8.0 para o 8.1 é um upgrade de remoções e defaults, não uma reescrita: mesma versão mínima de Ruby, um diff barulhento no schema.rb, uma dúzia de APIs removidas e sete defaults que você adota no seu ritmo. Dois desses defaults merecem cuidado (redirects relativos ao path e finders sem ordem), e os dois se anunciam nos logs antes de quebrar qualquer coisa. Faça dual boot, suba o 8.1 com os defaults antigos, vire as chaves em lotes, e você estará num Rails suportado bem antes de a janela de segurança do 8.0 fechar.
Fontes
- Política de manutenção do Ruby on Rails rubyonrails.org
- Notas de release do Ruby on Rails 8.1 guides.rubyonrails.org
- Upgrading Ruby on Rails (Rails Guides) guides.rubyonrails.org
- Configuring Rails Applications: valores default do 8.1 guides.rubyonrails.org
- Template new_framework_defaults_8_1.rb (rails/rails, 8-1-stable) github.com
- rails/rails #53281: colunas do schema.rb em ordem alfabética github.com
- Todas as versões da gem rails (RubyGems.org) rubygems.org
- Boletim de segurança do Rails AV26-767 (Centro Canadense de Cibersegurança) www.cyber.gc.ca
Perguntas frequentes
Quando exatamente o Rails 8.0 para de receber correções de segurança?
Em 7 de novembro de 2026, dois anos depois do lançamento do Rails 8.0.0 em 7 de novembro de 2024. As correções de bugs do 8.0 já acabaram em maio de 2026. Depois de 7 de novembro, novas vulnerabilidades são corrigidas só no 8.1.
Preciso atualizar o Ruby para ir para o Rails 8.1?
Não. Rails 8.0 e 8.1 exigem Ruby 3.2.0 ou mais novo. Se você também quer o Ruby 4.0, faça isso num passo separado, antes ou depois do Rails, para saber qual mudança causou cada falha.
É seguro definir config.load_defaults 8.1 logo de cara?
Funciona para muitas aplicações, mas muda sete comportamentos de uma vez. O caminho mais seguro é subir o 8.1 com load_defaults 8.0, ler os logs atrás de avisos de redirect relativo e de depreciações de ordem nos finders, e então ativar os novos defaults em lotes pequenos.
Por que meu schema.rb inteiro mudou depois do upgrade?
O Rails 8.1 ordena as colunas das tabelas em ordem alfabética no schema.rb para que o dump seja consistente entre máquinas. O primeiro dump reescreve o arquivo uma vez. Faça o commit dessa reescrita sozinho, sem mudança real de schema, e use structure.sql se você precisa da ordem física das colunas.
Consigo voltar do 8.1 para o 8.0 se algo der errado?
Sim. Nenhum dos defaults do 8.1 muda o formato do cache, a serialização de cookies ou os message verifiers, então uma imagem 8.0 consegue ler o que o 8.1 escreveu. Evite APIs exclusivas do 8.1, como ActiveJob::Continuable, até passar do ponto em que você faria rollback.
Qual novo default tem mais chance de quebrar minha aplicação?
A verificação de redirects relativos ao path e a de ordem nos finders. A primeira levanta erro em redirect_to com uma string sem barra inicial; a segunda levanta erro em first ou last para models sem chave primária. As duas geram avisos nos logs com os defaults antigos, então dá para encontrar cada caso antes de virar a chave.
Comentários
Faça login com GitHub para comentar.
Ainda não há comentários.