Skip to content

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.

Publicado em 14 min de leitura

Também disponível em English

Compartilhar

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:

  1. Remoções de coisas que foram depreciadas no 8.0.
  2. Sete novos defaults do framework, que você pode adotar um de cada vez.
  3. 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.ms saiu do Active Support. Ele vive na gem benchmark, que é uma bundled gem no Ruby 4.0, então adicione gem "benchmark" se você o usa.
  • to_time agora sempre preserva o fuso horário do receptor. Apague config.active_support.to_time_preserves_timezone da sua configuração; a própria opção está depreciada.
  • Passar um objeto Time para Time#since, e somar um Time a um ActiveSupport::TimeWithZone, foram removidos.

Active Job

  • config.active_job.enqueue_after_transaction_commit foi removido, e ActiveJob::Base.enqueue_after_transaction_commit não aceita mais :never, :always ou :default. Use true ou false nas classes de job que precisam disso.
  • O adapter interno do SuckerPunch foi removido; a gem sucker_punch traz o próprio.

Action Pack

  • Ponto e vírgula não separa mais parâmetros na query string. foo=bar;baz=quux agora vira um único parâmetro foo com valor bar;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]=bar agora é 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 :retries do adapter SQLite3 foi removida.
  • Os métodos de coluna :unsigned_float e :unsigned_decimal do 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 :azure do Active Storage foi removido. Se você ainda o usa, migre para um adapter mantido antes do upgrade, e não durante.

Railties

  • bin/rake stats e STATS_DIRECTORIES foram removidos; use bin/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çãoNovo valorRiscoO que procurar
action_controller.action_on_path_relative_redirect:raiseMédioRedirects para strings sem barra inicial
active_record.raise_on_missing_required_finder_order_columnstrueMédiofirst/last em models sem chave primária
action_view.render_tracker:rubyBaixoPossíveis misses pontuais no cache de fragmentos
action_controller.escape_json_responsesfalseBaixoRespostas JSON embutidas em HTML
active_support.escape_js_separators_in_jsonfalseBaixoConsumidores JavaScript anteriores a 2019
action_view.remove_hidden_field_autocompletetrueBaixoTestes que comparam HTML exato
config.yjit!Rails.env.local?BaixoYJIT 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 = :sidekiq continua igual.
  • String#mb_chars e ActiveSupport::Multibyte::Chars. Os próprios métodos de String do Ruby lidam com Unicode há anos: name.mb_chars.upcase vira name.upcase.
  • ActiveSupport::Configurable. Troque por class_attribute ou accessors simples.
  • ActiveRecord::Base.signed_id_verifier_secret. Migre para Rails.application.message_verifiers, ou Model.signed_id_verifier se o segredo for específico do model.
  • Escritas em lote. insert_all/upsert_all com registros não persistidos em associações, e update_all com WITH, WITH RECURSIVE ou DISTINCT.
  • Serializers customizados do Active Job precisam expor um método público #klass.
  • config.action_dispatch.ignore_leading_brackets está 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)

  1. 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.
  2. Suíte verde, zero avisos de depreciação.
  3. Rode os greps de remoções acima e corrija cada ocorrência.
  4. 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

  1. Quando o Gemfile.next estiver verde, deixe o 8.1 como única versão: aponte o Gemfile para ~> 8.1.4, rode bundle update rails e apague a estrutura de dual boot.
  2. Rode bin/rails app:update e revise cada conflito em vez de aceitar tudo às cegas. Mantenha config.load_defaults 8.0 por enquanto. Defina THOR_MERGE se quiser usar sua própria ferramenta de merge nos conflitos.
  3. Atualize os pacotes npm @rails/* no package.json para a versão correspondente, seja com jsbundling ou com Vite.
  4. Faça o commit do schema.rb em ordem alfabética separadamente.

Fase 4: deploy, depois virar os defaults em lotes

  1. Deploy 1: Rails 8.1 com load_defaults 8.0 e 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.
  2. Deploy 2: o lote de baixo risco: as duas opções de JSON, o autocomplete dos campos hidden, o YJIT.
  3. Deploy 3: render_tracker = :ruby, numa janela tranquila.
  4. Deploy 4: ordem nos finders e redirects relativos, quando os avisos do deploy 1 estiverem zerados.
  5. Apague new_framework_defaults_8_1.rb e defina config.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 o perform em steps e salve um cursor com step.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.rb e bin/ci declaram os passos de setup, lint, segurança e testes numa DSL Ruby, com integração opcional com gh signoff.
  • Renderização de Markdown. format.md { render markdown: @page } para qualquer objeto com to_markdown.
  • Associações depreciadas. has_many :posts, deprecated: true reporta cada uso, incluindo preloads e nested attributes, nos modos :warn, :raise ou :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 :azure migrado se usado
  • Nenhuma gem travando o Rails abaixo do 8.1
  • Gemfile.next verde no CI
  • app:update revisado, load_defaults 8.0 mantido
  • schema.rb em 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

  1. Política de manutenção do Ruby on Rails rubyonrails.org
  2. Notas de release do Ruby on Rails 8.1 guides.rubyonrails.org
  3. Upgrading Ruby on Rails (Rails Guides) guides.rubyonrails.org
  4. Configuring Rails Applications: valores default do 8.1 guides.rubyonrails.org
  5. Template new_framework_defaults_8_1.rb (rails/rails, 8-1-stable) github.com
  6. rails/rails #53281: colunas do schema.rb em ordem alfabética github.com
  7. Todas as versões da gem rails (RubyGems.org) rubygems.org
  8. 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.