From 48911bdaac95a48bc54ec2f16170d64dab50ccc0 Mon Sep 17 00:00:00 2001 From: Igor Fedoronchuk Date: Wed, 30 Sep 2026 12:17:39 +0200 Subject: [PATCH 1/5] Keep README gifs and dev-only paths out of the packaged gem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `git ls-files` with no filter published everything tracked, so the gem is 1.3 MB of which ~12 KB is the actual library. The bulk is two README demo gifs: screen/example_aa_filters_ordering.gif 722 KB screen/example_aa_filters_visibility.gif 656 KB 98% of every download, for images referenced from README.md as repo-relative paths — GitHub renders them out of the repo and nothing ever reads them from the package. The test/spec/features part of the filter is prophylactic: master has no spec/ today, but without a filter the first suite added ships to consumers automatically. Drops `s.test_files` while here: RubyGems deprecated it, and it pointed at paths that do not exist on master. Packaged: 11 files / 1.3 MB -> 9 files / 12 KB. --- active_admin_filters_visibility.gemspec | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/active_admin_filters_visibility.gemspec b/active_admin_filters_visibility.gemspec index 3c19831..e64ad11 100644 --- a/active_admin_filters_visibility.gemspec +++ b/active_admin_filters_visibility.gemspec @@ -13,8 +13,12 @@ Gem::Specification.new do |s| s.add_dependency 'activeadmin' - s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + # Dev-only paths stay out of the package. `screen/` is two README + # demo gifs totalling 1.38 MB — 98.5% of the published gem, for + # images nobody sees outside GitHub. `s.test_files` is dropped: + # RubyGems deprecated it, and it pointed at files this gem no longer + # ships. + s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|screen|\.github)/}) } s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ['lib'] end From f83adec6f427d95fb2d168da148f78fc92285cdd Mon Sep 17 00:00:00 2001 From: Igor Fedoronchuk Date: Wed, 30 Sep 2026 14:10:31 +0200 Subject: [PATCH 2/5] Switch spec.files to a whitelist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reject list only removes directories someone remembered to name. That is how spec/, .github/, screen/ and img/ got published in the first place — each needed a new pattern, and none was added until an audit went looking. A whitelist inverts the default: a new directory in the repo does not reach consumers until it is listed. Same shape the sibling gems activeadmin-oidc and credit_card_validations already use. Drops the remaining dev-only files the reject form kept: active_admin_filters_visibility.gemspec Gemfile package.json Rakefile Packaged: 9 -> 5 files. The runtime payload — everything under lib/, app/, vendor/, config/ and exe/ — is byte-identical to before, verified by diffing the built .gem both ways. --- active_admin_filters_visibility.gemspec | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/active_admin_filters_visibility.gemspec b/active_admin_filters_visibility.gemspec index e64ad11..300a4eb 100644 --- a/active_admin_filters_visibility.gemspec +++ b/active_admin_filters_visibility.gemspec @@ -13,12 +13,11 @@ Gem::Specification.new do |s| s.add_dependency 'activeadmin' - # Dev-only paths stay out of the package. `screen/` is two README - # demo gifs totalling 1.38 MB — 98.5% of the published gem, for - # images nobody sees outside GitHub. `s.test_files` is dropped: - # RubyGems deprecated it, and it pointed at files this gem no longer - # ships. - s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|screen|\.github)/}) } + # Whitelist, not a reject list: a new directory in the repo does not + # reach consumers until it is named here. The reject form needs a new + # pattern every time the repo grows one, and that is how 1.3 MB of + # README gifs under screen/ ended up published in the first place. + s.files = Dir['lib/**/*', 'app/**/*', 'README.md', 'LICENSE'] s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ['lib'] end From 585a19aa3040757598e9f33a461cd294fbcb85d2 Mon Sep 17 00:00:00 2001 From: Igor Fedoronchuk Date: Wed, 30 Sep 2026 14:36:30 +0200 Subject: [PATCH 3/5] Select the whitelist through git rather than Dir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Dir[...]` globs the working tree, so any untracked or generated file under lib/, app/ or vendor/ would be published in a release — the build artifact depended on the releaser's local checkout. The reject form it replaced was tracked-only; this restores that property while keeping the whitelist. `git ls-files -- ` also returns files only, where `Dir["**/*"]` returns directory entries too, so `files` no longer carries entries RubyGems just ignores. Built .gem is byte-for-byte the same file list as the Dir[] version. --- active_admin_filters_visibility.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active_admin_filters_visibility.gemspec b/active_admin_filters_visibility.gemspec index 300a4eb..60e207c 100644 --- a/active_admin_filters_visibility.gemspec +++ b/active_admin_filters_visibility.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| # reach consumers until it is named here. The reject form needs a new # pattern every time the repo grows one, and that is how 1.3 MB of # README gifs under screen/ ended up published in the first place. - s.files = Dir['lib/**/*', 'app/**/*', 'README.md', 'LICENSE'] + s.files = `git ls-files -z -- lib app README.md LICENSE`.split("\x0") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ['lib'] end From 6fa6742e4366a6921aba027c7bf297ef8caed2d5 Mon Sep 17 00:00:00 2001 From: Igor Fedoronchuk Date: Wed, 30 Sep 2026 14:49:42 +0200 Subject: [PATCH 4/5] Whitelist every root a Rails engine loads from, not just the ones in use MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A whitelist fails quietly: `git ls-files -- config` against a tree with no config/ exits 0 and prints nothing, so the day someone adds `config/initializers/foo.rb` the gem installs, boots, and the initializer never runs. Nothing in `gem build` warns. That is not hypothetical for this family of gems — active_admin_datetimepicker's Ransack predicates live in exactly such an initializer, and its filters return no results without them. So list every root Rails::Engine loads from (lib app vendor config exe bin) in all of them, present or not, instead of only the ones that happen to exist today. No package changes: the built .gem is identical in every gem. --- active_admin_filters_visibility.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active_admin_filters_visibility.gemspec b/active_admin_filters_visibility.gemspec index 60e207c..fae2d42 100644 --- a/active_admin_filters_visibility.gemspec +++ b/active_admin_filters_visibility.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| # reach consumers until it is named here. The reject form needs a new # pattern every time the repo grows one, and that is how 1.3 MB of # README gifs under screen/ ended up published in the first place. - s.files = `git ls-files -z -- lib app README.md LICENSE`.split("\x0") + s.files = `git ls-files -z -- lib app vendor config exe bin README.md LICENSE`.split("\x0") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ['lib'] end From 5199f3afaf44566c1a4744c3c3d2b173c4b76e82 Mon Sep 17 00:00:00 2001 From: Igor Fedoronchuk Date: Thu, 1 Oct 2026 13:02:04 +0200 Subject: [PATCH 5/5] Add db to the packaged-file whitelist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The whitelist names every root a Rails::Engine loads from so the list does not silently lose a directory the day one is added. db was missing: railties-8.1.4 lib/rails/engine/configuration.rb registers paths.add "db" paths.add "db/migrate" paths.add "db/seeds.rb" as engine roots, and `rake :install:migrations` copies out of db/migrate — so a migration added later would have installed with the gem and been invisible. public/ is deliberately not added: it is registered by Rails::Application, not Rails::Engine. No change to the current artifact. `gem build` before and after produces the same 5 files: LICENSE README.md app/assets/javascripts/active_admin_filters_visibility.js lib/active_admin_filters_visibility.rb lib/active_admin_filters_visibility/version.rb `git ls-files -- db` against a tree with no db/ exits 0 and prints nothing. --- active_admin_filters_visibility.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active_admin_filters_visibility.gemspec b/active_admin_filters_visibility.gemspec index fae2d42..a4c5f86 100644 --- a/active_admin_filters_visibility.gemspec +++ b/active_admin_filters_visibility.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| # reach consumers until it is named here. The reject form needs a new # pattern every time the repo grows one, and that is how 1.3 MB of # README gifs under screen/ ended up published in the first place. - s.files = `git ls-files -z -- lib app vendor config exe bin README.md LICENSE`.split("\x0") + s.files = `git ls-files -z -- lib app vendor config db exe bin README.md LICENSE`.split("\x0") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ['lib'] end