From 877d8c903ea8df9ea60a50d561192dc72e7596f1 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Fri, 10 Apr 2026 10:43:56 +0100 Subject: [PATCH 01/28] build: remove unused pysch dependency --- Gemfile | 4 ---- Gemfile.lock | 2 -- config/initializers/psych.rb | 22 ---------------------- 3 files changed, 28 deletions(-) delete mode 100644 config/initializers/psych.rb diff --git a/Gemfile b/Gemfile index e8fd7fba0e..9af49f31c4 100644 --- a/Gemfile +++ b/Gemfile @@ -21,10 +21,6 @@ group :default do gem 'faraday-multipart' gem 'rest-client' # Deprecated, but still used in some places, replace with Faraday where possible - # Fix incompatibility with between Ruby 3.1 and Psych 4 (used for yaml) - # see https://stackoverflow.com/a/71192990 - gem 'psych', '< 4' - # State machine gem 'aasm' gem 'after_commit_everywhere', '~> 1.0' # Required by AASM diff --git a/Gemfile.lock b/Gemfile.lock index 9e483c2e0d..fcd49354c8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -378,7 +378,6 @@ GEM pry (>= 0.13, < 0.17) pry-rails (0.3.11) pry (>= 0.13.0) - psych (3.3.4) public_suffix (7.0.5) puma (7.2.0) nio4r (~> 2.0) @@ -714,7 +713,6 @@ DEPENDENCIES prettier_print pry-byebug pry-rails - psych (< 4) puma rack-acceptable rack-cors diff --git a/config/initializers/psych.rb b/config/initializers/psych.rb deleted file mode 100644 index 472b3025db..0000000000 --- a/config/initializers/psych.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -Rails.application.configure do - # Fix for Psych::DisallowedClass: Tried to load unspecified class - config.active_record.yaml_column_permitted_classes = - Array(config.active_record.yaml_column_permitted_classes) + - %w[ - Symbol - ActiveSupport::HashWithIndifferentAccess - ActiveSupport::TimeWithZone - ActiveSupport::TimeZone - HashWithIndifferentAccess - RequestType::Validator::ArrayWithDefault - RequestType::Validator::LibraryTypeValidator - RequestType::Validator::FlowcellTypeValidator - ActionController::Parameters - Set - Range - FieldInfo - Time - ] -end From 960f766eccbaaf6c950343054ad8e0d1ab2a19d2 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Fri, 10 Apr 2026 10:49:33 +0100 Subject: [PATCH 02/28] revert: restore deleted file - turns out it is used --- config/initializers/psych.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 config/initializers/psych.rb diff --git a/config/initializers/psych.rb b/config/initializers/psych.rb new file mode 100644 index 0000000000..472b3025db --- /dev/null +++ b/config/initializers/psych.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +Rails.application.configure do + # Fix for Psych::DisallowedClass: Tried to load unspecified class + config.active_record.yaml_column_permitted_classes = + Array(config.active_record.yaml_column_permitted_classes) + + %w[ + Symbol + ActiveSupport::HashWithIndifferentAccess + ActiveSupport::TimeWithZone + ActiveSupport::TimeZone + HashWithIndifferentAccess + RequestType::Validator::ArrayWithDefault + RequestType::Validator::LibraryTypeValidator + RequestType::Validator::FlowcellTypeValidator + ActionController::Parameters + Set + Range + FieldInfo + Time + ] +end From 604d5d6ffd21e11dd961664f3af904770f54109e Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 27 Apr 2026 13:20:08 +0100 Subject: [PATCH 03/28] fix: update usages of unsafe loading for controlled files --- app/sequencescape_excel/sequencescape_excel/helpers.rb | 2 +- config/initializers/process_locale_files_with_erb.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/sequencescape_excel/sequencescape_excel/helpers.rb b/app/sequencescape_excel/sequencescape_excel/helpers.rb index 07c60d4513..a591556ea0 100644 --- a/app/sequencescape_excel/sequencescape_excel/helpers.rb +++ b/app/sequencescape_excel/sequencescape_excel/helpers.rb @@ -5,7 +5,7 @@ module SequencescapeExcel # Helpers module Helpers def load_file(folder, filename) - YAML.load_file(Rails.root.join(folder, "#{filename}.yml")).with_indifferent_access + YAML.load_file(Rails.root.join(folder, "#{filename}.yml"), aliases: true).with_indifferent_access end end end diff --git a/config/initializers/process_locale_files_with_erb.rb b/config/initializers/process_locale_files_with_erb.rb index c4eb19e95d..78cc131d13 100644 --- a/config/initializers/process_locale_files_with_erb.rb +++ b/config/initializers/process_locale_files_with_erb.rb @@ -4,7 +4,7 @@ module I18n module Backend module Base def load_yml(filename) - YAML.load(ERB.new(File.read(filename)).result) + YAML.unsafe_load(ERB.new(File.read(filename)).result) end end end From 98923647deec486e79c89e92986177a678c6bb6b Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 29 Apr 2026 14:08:18 +0100 Subject: [PATCH 04/28] test: add descriptive context to bulk submission specs --- .rubocop_todo.yml | 1 - spec/models/bulk_submission_spec.rb | 20 ++++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 313220ee53..e24bc77a2e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -649,7 +649,6 @@ RSpec/ContextWording: - 'spec/models/barcode_spec.rb' - 'spec/models/broadcast_event/lab_event_spec.rb' - 'spec/models/broadcast_event/qc_assay_spec.rb' - - 'spec/models/bulk_submission_spec.rb' - 'spec/models/comment_spec.rb' - 'spec/models/illumina_htp/initial_stock_tube_purpose_spec.rb' - 'spec/models/location_report_form_spec.rb' diff --git a/spec/models/bulk_submission_spec.rb b/spec/models/bulk_submission_spec.rb index 68422d4018..0fc0ad2818 100644 --- a/spec/models/bulk_submission_spec.rb +++ b/spec/models/bulk_submission_spec.rb @@ -57,7 +57,7 @@ create(:project, name: 'Test project') end - context 'a simple submission' do + context 'when creating a simple submission' do let(:submission_template_hash) do { name: 'Illumina-A - Cherrypick for pulldown - Pulldown WGS - HiSeq Paired end sequencing', @@ -93,7 +93,7 @@ end end - context 'an asset driven submission' do + context 'when creating an asset driven submission' do let(:spreadsheet_filename) { 'template_for_bulk_submission.csv' } let!(:asset) { create(:plate, barcode: 'SQPD-1', well_count: 1, well_factory: :untagged_well) } let(:submission_template_hash) do @@ -126,7 +126,7 @@ end end - context 'a submission with PCR cycles' do + context 'when creating a submission with PCR cycles' do let(:spreadsheet_filename) { 'pcr_cycles.csv' } let!(:submission_template) do @@ -157,7 +157,7 @@ end end - context 'a submission with primer_panels' do + context 'when creating a submission with primer_panels' do let(:spreadsheet_filename) { 'primer_panels.csv' } let!(:primer_panel) { create(:primer_panel, name: 'Test panel') } @@ -190,7 +190,7 @@ end end - context 'a submission with bait libraries' do + context 'when creating a submission with bait libraries' do let(:spreadsheet_filename) { '2_valid_sc_submissions.csv' } let!(:bait_library) { create(:bait_library, name: 'Bait library 1') } let!(:bait_library_2) { create(:bait_library, name: 'Bait library 2') } @@ -222,7 +222,7 @@ end end - context 'a submission with a lowercase library type' do + context 'when creating a submission with a lowercase library type' do let(:spreadsheet_filename) { 'with_lowercase_library_type.csv' } let!(:submission_template) do @@ -253,7 +253,7 @@ end end - context 'a submission with an unrecognised library type' do + context 'when creating a submission with an unrecognised library type' do let(:spreadsheet_filename) { 'with_unknown_library_type.csv' } let!(:submission_template) do @@ -274,7 +274,7 @@ end end - context 'a submission with additional template name validations' do + context 'when creating a submission with additional template name validations' do context 'when valid for scRNA template' do let(:submission_template_hash) do { @@ -434,7 +434,7 @@ end end - context 'a submission with a NovaSeqX sequencing request type' do + context 'when creating a submission with a NovaSeqX sequencing request type' do let(:spreadsheet_filename) { 'novaseqx_bulk_submission.csv' } let!(:request_types) { [create(:nova_seq_x_sequencing_request_type)] } let(:study) { create(:study, name: 'UAT Study') } @@ -480,7 +480,7 @@ end end - context 'a submission with a NovaSeq 6000 PE sequencing request type' do + context 'when creating a submission with a NovaSeq 6000 PE sequencing request type' do let(:spreadsheet_filename) { 'nova_seq_6000_pe_bulk_submission.csv' } let!(:request_types) { [create(:nova_seq_6000_p_e_sequencing_request_type)] } let(:study) { create(:study, name: 'UAT Study') } From 2890168404f53459e065c7ad2303c098d75fa8d3 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 29 Apr 2026 14:59:52 +0100 Subject: [PATCH 05/28] fix: update YAML file loading functions --- app/models/submission/request_options_behaviour.rb | 2 +- app/sequencescape_excel/sequencescape_excel/helpers.rb | 2 +- config/initializers/accession.rb | 2 +- config/initializers/failure_reasons.rb | 2 +- config/initializers/flipper.rb | 2 +- config/initializers/phi_x.rb | 2 +- db/seeds/0001_snp_plate_purposes.rb | 2 +- lib/accession.rb | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index f140271a35..b215396ad3 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -7,7 +7,7 @@ class HashWrapper def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? - YAML.load(hash_yaml) + YAML.unsafe_load(hash_yaml) end def self.dump(hash) diff --git a/app/sequencescape_excel/sequencescape_excel/helpers.rb b/app/sequencescape_excel/sequencescape_excel/helpers.rb index a591556ea0..423b89b999 100644 --- a/app/sequencescape_excel/sequencescape_excel/helpers.rb +++ b/app/sequencescape_excel/sequencescape_excel/helpers.rb @@ -5,7 +5,7 @@ module SequencescapeExcel # Helpers module Helpers def load_file(folder, filename) - YAML.load_file(Rails.root.join(folder, "#{filename}.yml"), aliases: true).with_indifferent_access + YAML.unsafe_load_file(Rails.root.join(folder, "#{filename}.yml")).with_indifferent_access end end end diff --git a/config/initializers/accession.rb b/config/initializers/accession.rb index 5602bbdd39..c00c57dbe7 100644 --- a/config/initializers/accession.rb +++ b/config/initializers/accession.rb @@ -9,5 +9,5 @@ end # add ena requirement fields here -ena_requirement_fields = YAML.load_file('config/ena_requirement_fields.yml') +ena_requirement_fields = YAML.unsafe_load_file('config/ena_requirement_fields.yml') Rails.application.config.ena_requirement_fields = ena_requirement_fields.with_indifferent_access diff --git a/config/initializers/failure_reasons.rb b/config/initializers/failure_reasons.rb index 87c12b48f6..7f3b12f975 100644 --- a/config/initializers/failure_reasons.rb +++ b/config/initializers/failure_reasons.rb @@ -1,2 +1,2 @@ # frozen_string_literal: true -FAILURE_REASONS = YAML.load(File.open("#{Rails.root}/config/failure_reasons.yml")) +FAILURE_REASONS = YAML.unsafe_load_file("#{Rails.root}/config/failure_reasons.yml") diff --git a/config/initializers/flipper.rb b/config/initializers/flipper.rb index c64a5c5c81..5d9d87a3b9 100644 --- a/config/initializers/flipper.rb +++ b/config/initializers/flipper.rb @@ -2,7 +2,7 @@ require 'yaml' require 'flipper/adapters/active_record' -FLIPPER_FEATURES = YAML.load_file('./config/feature_flags.yml') +FLIPPER_FEATURES = YAML.safe_load_file('./config/feature_flags.yml') Rails.application.configure do ## Memoization ensures that only one adapter call is made per feature per request. diff --git a/config/initializers/phi_x.rb b/config/initializers/phi_x.rb index d9c687a077..b4c8cb406e 100644 --- a/config/initializers/phi_x.rb +++ b/config/initializers/phi_x.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -phi_x_config = YAML.load_file('config/phi_x.yml') +phi_x_config = YAML.safe_load_file('config/phi_x.yml') Rails.application.config.phi_x = phi_x_config.with_indifferent_access diff --git a/db/seeds/0001_snp_plate_purposes.rb b/db/seeds/0001_snp_plate_purposes.rb index 2693e3e51a..c2c5b73765 100644 --- a/db/seeds/0001_snp_plate_purposes.rb +++ b/db/seeds/0001_snp_plate_purposes.rb @@ -51,7 +51,7 @@ EOS YAML - .load(plate_purposes) + .unsafe_load(plate_purposes) .each do |plate_purpose| attributes = plate_purpose.reverse_merge( diff --git a/lib/accession.rb b/lib/accession.rb index 8c27017783..91f0859962 100644 --- a/lib/accession.rb +++ b/lib/accession.rb @@ -24,7 +24,7 @@ module Accession # @see ftp://ftp.sra.ebi.ac.uk/meta/xsd/ Schema definitions module Helpers def load_file(folder, filename) - YAML.load_file(Rails.root.join(folder, "#{filename}.yml")).with_indifferent_access + YAML.safe_load_file(Rails.root.join(folder, "#{filename}.yml")).with_indifferent_access end end From 6f880c8d8495efa87800ef172592764383918303 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 29 Apr 2026 15:00:48 +0100 Subject: [PATCH 06/28] fix: allow the use of unsafe loading in Psych 4, same behaviour as in 3 --- config/initializers/psych.rb | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/config/initializers/psych.rb b/config/initializers/psych.rb index 472b3025db..ee5458f939 100644 --- a/config/initializers/psych.rb +++ b/config/initializers/psych.rb @@ -1,22 +1,20 @@ # frozen_string_literal: true Rails.application.configure do - # Fix for Psych::DisallowedClass: Tried to load unspecified class - config.active_record.yaml_column_permitted_classes = - Array(config.active_record.yaml_column_permitted_classes) + - %w[ - Symbol - ActiveSupport::HashWithIndifferentAccess - ActiveSupport::TimeWithZone - ActiveSupport::TimeZone - HashWithIndifferentAccess - RequestType::Validator::ArrayWithDefault - RequestType::Validator::LibraryTypeValidator - RequestType::Validator::FlowcellTypeValidator - ActionController::Parameters - Set - Range - FieldInfo - Time - ] + # Allow legacy YAML deserialization for ActiveRecord serialized columns + # Ideally replace with `permitted_classes`, such as those used in the list below: + # Symbol + # ActiveSupport::HashWithIndifferentAccess + # ActiveSupport::TimeWithZone + # ActiveSupport::TimeZone + # HashWithIndifferentAccess + # RequestType::Validator::ArrayWithDefault + # RequestType::Validator::LibraryTypeValidator + # RequestType::Validator::FlowcellTypeValidator + # ActionController::Parameters + # Set + # Range + # FieldInfo + # Time + config.active_record.use_yaml_unsafe_load = true end From f99987c7959633f080706ab14735522da558f98c Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 29 Apr 2026 15:04:58 +0100 Subject: [PATCH 07/28] stlye: lint --- spec/models/bulk_submission_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/bulk_submission_spec.rb b/spec/models/bulk_submission_spec.rb index 0fc0ad2818..6cbde9a8ce 100644 --- a/spec/models/bulk_submission_spec.rb +++ b/spec/models/bulk_submission_spec.rb @@ -420,13 +420,13 @@ end context 'when an scRNA Bulk Submission given with invalid number of samples per pool' do - context 'number of samples per pool < 5' do + context 'when number of samples per pool < 5' do let(:spreadsheet_filename) { 'scRNA_bulk_submission_tube_invalid.csv' } it_behaves_like 'an invalid scRNA Bulk Submission', 'scRNA_bulk_submission_tube_invalid', 4 end - context 'number of samples per pool > 25' do + context 'when number of samples per pool > 25' do let(:spreadsheet_filename) { 'scRNA_bulk_submission_tube_invalid_greater.csv' } it_behaves_like 'an invalid scRNA Bulk Submission', 'scRNA_bulk_submission_tube_invalid_greater', 32 From aa5f96caad42a5132bfcc8388d1eecac7af0e748 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 29 Apr 2026 15:13:18 +0100 Subject: [PATCH 08/28] fix: allow symbol loading for accessioning tags --- lib/accession.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/accession.rb b/lib/accession.rb index 91f0859962..64d98f2365 100644 --- a/lib/accession.rb +++ b/lib/accession.rb @@ -24,7 +24,8 @@ module Accession # @see ftp://ftp.sra.ebi.ac.uk/meta/xsd/ Schema definitions module Helpers def load_file(folder, filename) - YAML.safe_load_file(Rails.root.join(folder, "#{filename}.yml")).with_indifferent_access + file_path = Rails.root.join(folder, "#{filename}.yml") + YAML.safe_load_file(file_path, permitted_classes: [Symbol]).with_indifferent_access end end From 8e6f07e9c527f2e2ee1125dfa22278b181f13b3b Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 14:44:46 +0100 Subject: [PATCH 09/28] fix: preview against latest record_loader beta version --- Gemfile | 2 +- Gemfile.lock | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index bbdd96401b..1157904c8f 100644 --- a/Gemfile +++ b/Gemfile @@ -30,7 +30,7 @@ group :default do # Provides bulk insert capabilities gem 'activerecord-import' - gem 'record_loader', git: 'https://github.com/sanger/record_loader' + gem 'record_loader', git: 'https://github.com/sanger/record_loader', branch: 'sh51/psych-5' gem 'mysql2', platforms: :mri gem 'will_paginate' diff --git a/Gemfile.lock b/Gemfile.lock index 39a36346fd..7b9899460c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,9 +16,11 @@ GIT GIT remote: https://github.com/sanger/record_loader - revision: 9e7481f4d2342f042ab13465962e5d6689863198 + revision: cbd59574d1090a0a81a770478a6b4042fa882f6a + branch: sh51/psych-5 specs: - record_loader (0.3.0) + record_loader (1.0.0) + psych (~> 5.0) GIT remote: https://github.com/sanger/sanger_barcode_format.git @@ -377,6 +379,9 @@ GEM pry (>= 0.13, < 0.17) pry-rails (0.3.11) pry (>= 0.13.0) + psych (5.3.1) + date + stringio public_suffix (7.0.5) puma (7.2.0) nio4r (~> 2.0) @@ -585,6 +590,7 @@ GEM rbtree set (~> 1.0) ssrf_filter (1.2.0) + stringio (3.2.0) syntax_tree (6.3.0) prettier_print (>= 1.2.0) syntax_tree-haml (4.0.3) From b8ebbfe4daf40d18dde6dababb4aede4f159607a Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 15:09:19 +0100 Subject: [PATCH 10/28] fix: remove unrequired commas from asset_shapes --- config/default_records/asset_shapes/default_records.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/default_records/asset_shapes/default_records.yml b/config/default_records/asset_shapes/default_records.yml index 593842c86c..6e5b3778b5 100644 --- a/config/default_records/asset_shapes/default_records.yml +++ b/config/default_records/asset_shapes/default_records.yml @@ -62,8 +62,8 @@ Fluidigm192: sizes: [192] StripTubeColumn: - horizontal_ratio: 1, - vertical_ratio: 8, + horizontal_ratio: 1 + vertical_ratio: 8 description_strategy: Sequential sizes: [8] From a28b17379ad4f1746691314871db2c757843ee79 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 15:16:26 +0100 Subject: [PATCH 11/28] security: be stricter in what files are unsafe_loaded --- .../submission/request_options_behaviour.rb | 2 +- .../sequencescape_excel/helpers.rb | 2 +- config/initializers/accession.rb | 2 +- config/initializers/failure_reasons.rb | 2 +- config/initializers/psych.rb | 20 ------------------- 5 files changed, 4 insertions(+), 24 deletions(-) delete mode 100644 config/initializers/psych.rb diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index b215396ad3..7d431e0810 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -7,7 +7,7 @@ class HashWrapper def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? - YAML.unsafe_load(hash_yaml) + YAML.safe_load(hash_yaml) end def self.dump(hash) diff --git a/app/sequencescape_excel/sequencescape_excel/helpers.rb b/app/sequencescape_excel/sequencescape_excel/helpers.rb index 423b89b999..9462318a00 100644 --- a/app/sequencescape_excel/sequencescape_excel/helpers.rb +++ b/app/sequencescape_excel/sequencescape_excel/helpers.rb @@ -5,7 +5,7 @@ module SequencescapeExcel # Helpers module Helpers def load_file(folder, filename) - YAML.unsafe_load_file(Rails.root.join(folder, "#{filename}.yml")).with_indifferent_access + YAML.safe_load_file(Rails.root.join(folder, "#{filename}.yml")).with_indifferent_access end end end diff --git a/config/initializers/accession.rb b/config/initializers/accession.rb index c00c57dbe7..cd061dc4f8 100644 --- a/config/initializers/accession.rb +++ b/config/initializers/accession.rb @@ -9,5 +9,5 @@ end # add ena requirement fields here -ena_requirement_fields = YAML.unsafe_load_file('config/ena_requirement_fields.yml') +ena_requirement_fields = YAML.safe_load_file('config/ena_requirement_fields.yml') Rails.application.config.ena_requirement_fields = ena_requirement_fields.with_indifferent_access diff --git a/config/initializers/failure_reasons.rb b/config/initializers/failure_reasons.rb index 7f3b12f975..4efde706db 100644 --- a/config/initializers/failure_reasons.rb +++ b/config/initializers/failure_reasons.rb @@ -1,2 +1,2 @@ # frozen_string_literal: true -FAILURE_REASONS = YAML.unsafe_load_file("#{Rails.root}/config/failure_reasons.yml") +FAILURE_REASONS = YAML.safe_load_file("#{Rails.root}/config/failure_reasons.yml") diff --git a/config/initializers/psych.rb b/config/initializers/psych.rb deleted file mode 100644 index ee5458f939..0000000000 --- a/config/initializers/psych.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -Rails.application.configure do - # Allow legacy YAML deserialization for ActiveRecord serialized columns - # Ideally replace with `permitted_classes`, such as those used in the list below: - # Symbol - # ActiveSupport::HashWithIndifferentAccess - # ActiveSupport::TimeWithZone - # ActiveSupport::TimeZone - # HashWithIndifferentAccess - # RequestType::Validator::ArrayWithDefault - # RequestType::Validator::LibraryTypeValidator - # RequestType::Validator::FlowcellTypeValidator - # ActionController::Parameters - # Set - # Range - # FieldInfo - # Time - config.active_record.use_yaml_unsafe_load = true -end From dcc7da5cfb02045d0c97c4e7cb9b33f75e219286 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 15:29:16 +0100 Subject: [PATCH 12/28] fix: allow symbol loading for excel helpers --- app/sequencescape_excel/sequencescape_excel/helpers.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/sequencescape_excel/sequencescape_excel/helpers.rb b/app/sequencescape_excel/sequencescape_excel/helpers.rb index 9462318a00..5d018d146b 100644 --- a/app/sequencescape_excel/sequencescape_excel/helpers.rb +++ b/app/sequencescape_excel/sequencescape_excel/helpers.rb @@ -5,7 +5,8 @@ module SequencescapeExcel # Helpers module Helpers def load_file(folder, filename) - YAML.safe_load_file(Rails.root.join(folder, "#{filename}.yml")).with_indifferent_access + file_path = Rails.root.join(folder, "#{filename}.yml") + YAML.safe_load_file(file_path, permitted_classes: [Symbol]).with_indifferent_access end end end From 5f4d038c5cde0a82ad34f4b162a726b8e0aa7dc4 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 15:51:38 +0100 Subject: [PATCH 13/28] fix: allow aliases for excel helper --- app/sequencescape_excel/sequencescape_excel/helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/sequencescape_excel/sequencescape_excel/helpers.rb b/app/sequencescape_excel/sequencescape_excel/helpers.rb index 5d018d146b..0317705694 100644 --- a/app/sequencescape_excel/sequencescape_excel/helpers.rb +++ b/app/sequencescape_excel/sequencescape_excel/helpers.rb @@ -6,7 +6,7 @@ module SequencescapeExcel module Helpers def load_file(folder, filename) file_path = Rails.root.join(folder, "#{filename}.yml") - YAML.safe_load_file(file_path, permitted_classes: [Symbol]).with_indifferent_access + YAML.safe_load_file(file_path, permitted_classes: [Symbol], aliases: true).with_indifferent_access end end end From 08fff5b6ab54635fa0f428b8951ba52864bf9c45 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 15:59:36 +0100 Subject: [PATCH 14/28] fix: allow HashWithIndifferentAccess loading for request options loading --- app/models/submission/request_options_behaviour.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index 7d431e0810..0528997793 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -7,7 +7,7 @@ class HashWrapper def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? - YAML.safe_load(hash_yaml) + YAML.safe_load(hash_yaml, permitted_classes: [ActiveSupport::HashWithIndifferentAccess]) end def self.dump(hash) From a6b82fc06321b77e4fcae69f5b60ec124049f3d2 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 16:15:18 +0100 Subject: [PATCH 15/28] fix: allow request type serialization --- app/models/request_type/validator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/request_type/validator.rb b/app/models/request_type/validator.rb index d863ac909f..6fea8a85ce 100644 --- a/app/models/request_type/validator.rb +++ b/app/models/request_type/validator.rb @@ -104,7 +104,7 @@ def valid_options belongs_to :request_type, optional: false validates :request_option, :valid_options, presence: true - serialize :valid_options, coder: YAML + serialize :valid_options, coder: YAML, yaml: { permitted_classes: [Range, RequestType::Validator::ArrayWithDefault] } delegate :include?, to: :valid_options From ba929443dbee767963e0befe9bac5e9b9f51f5b8 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 16:43:13 +0100 Subject: [PATCH 16/28] fix: allow additional request type serialization --- app/models/request_type/validator.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/request_type/validator.rb b/app/models/request_type/validator.rb index 6fea8a85ce..db32ee1cba 100644 --- a/app/models/request_type/validator.rb +++ b/app/models/request_type/validator.rb @@ -104,7 +104,14 @@ def valid_options belongs_to :request_type, optional: false validates :request_option, :valid_options, presence: true - serialize :valid_options, coder: YAML, yaml: { permitted_classes: [Range, RequestType::Validator::ArrayWithDefault] } + serialize :valid_options, coder: YAML, yaml: { + permitted_classes: [ + Range, + RequestType::Validator::ArrayWithDefault, + RequestType::Validator::FlowcellTypeValidator, + RequestType::Validator::LibraryTypeValidator + ] + } delegate :include?, to: :valid_options From 6846d27daec80b00ee42815592487b524580ea3b Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 16:57:43 +0100 Subject: [PATCH 17/28] fix: allow primer programs serialization --- app/models/primer_panel.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/primer_panel.rb b/app/models/primer_panel.rb index 4b194e01b7..8fd4926bc1 100644 --- a/app/models/primer_panel.rb +++ b/app/models/primer_panel.rb @@ -11,7 +11,7 @@ class PrimerPanel < ApplicationRecord include ActiveModel::Validations include SharedBehaviour::Named - serialize :programs, coder: YAML + serialize :programs, coder: YAML, yaml: { permitted_classes: [ActiveSupport::HashWithIndifferentAccess] } # The name: Used to identify the assay set. validates :name, presence: true From 544c06c3e58e2e4a68b80b467640558e31b3d49e Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 17:01:15 +0100 Subject: [PATCH 18/28] fix: allow broadcase_event serialization --- app/models/broadcast_event.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/broadcast_event.rb b/app/models/broadcast_event.rb index 7493d841e6..e0e9a39796 100644 --- a/app/models/broadcast_event.rb +++ b/app/models/broadcast_event.rb @@ -19,7 +19,7 @@ class BroadcastEvent < ApplicationRecord # https://api.rubyonrails.org/classes/ActiveRecord/Inheritance/ClassMethods.html validates :sti_type, presence: true - serialize :properties, coder: YAML + serialize :properties, coder: YAML, yaml: { permitted_classes: [ActionController::Parameters, ActiveSupport::HashWithIndifferentAccess] } self.inheritance_column = 'sti_type' broadcast_with_warren From c40fd04a725302c9478a04d38870ff27d2a1bf9a Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 30 Apr 2026 17:03:19 +0100 Subject: [PATCH 19/28] fix: alllow loading of request options behaviour --- app/models/submission/request_options_behaviour.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index 0528997793..a47407c45b 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -7,7 +7,7 @@ class HashWrapper def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? - YAML.safe_load(hash_yaml, permitted_classes: [ActiveSupport::HashWithIndifferentAccess]) + YAML.safe_load(hash_yaml, permitted_classes: [ActiveSupport::HashWithIndifferentAccess, ActiveSupport::TimeWithZone]) end def self.dump(hash) From 7bb8f070a9bcc36d0f189657cd0c96da58c8fa76 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Fri, 1 May 2026 08:40:25 +0100 Subject: [PATCH 20/28] fix: allow tag_layout serialization --- app/models/tag_layout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/tag_layout.rb b/app/models/tag_layout.rb index e85e0a368e..2be3ed563f 100644 --- a/app/models/tag_layout.rb +++ b/app/models/tag_layout.rb @@ -48,7 +48,7 @@ def walking_by self.inheritance_column = 'sti_type' - serialize :substitutions, type: Hash, coder: YAML + serialize :substitutions, type: Hash, coder: YAML, yaml: { permitted_classes: [ActiveSupport::HashWithIndifferentAccess] } # The user performing the layout belongs_to :user, optional: false From a6969d1110f36479260819ed078a72854873fb12 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Fri, 1 May 2026 09:02:42 +0100 Subject: [PATCH 21/28] fix: allow Time for request options behaviour --- app/models/submission/request_options_behaviour.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index a47407c45b..a2725edacf 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -7,7 +7,8 @@ class HashWrapper def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? - YAML.safe_load(hash_yaml, permitted_classes: [ActiveSupport::HashWithIndifferentAccess, ActiveSupport::TimeWithZone]) + YAML.safe_load(hash_yaml, + permitted_classes: [ActiveSupport::HashWithIndifferentAccess, ActiveSupport::TimeWithZone, Time]) end def self.dump(hash) From aba4e203ea90cb62837dbad1402b926de5d3a0f8 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 5 May 2026 12:43:19 +0100 Subject: [PATCH 22/28] security: allow HashWithIndifferentAccess to be loaded by default --- app/models/broadcast_event.rb | 2 +- app/models/primer_panel.rb | 2 +- app/models/submission/request_options_behaviour.rb | 2 +- config/initializers/psych.rb | 6 ++++++ 4 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 config/initializers/psych.rb diff --git a/app/models/broadcast_event.rb b/app/models/broadcast_event.rb index e0e9a39796..146c5571ea 100644 --- a/app/models/broadcast_event.rb +++ b/app/models/broadcast_event.rb @@ -19,7 +19,7 @@ class BroadcastEvent < ApplicationRecord # https://api.rubyonrails.org/classes/ActiveRecord/Inheritance/ClassMethods.html validates :sti_type, presence: true - serialize :properties, coder: YAML, yaml: { permitted_classes: [ActionController::Parameters, ActiveSupport::HashWithIndifferentAccess] } + serialize :properties, coder: YAML, yaml: { permitted_classes: [ActionController::Parameters] } self.inheritance_column = 'sti_type' broadcast_with_warren diff --git a/app/models/primer_panel.rb b/app/models/primer_panel.rb index 8fd4926bc1..4b194e01b7 100644 --- a/app/models/primer_panel.rb +++ b/app/models/primer_panel.rb @@ -11,7 +11,7 @@ class PrimerPanel < ApplicationRecord include ActiveModel::Validations include SharedBehaviour::Named - serialize :programs, coder: YAML, yaml: { permitted_classes: [ActiveSupport::HashWithIndifferentAccess] } + serialize :programs, coder: YAML # The name: Used to identify the assay set. validates :name, presence: true diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index a2725edacf..5adc582f7a 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -8,7 +8,7 @@ def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? YAML.safe_load(hash_yaml, - permitted_classes: [ActiveSupport::HashWithIndifferentAccess, ActiveSupport::TimeWithZone, Time]) + permitted_classes: [ActiveSupport::TimeWithZone, Time]) end def self.dump(hash) diff --git a/config/initializers/psych.rb b/config/initializers/psych.rb new file mode 100644 index 0000000000..d4c7384d6e --- /dev/null +++ b/config/initializers/psych.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +Rails.application.configure do + # Allow YAML columns to contain HashWithIndifferentAccess objects by default + ActiveRecord.yaml_column_permitted_classes += [ActiveSupport::HashWithIndifferentAccess] +end From 26c9a5b851320607b819bd7555b7edee28d1a853 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 5 May 2026 15:00:03 +0100 Subject: [PATCH 23/28] revert: add HashWithIndifferentAccess back to non-ActiveRecord loading --- app/models/submission/request_options_behaviour.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index 5adc582f7a..a2725edacf 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -8,7 +8,7 @@ def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? YAML.safe_load(hash_yaml, - permitted_classes: [ActiveSupport::TimeWithZone, Time]) + permitted_classes: [ActiveSupport::HashWithIndifferentAccess, ActiveSupport::TimeWithZone, Time]) end def self.dump(hash) From 33481a04f3e76afd7afd52b2993d8e9bf4d4e182 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 5 May 2026 15:09:37 +0100 Subject: [PATCH 24/28] fix: remove already-permitted HashWithIndifferentAccess --- app/models/tag_layout.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/tag_layout.rb b/app/models/tag_layout.rb index 2be3ed563f..e85e0a368e 100644 --- a/app/models/tag_layout.rb +++ b/app/models/tag_layout.rb @@ -48,7 +48,7 @@ def walking_by self.inheritance_column = 'sti_type' - serialize :substitutions, type: Hash, coder: YAML, yaml: { permitted_classes: [ActiveSupport::HashWithIndifferentAccess] } + serialize :substitutions, type: Hash, coder: YAML # The user performing the layout belongs_to :user, optional: false From f61d3227cd9a445fe81bc73cd81906cde620a43d Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 5 May 2026 15:10:25 +0100 Subject: [PATCH 25/28] fix: allow request options behaviour to load ActiveSupport::TimeZone --- app/models/submission/request_options_behaviour.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index a2725edacf..aeeac42f25 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -7,8 +7,12 @@ class HashWrapper def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? - YAML.safe_load(hash_yaml, - permitted_classes: [ActiveSupport::HashWithIndifferentAccess, ActiveSupport::TimeWithZone, Time]) + YAML.safe_load(hash_yaml, permitted_classes: [ + ActiveSupport::HashWithIndifferentAccess, + ActiveSupport::TimeWithZone, + ActiveSupport::TimeZone, + Time + ]) end def self.dump(hash) From 49b76887d70563d690f405587ddf4125523e540d Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 5 May 2026 15:14:18 +0100 Subject: [PATCH 26/28] fix: allow request options behaviour to load aliases --- app/models/submission/request_options_behaviour.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index aeeac42f25..b207c98708 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -7,7 +7,7 @@ class HashWrapper def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? - YAML.safe_load(hash_yaml, permitted_classes: [ + YAML.safe_load(hash_yaml, aliases: true, permitted_classes: [ ActiveSupport::HashWithIndifferentAccess, ActiveSupport::TimeWithZone, ActiveSupport::TimeZone, From d4620706999d1c83dd622890d7c21c5378e6c68d Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 5 May 2026 15:31:23 +0100 Subject: [PATCH 27/28] style: lint --- .../submission/request_options_behaviour.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/models/submission/request_options_behaviour.rb b/app/models/submission/request_options_behaviour.rb index b207c98708..24489254f1 100644 --- a/app/models/submission/request_options_behaviour.rb +++ b/app/models/submission/request_options_behaviour.rb @@ -7,7 +7,9 @@ class HashWrapper def self.load(hash_yaml) return hash_yaml if hash_yaml.nil? - YAML.safe_load(hash_yaml, aliases: true, permitted_classes: [ + YAML.safe_load(hash_yaml, + aliases: true, + permitted_classes: [ ActiveSupport::HashWithIndifferentAccess, ActiveSupport::TimeWithZone, ActiveSupport::TimeZone, @@ -33,22 +35,20 @@ def request_options=(options) super end + private + def check_request_options check_multipliers_are_valid end - private :check_request_options - # rubocop:todo Metrics/PerceivedComplexity, Metrics/AbcSize - def check_multipliers_are_valid # rubocop:todo Metrics/CyclomaticComplexity + def check_multipliers_are_valid # rubocop:disable Metrics/CyclomaticComplexity multipliers = request_options.try(:[], :multiplier) return if multipliers.blank? # We're ok with nothing being specified! # TODO[xxx]: should probably error if they've specified a request type that isn't being used - errors.add(:request_options, 'negative multiplier supplied') if multipliers.values.map(&:to_i).any?(&:negative?) - errors.add(:request_options, 'zero multiplier supplied') if multipliers.values.map(&:to_i).any?(&:zero?) + multiplier_values = multipliers.values.map(&:to_i) + errors.add(:request_options, 'negative multiplier supplied') if multiplier_values.any?(&:negative?) + errors.add(:request_options, 'zero multiplier supplied') if multiplier_values.any?(&:zero?) false unless errors.empty? end - - # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity - private :check_multipliers_are_valid end From 582847edda6960f8be977d1920bf003c20fa7933 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 5 May 2026 15:38:29 +0100 Subject: [PATCH 28/28] style: update rubocop todo --- .rubocop_todo.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e24bc77a2e..4c111a75c9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --no-exclude-limit` -# on 2026-04-22 14:37:36 UTC using RuboCop version 1.86.1. +# on 2026-05-05 14:36:42 UTC using RuboCop version 1.86.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -301,12 +301,11 @@ Lint/UselessOr: - 'app/models/qcable/statemachine.rb' - 'app/models/ui_helper/summary.rb' -# Offense count: 11 +# Offense count: 7 # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max. Metrics/AbcSize: Exclude: - 'app/controllers/api/v2/transfers_controller.rb' - - 'app/controllers/studies/information_controller.rb' - 'app/jobs/export_pool_xp_to_traction_job.rb' - 'app/models/accession_service/base_service.rb' - 'app/sample_manifest_excel/sample_manifest_excel/manifest_type_list.rb' @@ -334,7 +333,7 @@ Metrics/CyclomaticComplexity: - 'app/models/accession_service/base_service.rb' - 'lib/limber/helper.rb' -# Offense count: 18 +# Offense count: 17 # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: Exclude: @@ -617,7 +616,7 @@ RSpec/BeforeAfterAll: - 'spec/sample_manifest_excel/worksheet_spec.rb' - 'spec/sequencescape_excel/worksheet_spec.rb' -# Offense count: 342 +# Offense count: 330 # Configuration parameters: Prefixes, AllowedPatterns. # Prefixes: when, with, without RSpec/ContextWording: @@ -1196,7 +1195,7 @@ RSpec/MultipleExpectations: - 'spec/views/labware/show_chromium_chip_spec.rb' - 'spec/views/samples/index_html_erb_spec.rb' -# Offense count: 243 +# Offense count: 249 # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. # SupportedStyles: always, named_only RSpec/NamedSubject: