I have always been interested in frontend solutions for building web
applications. Even though I’m more of a backend programmer, I really dig all the
different techniques people come up with for creating browser-based apps that
are both reactive (reflecting remote state in real-time) and interactive
(responding to user actions).
Over the last few years, I’ve been looking at different frontend frameworks and
libraries, to see how they work, and what I can learn from them. I adore
Svelte, which is really smart and efficient. Its sister
framework SvelteKit inspired the
file-based routing in my own web framework for Ruby,
Syntropy. I also really like the
approach of the Phoenix framework…
A story of race conditions, Docker containers, RabbitMQ queues, and the architecture decisions that kept our risk management platform alive under pressure. The Problem Nobody Wants on a Trading Floor Picture this: it’s 9:15 AM. The market just opened. Orders are flooding in — portfolio checks, risk calculations, compliance validations. And your Risk Management system? …
Continue reading From One to Five: How We Solved a Real-Time Trading System’s Scaling CrisisAsk any LLM to style a layout and it uses CSS units like: px, rem, em, % and vh. So do you, out of habit. But CSS has grown its number of units. Ones that fit specific jobs way better than your old defaults. Here are seven your code generator has probably never suggested.
They are all baseline ready (e.g. supported by core browsers for at least 30 months), so there is no fallback excuse left. Let’s get into it.
Logical properties: think in axes (x and y)
A few of these units only make sense when you drop the ideas of “width” and “height” and think in axes instead. CSS now measures along the inline axis (the direction your text flows, left to right in, for example, English) and the block…
RubyGems 4.0.24 includes enhancements, bug fixes and documentation and Bundler 4.0.24 includes enhancements and bug fixes.
To update to the latest RubyGems you can run:
gem update --system [--pre]
To update to the latest Bundler you can run:
gem install bundler [--pre]
bundle update --bundler=4.0.24
RubyGems Release Notes
Enhancements:
- Installs bundler 4.0.24 as a default gem.
Bug fixes:
- Skip executable removal gracefully when the bin directory is missing. Pull request #9963 by Evan Li and Hiroshi SHIBATA
- Fix file locks letting multiple processes in when lock files are removed. Pull request #9930 by Richard Janita and Hiroshi SHIBATA
- Turn off echo before printing the…
Documentation:
- [DOC] Fix link. Pull request #10014 by Burdette Lamar
Bundler Release Notes
Enhancements:
- Read the Ruby version from mise’s inline table form. Pull request #9991 by…
Author: Varya Nekhina, Account Manager
Topics: Case Study, AI, DX, Design for devtools, UI coding harness, Agentic development, Figma, React, Ruby on Rails, CSS, Accessibility

Buildkite runs CI/CD for some of the most demanding engineering teams in the world. Why a team of opinionated developers hired Evil Martians to build a framework-agnostic design system with AI skills that guide coding agents through every component.
Buildkite is a high-scale, hybrid CI/CD platform that lets engineering teams run fast automated build, test, and deployment pipelines.
If you've set up an automated email sequence and haven't reviewed it in a while, here's how to audit it with help from AI agents.
Continue Reading
.bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
.bh__table_cell { padding: 5px; background-color: #FFFFFF; }
.bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
.bh__table_header { padding: 5px; background-color:#F1F1F1; }
.bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
Welcome to the 40th edition of Ruby AI News! This edition features an avalanche of security issues, Ruby’s MCP SDK reaching Tier 1 status, a closer look at building AI evals for Rails, and much more.
Read on the web
Personal…
.bh__table, .bh__table_header, .bh__table_cell { border: 1px solid #C0C0C0; }
.bh__table_cell { padding: 5px; background-color: #FFFFFF; }
.bh__table_cell p { color: #2D2D2D; font-family: 'Helvetica',Arial,sans-serif !important; overflow-wrap: break-word; }
.bh__table_header { padding: 5px; background-color:#F1F1F1; }
.bh__table_header p { color: #2A2A2A; font-family:'Trebuchet MS','Lucida Grande',Tahoma,sans-serif !important; overflow-wrap: break-word; }
New Sponsorship Opportunities
Ruby AI News has reached its 40th edition, and to mark it I am opening sponsorship packages that cover everything in the RoboRuby network. It is built for organizations that want in depth…
### Impact
Since Rails 8.1.0, `ActionDispatch::RemoteIp` derives `request.remote_ip` from
`Rack::Request#forwarded_for`, which under Rack 3 reads the RFC 7239
`Forwarded` header in preference to `X-Forwarded-For`. When a reverse proxy
appends the client address to `X-Forwarded-For` but passes a client-supplied
`Forwarded` header through unchanged, `request.remote_ip` returns an address
chosen by the client, and the IP spoofing check does not raise, because it
compares only `Client-Ip` with the forwarded addresses. An unauthenticated
attacker could therefore spoof `request.remote_ip`, possibly bypassing
IP-based access controls and rate limits keyed on the client address
(including the…
### Impact
With the HTML5 sanitizer (`Rails::HTML5::Sanitizer`, the default vendor on
CRuby for applications using `load_defaults 7.1` or later), Nokogiri before
1.19.5 serializes `<` and `>` unescaped inside attribute values. The Action
View text helpers `highlight` and `simple_format` sanitize their input and
then process the resulting HTML as a string: `highlight` splits it on tag
boundaries to insert matches, and `simple_format` splits it into paragraphs on
blank lines. An unescaped `>` or a blank line inside an attribute value could
cause markup in that value, which the sanitizer left inert, to be emitted as
live elements in the helper's `html_safe` output, possibly leading to…
### Impact
`ActiveSupport::MessagePack` serializes an `ActiveSupport::SafeBuffer` as its
bytes alone and deserializes it as a new `SafeBuffer`, so a buffer that is no
longer `html_safe?` (for example, one mutated in place with `gsub!`) is
restored as `html_safe`. If such a buffer holds untrusted input and is stored
through a MessagePack serializer, rendering it after it is loaded bypasses ERB
auto-escaping, possibly leading to XSS.
Applications on Rails 8.1 are affected if they use a MessagePack serializer,
such as `config.action_dispatch.cookies_serializer = :message_pack` or
`:message_pack_allow_marshal`, or `serializer: :message_pack` for
`Rails.cache`, `ActiveSupport::MessageVerifier`…
### Impact
Active Storage's `VideoPreviewer`, `VideoAnalyzer` and `AudioAnalyzer` run
`ffmpeg` and `ffprobe` on a blob's contents without restricting the input
format, so FFmpeg selects a demuxer by probing the file. A blob created
through a direct upload keeps the content type the client declared, so a
client can store an HLS playlist under a `video/*` or `audio/*` type. When
that blob is previewed or analyzed, the `hls` demuxer opens the `file://`
segments that the playlist names, and the preview image or the stored metadata
could disclose a local media file that the application process can read and
whose path the attacker knows.
Applications that accept uploads from untrusted users and…
### Impact
When a GCS service is configured with the `cache_control` option,
`ActiveStorage::Service::GCSService#url_for_direct_upload` signs direct-upload
URLs with V4, and the V4 signature covers only the `Cache-Control` and `Host`
headers. A direct-upload client can therefore send a `Content-Type` of its
choosing, and can omit the declared `Content-MD5`, without invalidating the
URL. GCS stores the supplied content type as object metadata, which takes
precedence over the `response-content-type` that Active Storage sets on
download URLs, so an uploaded object could be served as inline HTML under the
application's bucket path on `storage.googleapis.com`. Script in such an
object runs on…
### Impact
When Active Storage serves a file through its proxy controllers,
`ActiveStorage::Streaming#send_blob_stream` handles an error from the storage
service by calling `head` to send a 404 or 500 response. If the error occurs
after the first chunk has been written, the response is already committed, and
`head` replaces the response body while the server is still reading the
original `ActionController::Live` buffer. That buffer is never closed, so the
server thread handling the request blocks until the process restarts. Repeated
failed downloads could exhaust the server's threads, possibly resulting in a
DoS vulnerability.
Applications are affected when they store files on a service…
### Impact
The Action Mailbox Mailgun ingress authenticates a request with an HMAC of the
`timestamp` and `token` parameters concatenated without a separator, and
`ActionMailbox::Ingresses::Mailgun::InboundEmailsController::Authenticator#recent?`
rejects only timestamps more than 2 minutes in the past. Because the signature
does not fix the boundary between the two values, a captured signed request
can be presented with a timestamp far in the future, which the ingress then
accepts indefinitely. An attacker who obtains one signed Mailgun webhook
request, but not the signing key, could submit arbitrary inbound email that
Action Mailbox processes as genuine, possibly leading to forged…
### Impact
The fix for
[CVE-2026-33168](https://github.com/rails/rails/security/advisories/GHSA-v55j-83pf-r9cq)
skips blank HTML attribute names in Action View tag helpers, but it tests the
name before converting it to a string. A Symbol name made only of whitespace,
such as `:" "`, is not blank, because `Symbol#blank?` is `Symbol#empty?`, so
it reaches the attribute escaping, which emits an attribute with no name and
produces malformed HTML. The asset tag helpers, such as `image_tag`,
`video_tag` and `audio_tag`, symbolize their options, so a whitespace String
name passed to them takes the same path. A carefully crafted attribute value
could then be misinterpreted by the browser as a…
### Impact
When constant lookup raises `LoadError`,
`ActiveSupport::Inflector#safe_constantize` (and `String#safe_constantize`)
builds a regular expression from the requested constant name without escaping
it, so a caller that passes untrusted input lets the attacker choose the
source of a pattern that the application compiles. Crafted patterns can
trigger defects in Ruby's regular expression compiler, possibly leading to a
crash of the server process, and repeated requests could keep the application
unavailable.
Action Cable passes the `channel` value of each `subscribe` command to
`safe_constantize` before any channel lookup, so in the default configuration
an unauthenticated client of…
### Impact
A Rails application that processes attachments with ImageMagick may allow an
unauthenticated attacker to write arbitrary files on the server and, when
attachments are stored with the Disk service, to read arbitrary files readable
by the application process. Those files typically include `secret_key_base`
and credentials for external systems, which may in turn allow escalation to
remote code execution or lateral movement to those systems.
### Details
ImageMagick selects a coder from the content of the file it is given, and some
coders act on files named inside the image rather than on the image itself.
Active Storage constrained none of ImageMagick's coders, and the…
### Impact
Action Pack session stores other than `ActionDispatch::Session::CookieStore`
(`CacheStore`, `MemCacheStore`, and third-party stores built on
`AbstractStore`, `AbstractSecureStore` or the session mixins) write the
session cookie without a `SameSite` attribute, regardless of
`config.action_dispatch.cookies_same_site_protection`, which defaults to
`:lax`. Only `CookieStore` applies the configured policy. In browsers that do
not treat a cookie without the attribute as `SameSite=Lax`, a cross-site
request could carry the session cookie, possibly leading to cross-site request
forgery against actions that do not verify an authenticity token, such as
those of API-only…
### Impact
`ActiveStorage::Service::GCSService#url_for_direct_upload` does not bind the
declared content length into the signed direct-upload URL, and
`#headers_for_direct_upload` returns no length constraint, so the URL
authorizes an upload body of any size. A direct-upload client could store a
file larger than the `byte_size` it declared, and the resulting blob keeps the
declared size, possibly bypassing application file size validations and
quotas. This is the weakness that CVE-2020-8162 fixed in the S3 service.
Applications that store files in Google Cloud Storage and accept direct
uploads are affected.
### Details
The fixed releases sign an `x-goog-content-length-range` header that…
### Impact
`ActiveStorage::Filename#base` and `#extension_with_delimiter`, and therefore
`#extension`, pass the stored filename unmodified to `File.basename` and
`File.extname`, which raise `ArgumentError` when the name contains a NUL byte.
Active Storage stores the client-supplied filename verbatim, so a user who is
permitted to upload an attachment could persist such a filename on databases
that accept NUL bytes in string columns, such as SQLite and MySQL/MariaDB.
Each subsequent request that builds a variant or preview of the blob, opens
it, or renders it as an Action Text attachment then raises, possibly resulting
in a persistent DoS vulnerability for the pages that display the blob…
### Impact
Action Pack negotiates the format of a rendered response from the `Accept`,
`X-Requested-With` and, for XHR requests without `Accept`, `Content-Type`
request headers, but `ActionController::Rendering#_set_vary_header` adds
`Vary: Accept` only when the request carries a usable `Accept` header and the
response has no `Vary` header yet. A response to a request without `Accept`,
or to an XHR request, therefore omits the headers that selected its
representation, and a response whose `Vary` the application set before
rendering (for example `Vary: Accept-Language`) never gains `Accept`. A shared
cache that honours `Vary` could store such a response and serve it to later
clients that…
### Impact
`strip_tags` in Action View returns its sanitized output marked `html_safe`,
but the underlying sanitizer encodes only `&`, `<` and `>`, leaving `"` and
`'` unencoded. Because ERB does not escape a string marked `html_safe`,
untrusted input passed through `strip_tags` and rendered inside a quoted HTML
attribute value could close the attribute and add new attributes, possibly
leading to XSS. Applications that render `strip_tags` output from untrusted
input inside an HTML attribute, for example in a `title` attribute or a
`` tag, are affected. Output rendered as element content is not
affected.
### Details
`strip_tags` now encodes `"` and `'` in its output, as well as `&`, `<`…
### Impact
`ActionController::Live::Buffer#close` pushes its end-of-stream marker onto
the response's bounded queue while it holds the buffer's monitor. When the
client has stopped reading and the queue is full, the push blocks, and
`Buffer#abort`, which runs when the client disconnects or a write times out,
waits indefinitely for the same monitor. Each such response could permanently
hold a web server thread and its `ActionController::Live` thread, possibly
resulting in a DoS vulnerability through thread exhaustion.
Applications that stream responses with `ActionController::Live`, including
`send_stream`, are affected. Applications that serve large files through
Active Storage proxy mode…
### Impact
`ActiveStorage::Blob#open` writes a blob to a tempfile whose name carries the
extension of the uploaded filename, and hands that path to `ffprobe`
(`VideoAnalyzer`, `AudioAnalyzer`), `ffmpeg` (`VideoPreviewer`) and
ImageMagick (`ImageAnalyzer`, the `:mini_magick` variant processor). These
tools select a decoder from the file extension when the content does not
identify its own format, so an attacker who chooses the filename and declares
the content type of an upload chooses which decoder processes it. An FFmpeg
built with AviSynth support treats a file with the `.avs` extension as an
AviSynth script and evaluates it, which could disclose files readable by the
application process…
### Impact
`ActionController::Parameters#each_key` delegates to the internal
`ActiveSupport::HashWithIndifferentAccess` that holds the request parameters,
so when called with a block it returns that hash instead of the `Parameters`
instance. The hash does not respond to `permitted?`, so Active Model mass
assignment (`new`, `assign_attributes`, `update`) accepts it without raising
`ActiveModel::ForbiddenAttributesError`. An application that passes the return
value of `each_key` to mass assignment could allow a remote, unauthenticated
attacker to set attributes that the application does not permit, such as an
authorization flag.
This is an incomplete fix…
### Impact
When `ActionDispatch::Cookies` reads a signed or encrypted cookie that was
written under a rotated secret (`config.action_dispatch.cookies_rotations`) or
in the format that a `:hybrid` cookie serializer migrates from, it writes the
cookie back to the response in the current format, and that write ignores
`request.session_options[:skip]`. `ActiveStorage::DisableSession` relies on
that option to keep the session cookie off the public, immutable responses of
the Active Storage proxy controllers, but those controllers still load the
session, so a request carrying an old-format session cookie could receive the
rewritten session cookie in `Set-Cookie` on a cacheable response. A…
I recently ported my 1996 Pascal/Delphi calculator to Rust. The result is excalc, a cross-platform command-line application and MCP server.
Rust makes it easy to compile an application on every platform. Distributing it via crates.io, MSI, Homebrew or WinGet needed more work. Paste this cookbook into your favorite AI prompt to get it done.
We trigger a distribution from GitHub Releases: a Git tag identifies the new version, CI publishes the crate from that tag, builds portable archives and an MSI from that tag, and attaches everything to the release. Homebrew downloads the tagged source archive. WinGet points at the released MSI.
Prepare the Rust Package
Start with complete package…
What happens when the tool you rely on disappears? For Eduardo and Camila Souza, the shutdown of Pivotal Tracker became the motivation to build Pivotal Tasker, a Rails-based alternative inspired by the project management workflow they missed. They discuss reconstructing the experience from memory and reference material, adding classic and modern interfaces, and integrating coding agents to help create and work through stories.
The conversation expands into their move from consulting toward building products. Eduardo and Camila describe Chamber Builder, an all-in-one platform for chambers of commerce, and a custom sales CRM that brings calling, recordings, transcripts, and…
Five years ago, I announced that RubyTapas had become Graceful.Dev. But a revenant of that feast was left behind: the rubytapas.com site stuck around to handle billing for legacy customers, and I never got around to fully rolling that aspect into Graceful.Dev.
Until now. As of today, RubyTapas.com is no more. If you have a RubyTapas legacy account, it is now managed at Graceful.Dev. Same plan, same price, same access. Nothing changes except the words on the receipts, and there’s nothing you need to do.
And of course, all RubyTapas episodes are here at graceful.dev (as they have been for years). As well as lots and lots of post-RubyTapas content, organized into courses. The old…
Hello, Rails community!
Rails Versions 8.0.5.2 and 8.1.4.1 have been released. These are security updates addressing 21 security issues falling into a few themes:
- Possible arbitrary file read, file write and remote code execution in Active Storage image and video processing
- Possible Strong Parameters bypass in
Parameters#each_key
- Possible IP spoofing in
remote_ip through the Forwarded header (8.1 only)
- Multiple possible XSS vulnerabilities in Action View helpers, and one in the Active Support MessagePack serializer (8.1 only)
- Possible DoS vulnerabilities in Active Support
safe_constantize, Action Controller Live streaming, and Active Storage proxy mode and filename handling
- U…
We strongly recommend upgrading as soon as possible.
Older versions of Rails are unsupported, and users are…
Every business has a list of jobs nobody wants and nobody can automate. Someone logs into a supplier portal every Monday to download invoices, then types them into the accounting software one line at a time. Someone else fills in the same government form every quarter, copying numbers from a spreadsheet into boxes on a website. Neither system offers a way for other software to connect to it (an API), the vendor isn’t going to build an integration for a customer your size, and so the work stays manual.
At the end of October, OpenAI released GPT-6.1 Terra, a model built for what OpenAI calls computer use: it looks at a screen, reads what’s on it, and clicks and types its way through apps and…
We recently moved a client’s Ruby on Rails app off Heroku: a Rails API with Sidekiq workers and several services around it. Before we changed anything, we spent about a week on discovery. We had a kickoff call with the client’s team on a Monday, and then two of us paired for the rest of the week to map the infrastructure and draft a migration plan.
Nothing about that app followed one pattern. Its services were deployed in different ways, and some of the things that mattered most for the migration, like the tools outside Heroku that read from the database and the service that promoted code to production, didn’t show up in the Heroku dashboard at all.
In this article, you will learn what we…
AI can generate a lot of code quickly. There’s a thought that we can catch its mistakes or other issues by reviewing that code. In theory, this may be true; code review can catch bugs. In practice, it only works if the reviewer knows what to look for, has some context and, well, gives a shit.
There's a thought that we can catch mistakes in AI-generated code by reviewing it (or having an AI do that). This is short-sighted thinking, especially as developers lose the skill of code review entirely. Better to use AI to create better tooling.
This is all in a professional context, i.e. at a job. I don't think the conclusions here are necessarily relevant to hobby or open source…
Why Review…
Authors: Vladimir Dementyev, Principal Backend Engineer, Artur Petrov, Backend Engineer, and Travis Turner, Tech Editor
Topics: Rails, AI, Open Source, LLM benchmarking, Ruby, LLMs

A tour of lemans, the open source Ruby harness behind Agents on Rails: eval task anatomy, how it runs and grades coding agents for hours, and how cheaters shaped its guardrails.
Benchmarking coding agents in one sentence: give a model a task, let it work, check the result. The hard part is keeping the race fair. Every model must get the same conditions, and no agent should be able to escape its sandbox or fool its grader (and they try). And all of that has to keep running for hours. In the first post of The Agents…
Milestone: this week I called a freeze on new Hanami work. From here we’re focused on cutting down our outstanding items and then switching to release prep.
I cut down a few items myself: three little pigs things to finish our Hanami CLI changes for compatibility with Dry CLI. This CLI is just right.
After some great feedback from Michael, I made a few adjustments to Dry Logger’s expanded log filtering (like making Regexp filters case insensitive by default, for consistency with string filters), and merged it. Thanks Michael! I also updated Hanami’s default list of filters. Combined with Dry Loggers substring filtering, this will make for a much more filtery out of the box experience.
A…
I’ve been building a new project in my spare time while working through ideas for a few other apps.
Naturally, I found something to get distracted with.
Oh My Scrobbles is a Mac app for scrobbling Apple Music (and Radio!) to Last.fm and exploring your old listening history.
Like most projects I actually follow through on… this started with a huge itch I needed to scratch. I have over 300,000 scrobbles. I take this shit very seriously. 😉

It can sync my whole Last.fm history to my Mac and expose a local MCP server, so I can ask ChatGPT and Claude questions about it.
I used that to find the exact tracks I’d scrobbled just before and after creating my GitHub account in 2008. Apparently,…
Last week I learned a technique in Ruby so small and useful, I couldn't believe I hadn't come across it before. Suppose a method doubles as a getter and setter. Pass a value to update a setting, or pass nothing to read it: module Cloakable
def clo

Somebody at Gusto asked, during one of our internal AI AMAs, whether /login was costing them tokens. The answer came back yes, relayed into our Claude Code channel a few minutes later: /login "is an LLM call, which will take more time and end up with unnecessary token use," so run claude auth login from the CLI instead, because that one's instant and free. It came with receipts, sort of. Secondhand from someone who'd watched /login run up $3.
That felt off, so I went and checked. It’s off. /login never touches the model, in any config. It's a local command: it runs entirely on your machine and never builds a prompt, so there is nothing to send.
So where did the $3 come from? A leading space.…
## Summary
Loofah restricts `href` and `xlink:href` on certain SVG elements to
same-document references. Two elements escaped the restriction:
- ``: `SVG_ALLOW_LOCAL_HREF` listed it in lowercase as `textpath`,
but the HTML5 parser names the element `textPath` inside ``, so the
check never matched on the HTML5 path.
- ``: absent from `SVG_ALLOW_LOCAL_HREF`, so its references were never
restricted on either path.
`rails-html-sanitizer` reuses this safelist in its `PermitScrubber`, so it is
affected too.
## Impact
An external `href` or `xlink:href` on `` or `` survives
sanitization. Firefox fetches the URL when rendering the content, which leaks
the viewer's IP address, user agent,…
### Summary
Nokogiri's CRuby native extension could leave a Ruby wrapper pointing to freed
memory when replacing the value of an existing XML attribute via
`Nokogiri::XML::Node#[]=` or `Nokogiri::XML::Node#lang=`. If Ruby code had
already accessed the attribute's child nodes, replacing the value could free
those native child nodes while their wrappers remained reachable. Using one of
those child nodes afterwards could cause an invalid read and a possible
segfault.
This is the same class of issue as
[GHSA-phwj-rprq-35pp](https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-phwj-rprq-35pp),
which was fixed for `Attr#value=` in Nokogiri 1.19.4.
Nokogiri 1.19.5 preserves all…
### Summary
Nokogiri's HTML5 serializer did not escape text inside `noscript` elements, or
`<` and `>` inside attribute values. When a document parsed with the default
options is serialized, a browser with scripting enabled can parse the output
into markup that Nokogiri's parser never produced. This is a mutation XSS for
applications that sanitize untrusted HTML with `Nokogiri::HTML5`, allow
`noscript` elements, and send the serialized output to browsers.
Nokogiri 1.19.5 escapes both, as the HTML standard's serialization algorithm
requires. Text inside `noscript` is still emitted unescaped for documents
parsed with `parse_noscript_content_as_text: true`.
Only the C extension (CRuby and…
## Summary
`Rails::HTML::PermitScrubber` restricts the `href` and `xlink:href` attributes
of SVG reference elements to local references. That check matched the
attribute value against `/^\s*[^#\s].*/m`. In Ruby `^` anchors to every line
start and `\s` matches newlines, so an all-whitespace value made the engine
rescan the value from each of its line starts, taking time quadratic in its
length.
- Versions affected: `>= 1.0.3, < 1.7.2`
- Not affected: `< 1.0.3`
- Fixed versions: `1.7.2`
CRuby 3.1 and earlier, and JRuby, are affected. CRuby 3.2 added linear-time
regexp matching, which makes the pattern linear. JRuby's regexp engine does
not.
## Impact
An attacker who supplies HTML to be…
## Summary
Loofah's HTML5 scrubbers could emit output that a browser parses into
different markup, allowing mutation XSS. Nokogiri < 1.19.5 did not escape `<`
and `>` in attribute values. When the output contained certain MathML, a
browser treated markup inside an allowed attribute as live HTML. The `:strip`
scrubber is affected in every browser. The `:prune` and `:escape` scrubbers
are affected in WebKit browsers such as Safari.
Nokogiri 1.19.5 escapes `<` and `>` in attribute values, as the HTML standard
requires. Loofah 2.25.3 requires Nokogiri >= 1.19.5.
## Impact
Applications that sanitize untrusted HTML with Loofah's HTML5 API
(`Loofah.html5_fragment`, `Loofah.html5_document` and…
## Summary
`Rails::HTML::SafeListSanitizer` (both the HTML4 and HTML5 variants) shares
one `Rails::HTML::PermitScrubber` across every `sanitize` call on the same
instance, assigning its `tags` and `attributes` on each call without
synchronization. Two concurrent `sanitize` calls with different `tags:` or
`attributes:` options can interleave, so one call is scrubbed with the other
call's allowlist.
- Versions affected: `< 1.7.2`
- Not affected: none
- Fixed versions: `1.7.2`
## Impact
Action View memoizes a single `SafeListSanitizer` per helper module and uses
it for every `sanitize` call in the process, so in a multi-threaded server
(for example Puma) all requests share the same…
## Summary
`Loofah::HTML5::Scrub.allowed_uri?` returns `true` for a `data:` URI whose
mediatype is written with the HTML5 named character reference `/` in place
of the solidus. Loofah reads that mediatype as malformed and falls back to the
allowed `text/plain`, while a browser decodes the reference and reads the same
URI as `text/html`.
## Details
`allowed_uri?` decodes HTML entities with `CGI.unescapeHTML`, which handles
numeric character references but not HTML5 named character references. `/`
therefore survives normalization, so the mediatype carries no `/`, fails the
MIME grammar, and falls back to the allowed `text/plain` under the WHATWG rule
for a malformed…
## Summary
`Loofah::HTML5::Scrub.scrub_css` rejected any property whose top-level Crass
tokens included a `:url` or `:bad_url`, and left `url` out of the allowed CSS
functions. Both checks ran only against the first parse of the `style`
attribute. When `scrub_css` rebuilt each property it emitted identifiers with
their CSS escapes decoded, so parentheses written as escapes — `url\28 …\29` —
hid the `url` token from the checks, and the rebuilt value was re-parsed into
a live `url()` that nothing checked again. An escaped property name, six-digit
escapes, escaped comments and escaped quotes all reached the same place.
## Impact
A sanitized `style` attribute could make a viewer's browser…
## Summary
Loofah's HTML5 sanitizer restricts the `href` and `xlink:href` attributes of
SVG reference elements to local references. That check matched the attribute
value against `/^\s*[^#\s].*/m`. In Ruby `^` anchors to every line start and
`\s` matches newlines, so an all-whitespace value made the engine rescan the
value from each of its line starts, taking time quadratic in its length.
CRuby 3.1 and earlier, and JRuby, are affected. CRuby 3.2 added linear-time
regexp matching, which makes the pattern linear. JRuby's regexp engine does
not.
## Impact
An attacker who supplies HTML to be sanitized can consume CPU out of
proportion to the input size, by sending an SVG reference element…
## Summary
`Loofah::HTML5::Scrub.allowed_uri?` returns `true` for a `javascript:` or
`vbscript:` URI whose scheme is split by a numeric character reference without
a trailing semicolon, followed by a character that `allowed_uri?` strips, such
as a space or tab. A browser ends the reference at that character and reads
`javascript: alert(1)` as `javascript: alert(1)`.
## Details
`allowed_uri?` strips control characters and whitespace before it decodes
numeric character references. Removing the character that ends a
semicolon-less reference lets the reference run on into the following text, so
`: alert` decodes as `Ϊ` (U+03AA) instead of a colon, and no scheme
is detected. The same applies…
### Summary
On JRuby, the `NONET` parse option did not block an external DTD or entity
whose system identifier used a short-form IPv4 host, such as `http://127.1/`
or `http://169.254.43518/`, or was an absolute path such as `/x.dtd` in a
document parsed from a network URL. An application that parses untrusted XML
with `DTDLOAD`, `DTDVALID` or `NOENT` could be made to send network requests,
potentially enabling SSRF.
Nokogiri 1.19.5 applies the `XML::Schema` check from
[GHSA-8678-w3jw-xfc2](https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-8678-w3jw-xfc2):
with `NONET` set, only local resources are loaded.
Only JRuby is affected. Default parse options are not…
Rails main lets ActiveSupport::EncryptedFile try comma-separated keys when decrypting, including keys supplied through RAILS_MASTER_KEY.
This is unreleased Rails 8.2 development functionality; Rails 8.1.4 still expects a single key.
Successful fallback reads do not establish support for multi-key writes or a complete credentials-editing workflow.
Try fallback reads with a disposable file
This standalone example generates its own keys and uses a separate example variable.
It does not load application credentials or read RAILS_MASTER_KEY.
require "active_support"
require "active_support/encrypted_file"
require "tmpdir"
require "fileutils"
variable = "SAELOUN_SYNTHETIC_KEYS"
raise "Use an…
In this episode, we look at several different examples of background jobs and how we can refactor them to handle situations where they get called twice from either a user action or a retry. We'll look at scenarios where we can prevent duplicate actions.
Learn how to restrict admin routes to VPN IP addresses on Heroku using Rails Advanced Route Constraints.
In some cases, GraphQL::Execution::Next rendered results for objects
whose .authorized? hooks indicated that the object was unauthorized.
The legacy interpreter (Execution::Interpreter) renders the original,
unauthorized object when authorized? raises GraphQL::UnauthorizedError
and Schema.unauthorized_object returns a replacement.
Returning false correctly renders the replacement.
sqlite3-ruby cast Ruby string lengths (long) to int before passing
them to SQLite. On 64-bit platforms, a string of 4 GiB (2^32 bytes)
or larger had its length wrap, so SQLite saw only the first length
mod 2^32 bytes. An attacker who controls the length of a SQL string
could truncate the query, for example dropping a trailing WHERE
clause. The affected paths are:
* the SQL passed to Database#prepare, #execute, and related methods
* String and Blob values passed to Statement#bind_param and related methods
* String and Blob values returned from SQL functions and aggregates
defined in Ruby (Database#create_function, #create_aggregate, and
related methods)
## AFTER PATCH
sqlite3 v2.10.2…
### Impact
`Extern#to_func(gvl: false)` releases Ruby's Global VM Lock (GVL) for
the whole duration of a WebAssembly call. Two WASI host paths that run
during such a call access Ruby objects without re-acquiring the GVL:
- Writes to stdout/stderr captured with `WasiConfig#set_stdout_buffer`
or `WasiConfig#set_stderr_buffer`, which append to the Ruby `String`
buffer;
- The block given to `WasiConfig#socket_addr_check`, which is called to
allow or deny socket operations.
A guest can trigger these paths, for example by writing to stdout. If
another Ruby thread runs while the call is in progress, the
unsynchronized access to the Ruby heap is undefined behavior. In
practice it has been…
Ruby values returned from Wasmtime through externrefs from function
results or hostcall arguments may be invalid references. This is due
to not making the Ruby garbage collector aware that the Ruby value
referenced by the externref is still in use.
## Impact
Using an externref returned from a Wasm function or an externref
provided as an argument to a hostcall could trigger a segmentation
fault crashing the host process.
Within the URI template implementation in Addressable, expressions
with several exploded variables (e.g., {+a*,b*}, {+a*,b*,c*},
{/a*,b*}, {?a*,b*}) generate regular expressions vulnerable to
excessive backtracking. A run of members can be divided among the
exploded variables in O(n^k) ways for k variables. When Template#match
or Template#extract is called with a maliciously crafted URI, this
can result in uncontrolled resource consumption, leading to denial
of service. With 2.9.0, {+a*,b*,c*} takes over 12 seconds to reject
1 KB of input on MRI Ruby 3.2.
Users of the URI parsing capabilities in Addressable but not the
URI template matching capabilities are unaffected. Users who only
call…
Addressable::IDNA.to_unicode decodes Punycode labels (labels beginning
with xn--) that contain characters which change how text is displayed
without being visible themselves. These include bidirectional controls
such as U+202E RIGHT-TO-LEFT OVERRIDE and U+2066–U+2069, zero-width
characters, and other invisible format characters.
Addressable::URI#display_uri uses to_unicode, so it can display a
host whose characters appear reordered or which looks identical to
a different host. For example, the host of http://xn--abcmoc-dm0c.com/
is displayed as abc, U+202E, moc.com, and the override makes
everything after abc render right to left.
Parsing, host, normalized_host and any connection made from…
Addressable::URI.normalize_path resolves some paths containing ..
segments to a different path than RFC 3986 does. When a path climbs
above the root before any other .. is resolved, the segments that
later .. segments should remove are instead kept:
/../../public/x/../../secret normalizes to /public/x/secret instead
of /secret. URI#normalize, URI#join, URI#== and URI#=== all use
this normalization. An application that checks a normalized Addressable
path against an allowlist, but sends the original URI to something
that resolves it according to RFC 3986, can be made to allow a
request for a path outside the allowlist.
Addressable::IDNA.to_unicode, in the pure-Ruby IDNA implementation,
decodes Punycode labels (labels beginning with xn--) that are not
valid A-labels. Those labels are returned in decoded form, when
they should be returned unchanged. That includes:
* labels that decode to only ASCII characters, e.g. xn--google-
decodes to google;
* labels that decode to text that doesn't convert back to the same
label (RFC 3490 §4.2), e.g. text that isn't in normalized form.
Addressable::URI#display_uri uses to_unicode, so for
https://xn--google-.example.org/ it displays the host google.example.org,
while #host, #normalized_host and any connection made from the URI
use xn--google-.example.org. An…
Within the URI template implementation in Addressable, templates
that put several variables in one expression generate regular
expressions vulnerable to excessive backtracking:
1. Expressions with any operator other than + and # (e.g., {/owner,repo,ref},
{.a,b}, {a,b,c}, {;a,b,c}). The values are joined by an optional
separator, so a run of characters can be divided among the variables
in O(n^k) ways for k variables.
2. Expressions with the + or # operators and many variables (e.g.,
{+v1,v2,v3,v4,v5,v6,v7,v8,v9,v10}). A failing match is exponential
in the number of variables.
3. Several single-variable expressions next to each other (e.g.,
{+a}{+b}{+c} or {a}{b}{c}),…
Within the URI template implementation in Addressable, templates
using the * (explode) modifier with the /, ., ;, ? or & operators
(e.g., {/segments*}, {.ext*}, {;params*}, {?query*}) generate
regular expressions vulnerable to catastrophic backtracking. When
Template#match or Template#extract is called with a maliciously
crafted URI that starts with the operator's leading character,
matching takes exponential time, leading to uncontrolled resource
consumption and denial of service. An input under 60 bytes is
enough to occupy a thread for minutes.
GHSA-h27x-rffw-24p4 (CVE-2026-35611) reported this pattern as
fully remediated in 2.9.0. The 2.9.0 fix only made the joiner
between exploded…
Addressable::URI.normalize_path takes time quadratic in the number
of nested dot segments in a path (e.g., "/" + "a/" * n + "../" * n,
which for n = 3 is /a/a/a/../../../), because it removes one dot
segment per pass over the whole path. URI#normalize, URI#normalized_path,
URI#==, URI#=== and URI#join all call it. Normalizing, comparing or
joining a URI taken from user input can result in uncontrolled
resource consumption, leading to denial of service. On 2.9.0,
normalizing a 34 KB path takes about 3.5 seconds on MRI Ruby 4.0,
and the cost grows with the square of the path length.
This affects every Ruby implementation, including MRI Ruby 3.2 and
later. URI.parse by itself is unaffected,…
We are, disturbingly, in a Gilded Age 2.0.0 era and, like the corrupt industrial tycoons of years past, we have tech tycoons forcing LLMs and data centers upon us. Software should be used to enhance and enrich the lives of staff and customers instead of extracting all their worth.
Worse, we have to be much more diligent about what we pay for and be quick to cancel when they disregard their engineers and customers. As was — and still is the case — where you must Avoid Zoom, you need to cancel any/all services with Proton as well. To quote from 404 Media:
Proton, the company behind ProtonMail and other privacy-focused products, told staff on Monday it would be…
Two jobs finish successfully. One account update disappears.
Suppose an account has 100 credits. You enqueue two jobs to add 10 and 20. Both jobs finish without an exception, but the account ends with 120 credits.
Each job runs this code:
class AddCreditsJob < ApplicationJob
def perform(account_id, amount)
account = Account.find(account_id)
account.update!(credits: account.credits + amount)
end
end
Assume credits is an integer column, with no optimistic locking or custom database rule protecting the update. These are application credits; the example doesn't model a payment system.
Two workers can execute the job in this sequence:
Job A (+10) Job B (+20)
read 100
…
The idea for Svet came from a private repository at work, where checking filename case helps catch portability problems like Logo.svg and logo.svg colliding on a case-insensitive Windows filesystem. These checks are implemented in pure JavaScript. That seemed like a useful job for a reusable repository linter.
I went looking for an existing tool.
Tool
Why it isn’t enough here
ESLint
Can check filenames with a processor; byte-level checks and Git snapshots need custom plumbing.
Oxlint
Arbitrary non-JS/TS files don’t reach its lint rules.
Biome
Checks supported languages, not arbitrary repository…
I had a Raspberry Pi 4B I thought might be dead. It wasn’t. I’m having a bunch of building work done and of course, the thermostats do not work with Apple HomeKit.
So the Pi now runs Homebridge, which puts our underfloor heating thermostats (and also now Samsung TVs) in Apple Home. For good measure it also runs Pi-hole, which blocks ads and trackers for every device in the house.
Gear required
I thought the Pi was dead
First I checked the cheap parts. SD cards and power supplies fail far more often than the Pis themselves.
- Removed and replaced…
`Trilogy#escape` produces unsafe output for Strings in Shift_JIS,
Windows-31J, Big5, GBK or GB18030. In those encodings, the continuation
byte of a multi byte character can be in the ASCII range. `escape` was
intended for strict ASCII supersets and did not handle this correctly,
which allowed SQL injection.
## Details
Applications are affected in two cases:
1. On any connection charset, including the default `utf8mb4`, when
an attacker can influence the encoding of a String the application
escapes.
2. On connections configured with `encoding: sjis`, `cp932`, `big5`,
`gbk` or `gb18030`. These connection encodings never worked
correctly, are no longer supported, and now raise…
Rack::Request#host_with_port, and therefore #base_url and #url,
return the raw authority string when it fails validation. An
attacker-controlled Host, X-Forwarded-Host, or Forwarded:
host= value containing characters that are not permitted in an
authority is embedded verbatim in URLs generated by the application.
This completes the fix for CVE-2026-34835 (GHSA-g2pf-xv49-m2h5),
which made #host and #hostname return nil for an invalid authority
but left this path unchanged.
## Impact
Applications that build absolute URLs, redirects, or origin-dependent
checks from req.url, req.base_url, or req.host_with_port, and that
do not enforce a host allowlist, may emit URLs containing…
Rack::Multipart::Parser reads the request body in chunks of
rack.multipart.buffer_size (1 MiB by default) into an internal
scan buffer. Parsed bytes are only discarded from that buffer
when a chunk ends inside a part body. A request whose parts are
sized so that every chunk ends inside, or immediately after, a
part's headers is never compacted, so the buffer grows to the
size of the request.
This results in a denial of service condition through memory
exhaustion for applications that parse multipart requests.
## Impact
Any application that parses multipart requests with Rack is affected
in its default configuration. An unauthenticated attacker can cause
a single request to hold memory…
The guard added for CVE-2026-26961 (GHSA-vgpv-f759-9wx3), which
rejects multipart requests with more than one boundary parameter,
only inspects the part of the Content-Type header after the boundary
it extracts. A Content-Type whose first boundary parameter is empty
or begins with a quote, semicolon, or comma is skipped by the
extraction pattern, so a second boundary parameter is accepted
silently and Rack parses the body with a boundary that a conforming
parser would not derive from the header.
## Impact
As with CVE-2026-26961, an attacker can craft a multipart request that
a web application firewall or other inspecting proxy in front of a
Rack application interprets differently from…
Rack::Static normalizes the request path with Utils.clean_path_info
when deciding which file to serve, but matches header_rules against
a path that is only URL-decoded. A request containing dot segments,
raw or percent-encoded, is served the normalized target file while
the header rules for that file's location are not applied.
This allows an attacker to receive a protected static file without
the security headers configured for it.
## Impact
Applications that use Rack::Static header_rules to attach
security-relevant headers to particular paths or file types are
affected. An unauthenticated attacker who knows the path of a
protected file can request it through a dot segment path…
Batching the Billing Ledger: Small Trade-Offs with High Impact is a great article about “systems assembly”, and microbilling is part and parcel of nearly any web app today, certainly when your users call into LLM models. Gotta keep’em tokens counted!
Since the start of the agentic era, I’ve been curious about “how much software” we need for workloads like this. After all, my doctrine is less stuff and this looked like a use case too perfect to pass on. Thank you, Vicente, for the pitch-perfect nerdsnipe!
So, here’s bab - but I am getting ahead of myself. First, we need the parts.
I am currently available for contract work.
Hire meto help make your Rails app better!
…
T…
With the meta protocol, Dalli sends keys containing whitespace, control
characters or non-ASCII characters base64-encoded. When `incr` or `decr`
is called with an initial value and the key doesn't exist yet, memcached
(verified with 1.6.45) creates the item under the decoded, raw key bytes
and later echoes those raw bytes in `mg` replies. Dalli's pipelined
`get_multi` parser trusted that echoed key, so a key containing CRLF
split the reply header and the rest of the key was parsed as further
replies. With a crafted key in the same `get_multi` batch, the block form
of `get_multi`, `get_multi_cas`, and multi-server `get_multi` returned an
attacker-chosen value for another requested key, which…
Hi, it’s Claudio Baccigalupo. Don’t be scared 🎃 this week’s changes to the Rails codebase are all treats. Let’s explore them.
New guide to multiple databases in Active Record
As part of the Rails Foundation effort, the refreshed guide is live, tackling the three-tier production configuration that new apps get by default.
Tuning performance for deployment guide open for review
A PR that updates the guide to performance tuning is open. Your feedback is helpful, especially if you have experience tuning Rails apps in production.
Don’t load Capybara and Selenium on boot in new applications
The generated Gemfile now declares capybara and selenium-webdriver with require: false. ActionDispatc…
While working on Svet, a Git and filesystem linter, I had a conversation with GitHub Copilot CLI about a flag.
The tool checks files. We were adding automatic fixes. Naturally, I wanted to preview changes before allowing the tool to write them.
The first suggestion was --fix-dry-run. I preferred a global --dry-run that could apply to both checking and fixing.
Simple enough. Except checking already doesn’t write anything. What should a dry run of a check do?
One answer is that a dry run prohibits writes. Run all the checks, report the findings, and leave the files alone. With --fix, calculate the fixes but don’t apply them. Without --fix, the flag changes nothing.
That’s a consistent…
I’ve put together “Which Rails Layer Owns This?”, a six-page reference for when production Rails does something you didn’t expect.
Use it to find the layer to inspect: request handling, identity, loaded records, database writes or background work. Each page gives you checks to run, evidence to collect and links to the full explanations.
Download the attached PDF and keep it beside your logs the next time you’re tracing a production bug.
Summary
- The private keys of eight eMudhra intermediate certificates were compromised. A network attacker could use them to impersonate websites and services that JetBrains tools connect to.
- JetBrains IDEs, the Toolbox App, Gateway, and ReSharper are affected because they use the trust store of the bundled JetBrains Runtime (JBR). For IDEs, the embedded browser (JCEF) is also affected. Operating system updates don’t protect against this issue.
- The Toolbox App and JetBrains IDEs use a background service,
jetbrainsd. It has its own built-in list of trusted certificates and is fixed separately, in jetbrainsd 0.9.9728.
- At the time of publishing, we are not aware of any…
Available fixed versions
The issue is fixed in the following versions:
-
IntelliJ IDEA: 2026.3 RC, 2026.2.4, 2026.1.5.1, 2025.3.6.2, 2025.2.6.4.
-
PyCharm: 2026.3 RC, 2026.2.4, 2026.1.5.1, 2025.3.6.2, 2025.2.6.3.
-
PhpStorm: 2026.3 RC, 2026.2.4, 2026.1.5.1, 2025.3.6.2, 2025.2.6.3.
-
RubyMine: 2026.3 RC, 2026.2.4, 2026.1.5.1, 2025.3.6.2, 2025.2.6.3.
-
WebStorm: 2026.3 RC, 2026.2.4, 2026.1.5.1, 2025.3.6.2, 2025.2.6.3.
-
Rider: 2026.3 EAP 9, 2026.2.4, 2026.1.5.3, 2025.3.5.3.
-
CLion: 2026.3 RC, 2026.2.4.1, 2026.1.5.2, 2025.3.6.3, 2025.2.6.3.
- DataSpel…
The 2025.2.6.* updates for all IDEs now…
RWX Agent-native CI/CD with Dan Manges
This week on Remote Ruby, Chris, Andrew, and David welcome Dan Manges, co-founder of RWX and former CTO of Braintree, to explore a new approach to continuous integration built for the age of AI coding agents. Dan explains how RWX uses intelligent caching, graph-based execution, and isolated agent sandboxes to dramatically improve development workflows. They dive into flaky tests, automated code validation, security concerns, and why traditional CI platforms may be struggling to keep up with AI-driven development. Plus, they discuss how engineering teams can test changes before opening pull requests, reduce infrastructure costs, and rethink the way software gets built and shipped. Hit…
This post has an important update, please read it.
Two things below turned out to be wrong: the force_encoding in the proof of concept is not
necessary, and the Rails parameter encoding check is not the mitigation I
thought it was. This is exploitable with a single request against an app
that does nothing unusual, if there is an Active Record query with two
slots using `escape`, the app can be exploited, so read the update
at the end of the post.
Imagine spending weeks preparing for an important demo. You’ve rehearsed every flow, verified every functionality, created the perfect test data, and tested the application one last time before walking into the meeting. You know exactly which buttons you’ll click, what features you’ll highlight, what words you’re going to say, and even where you’ll pause for …
Continue reading Beyond the Perfect Demo(I’m still looking for my next gig. You can get in touch by emailing job@ryanbigg.com.)
As someone with infinite spare time, I feel qualified to share my opinion on AI by carving a post out of the raw marble of my keyboard with my bare hands.
I wrote this on LinkedIn at the start of this month:
After interviewing now at a few places these things are simultaneously true:
- EVERYONE is on the hype train
- Nobody knows how to REALLY use AI and people are trying to figure that out
- There are a lot of experiments that sound promising on the cover, but nothing past “we’re gonna go THATAWAY”
- There is an unlimited budget for this exploration, until there won’t be
I’ve now…
Last weekend we were camping. Fall break. Glorious northern Indiana weather. My kids were fishing with grandpa, so I went for a walk around the campground (to check out the other rigs as dads do). Somewhere along the way I opened T3 and told Nunebot to do another run. Merge, merge, merge, run it again, merge, merge. Just getting some exercise and enjoying the fall weather.
Between Saturday and Sunday I merged 113 pull requests. Definitely the most in a two day span in the two decades I’ve been doing this.
I know how that sounds. But I honestly like this more than games, mindless scrolling, or Instagram. It’s relaxing for me. Read a PR, it makes sense, merge. Little dopamine hit. Next.
I’ve…
you can train someone to be better. You can’t make someone care about things they don’t intrinsicly value.
naildrivin5.com – David Bryant Copeland’s Website: You Cannot Trust the AI Code Generator
I’ve built five spell-checking plugins in the last week. This is not an unhealthy interest in spelling. The cost of rolling tools out is the cost of tokens + my time supervising AI. So, before adding another language runtime, package manager, configuration file, and CI job to your project, consider spending an hour vibe coding the missing plugin.
This blog used to run a Python spell checker that took more than three minutes. It’s a Jekyll blog, which is Ruby. Why was I maintaining a Python environment to tell me that I misspelled “hello”? I replaced it with a RuboCop plugin. Spelling became another cop, with the same configuration, exclusions, command, and CI integration as the rest of the…
A Rust project…
#824 — October 29, 2026
Read on the Web
🔒 Last Friday we sent out a special issue about some CRuby security releases which fixed a few nasty regexp bugs. If you haven't updated yet, it's worth a read to see if you're affected and what to check!
__
Your editor, Peter Cooper
Ruby Weekly
How Fast Can You Actually Patch Ruby? — When the Ruby security fixes shipped last week, Dennis timed how long they took to reach version managers, Docker images, CI services and hosts. The core team's image took eight minutes, while Docker's official image (Rails and Kamal's default) took ~16 hours.
Dennis Paagman
💡 Things are improving,…
Superpowers Ruby becomes RubyPowers in version 8.0.0. Existing users need to migrate their plugin installation and update saved skill references; existing plans and specs remain supported.
Most of what can be read here is around and for Ruby on Rails. My main apps still are powered by it. But monopolies are never good. Hanami is the one I get most excited about.
It is why I extracted the core features (pulling SVGs from remote repository, transforming filenames, etc.) of Rails Icons into a Ruby gem, called Icons, so other frameworks can have all the icons in their apps as well.
Hanami Icons is the next wrapper of the Icons gem for… drumroll… Hanami! It gives you the same interface as Rails Icons too, whether that is Heroicons, Lucide, Tabler or even a folder of your own SVGs.
<%== icon "check", class: "text-gray-500" %>
<%== icon "apple", library: "simple_icons", class:…
if you have seen Rails icons, you…
Originally published on Ruby Central.
Relaunching the changelog
It has been a long time since the last Ruby Central Changelog, and we’re bringing it back. Ruby Central’s open source team operates RubyGems.org, a service many of us rely on every day, and supports the packaging ecosystem around it. The Changelog is where we will share that work. Each issue will cover what is changing, why it matters, and where you can get involved.
This is the first issue edited by our new Community Manager, Kirk Haines, who joined Ruby Central in August. If you missed his introduction in the Ruby Central August README, the short version is that Kirk has been using Ruby since 2001 and is glad to give back…
Relaunching the Changelog
It has been a long time since the last Ruby Central Changelog, and we’re bringing it back. Ruby Central’s open source team operates RubyGems.org, a service many of us rely on every day, and supports the packaging ecosystem around it. The Changelog is where we will share that work. Each issue will cover what is changing, why it matters, and where you can get involved.
This is the first issue edited by our new Community Manager, Kirk Haines, who joined Ruby Central in August. If you missed his introduction in the Ruby Central August README, the short version is that Kirk has been using Ruby since 2001 and is glad to give back to the language and the community that…
Starting a new Solidus store involves only a few tweaks to support my preferred
JS bundler, as well as a number of decisions regarding hosting, databases,
assets and more. This is the sequence of steps I follow to launch a fresh
store.
For the purposes of this demo, I have intentionally left out some of the
details regarding configuring pipelines in Heroku, and distinct public/private
S3 buckets in AWS.
Please read the documentation I’ve provided in those sections to ensure you are
complying with best practices and procedures to keep your app safe and secure.
Prerequisites
Before you get started, make sure to have Node, Docker, and the Heroku CLI
installed. You will also need to set up an…
When we onboard a new event source, the system looks at a few sample events on the site and works out where each field lives on the page: title, description, date, time, price, image, venue, and ticket link. An admin then reviews the extracted samples before the source goes live. Sometimes a value is wrong. The extraction grabbed footer text instead of the event title, or pulled in a paragraph of extra text around a price, or found nothing at all.
The obvious fix is to let the admin edit the value and save it. That fixes the one event on the screen and nothing else, because the next event from the same site is still extracted by the same wrong rule. An edit is far more useful when we read…
I’ve kept a notebook page since, oh, 2013, of common exceptions I’ve encountered ever since I started building production Rails applications. It’s a big one.
When you put a web application on the internet, it’s going to get hit with a firehose of garbage requests: scanners and bots that are trying to discover malicious vulnerabilities, and scanners and bots trying to do something useful, like put your website in a search engine, but are built oddly.
And when you’re using an exception monitoring product like Sentry or AppSignal or whatever, as you should, you’re going to start being alerted to exceptions from this natural background noise of the firehose of garbage.
…
Authors: Pavel Grinchenko, Frontend Engineer, and Travis Turner, Tech Editor
Topics: DX, Open Source, TypeScript, JavaScript

Typed-channel is a library for typed messages and requests across workers, iframes, tabs, and event buses.
typed-channel makes postMessage type-safe in about 350 bytes. You define message names and payloads once, and TypeScript checks both sides. The same API works across Web Workers, iframes, tabs, and in-page event buses. Version 0.11 adds cancelable requests: cancel a request, and its handler stops too.
Some of you may not know this, but I’m a DJ in my free time. I founded Floppy Disco, a music collective to bring the magic of Disco and House to Berlin, and I’ve played clubs all over here and in other places in Europe. I love DJing, and my favourite part is finding new music and playing it for people.
Preparing my tracks, however, has never been truly fun. It rather felt like admin: fixing beat grids that were often wrong, fighting clunky software like rekordbox, building playlists by hand, correcting tags and genres, updating playlists, and deciding whether ratings or colours should stand for energy, mood or vocals.
So I said enough, and built Solco to do that work for me. Today it…
Work continues on closing all our open parentheses in the lead-up to the next Hanami release.
I started this week fixing a regression in our body params logging. Thanks @Drowze for reporting it! In response to some good feedback from Paweł, I also filed an issue about possibly making body params logging a configurable thing in the future.
While I was working in this space, I decided it was time we expanded Dry Logger’s log filtering, including substring matching and first-class regex support. This is close to being ready to go, I just need to reply to some good feedback on the PR from Michael.
Aaron voiced a perplexing inconsistency in how predicate functions are used as keyword…
JP Camara joins the show to explore background job coordination, real-time collaboration, and the opportunities he sees for Ruby and Rails as AI changes how developers work.
First, JP shares the story behind bringing batches to Solid Queue, from an early proposal to the conference conversations and community interest that helped move the feature forward. He explains how batches let developers treat a group of jobs as a unit, track success and failure, and trigger callbacks when processing finishes—making it easier to coordinate workflows that span many background jobs.
JP also discusses the implementation challenges behind the feature, including database transactions, jobs…
RubyGems and Bundler 4.1.0.beta3 include enhancements, bug fixes and features.
To update to the latest RubyGems you can run:
gem update --system [--pre]
To update to the latest Bundler you can run:
gem install bundler [--pre]
bundle update --bundler=4.1.0.beta3
Release Notes
RubyGems
Enhancements:
- Installs bundler 4.1.0.beta3 as a default gem.
Bug fixes:
- Skip executable removal gracefully when the bin directory is missing. Pull request #9963 by Evan Li and Hiroshi SHIBATA
- Fix file locks letting multiple processes in when lock files are removed. Pull request #9930 by Richard Janita and Hiroshi SHIBATA
- Turn off echo before printing the password prompt. Pull request #9969…
Bundler
Changes already released in 4.0.x are not repeated here.
Features:
- Rename the
from: and to: keys of override to in: and with:. Pull request #9979 by Hiroshi SHIBATA
- Record overrides in Gemfile.lock (
OVERRIDES…
Bug fixes:
- Make
Bundler::Source::Rubygems#hash agree with …
Our Software Delivery Manager's thoughts on agent-ready Rails apps and project management after Rails World.
Continue Reading
On October 7, I attended the 1682 Conference in Philadelphia and got to hear
many great speakers talk about how AI is changing customer experience. One talk that stuck with me was “The PB&J Principle: What every growing AI engine needs” by Mike Pitone from
Wawa.
If you live anywhere near Philadelphia, you probably have a few Wawa memories. Maybe it’s coffee on the way to work, a
late-night stop with friends, or grabbing hoagies before heading down the shore. After enough visits, you have an order
you barely have to think about. Mine is the Meatball Parmesan hoagie.
What you might not know is how complicated a convenience store gets when orders come in from the counter, the
kiosks, and…
Before adopting spec-driven development (SDD), I spent a lot of my time renegotiating with AI agents before we reached a plan I was happy with. Worse, without persisted artifacts or structured task tracking, agents would routinely suffer context window decay.

Illustration of a developer looking overwhelmed while surrounded by floating panels of code.
Without SDD, my AI-assisted work wasn’t leaving durable records of what had been implemented. When I iterated on an AI-implemented feature that was undocumented, it was hard to backtrack on the reasoning behind the code. Not to mention the hassle when picking up a task in a new session! Lastly, if I needed uncommitted, deleted code rewritten,…
Over in the OmbuLabs.ai blog, our post on the ReAct pattern in Ruby walked through building an agent that reasons about a task, calls tools, and acts on the results, all inside a single Ruby script. That’s fine for a command-line experiment. It’s a different story once that same agent needs to run inside a Rails app, triggered by a web request, where a multi-step reasoning loop can easily take longer than any request should stay open.
The obvious fix is to move the agent into a background job. Sidekiq already handles work that takes a while, so an agent’s loop of thinking, searching, and acting should fit right in. It does, mostly, but a job’s retry semantics were built around retrying a…
I firmly believe that we still need to read some of the code.1 It’s suboptimal to read all of the code, but I consider it irresponsible to read none of it. I won’t explain why now because that would fill several more articles. I just want to share this upfront because it clarifies where I’m coming from. But I am trying to be smart about it2, and I’ve found the quote in the title very useful: “...
I recently joined Errol Schmidt on his Tech for Humans podcast for a conversation about Ruby on Rails, AI, running a software consultancy, open source, and somehow… punk rock.
We started by revisiting my recent Rails World keynote and why I think engineers should spend more time improving the environments they work in. From there, we wandered into what it’s like running Planet Argon in an increasingly AI-assisted industry, why Rails upgrades keep getting postponed, and whether AI might change how we think about open source dependencies.
We also talked about my early days as an anarcho-punk kid experimenting with Linux, how that eventually led to starting a software consultancy, and why…
When I tried to update an app to Ruby 4.0.9 last Friday, the official Docker community image wasn’t available yet. The release fixed vulnerabilities GitLab had found, but I had to wait before I could deploy.
A few months ago, a government Rails site was hit by an ActiveStorage vulnerability eight hours after the patch went out. Last week, PHP2Shell was being exploited before it was announced. Waiting for a Docker image can eat up the time you have to respond.
When Ruby 4.0.9 became available
I took every installer and version manager listed on the official installation page on ruby-lang.org, then added container images, CI providers and hosting platforms I could think of. For each, I…
All…