From 1412bbc1a30e0311a04fa1b3db61d873ce7bb9a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 6 Sep 2026 07:34:22 +0000 Subject: [PATCH] Fix shell command injection via sequence range in Database#retrieve Database#retrieve interpolates the user supplied range into a shell command (`blastdbcmd ... -range `), but validated it with the unanchored regex /[0-9]+-[0-9]*/. Any string containing a digit-dash pair passed, so a range such as `1-2;echo INJECTED` was accepted and the trailing command was executed by the shell. The range is reachable, without authentication in standalone SequenceServer, through `GET /searchdata.json?query=:`. The range is now validated against BLAST::VALID_SEQUENCE_RANGE, which is anchored at both ends (\A[0-9]+-[0-9]*\z) so only "start-stop" or "start-" can reach the command line. Specs cover an appended command, a command hidden behind a newline, and a non-numeric prefix. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01T22rUQt69nPsbSUDFxhuq3 --- lib/sequenceserver/blast.rb | 5 +++++ lib/sequenceserver/database.rb | 2 +- spec/database_spec.rb | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/sequenceserver/blast.rb b/lib/sequenceserver/blast.rb index fa27a320..9a499302 100644 --- a/lib/sequenceserver/blast.rb +++ b/lib/sequenceserver/blast.rb @@ -5,5 +5,10 @@ module SequenceServer module BLAST VALID_SEQUENCE_ID = /\A[a-zA-Z0-9\-_.:*#|\[\]]+\z/ + + # Sequence range accepted by `blastdbcmd -range`: "start-stop" or "start-" + # (open ended). Anchored at both ends so that shell metacharacters cannot + # be smuggled in alongside an otherwise valid range. + VALID_SEQUENCE_RANGE = /\A[0-9]+-[0-9]*\z/ end end diff --git a/lib/sequenceserver/database.rb b/lib/sequenceserver/database.rb index 298a34e5..b74e0865 100644 --- a/lib/sequenceserver/database.rb +++ b/lib/sequenceserver/database.rb @@ -48,7 +48,7 @@ def retrieve(accession, coords = nil) fail( InvalidParameterError, "Invalid range coordinates: #{coords}" - ) unless coords =~ /[0-9]+-[0-9]*/ + ) unless coords =~ SequenceServer::BLAST::VALID_SEQUENCE_RANGE cmd << " -range #{coords}" end diff --git a/spec/database_spec.rb b/spec/database_spec.rb index f604db7c..b2ae308f 100644 --- a/spec/database_spec.rb +++ b/spec/database_spec.rb @@ -74,6 +74,24 @@ module SequenceServer Database.retrieve("SI2.2.0_06267:';hi") end.to raise_error(SequenceServer::InvalidParameterError) end + + it 'rejects a shell command appended to an otherwise valid range' do + expect do + Database.retrieve('SI2.2.0_06267:1-2;echo INJECTED') + end.to raise_error(SequenceServer::InvalidParameterError) + end + + it 'rejects a shell command hidden behind a newline in the range' do + expect do + Database.retrieve("SI2.2.0_06267:1-2\n;echo INJECTED") + end.to raise_error(SequenceServer::InvalidParameterError) + end + + it 'rejects a range that is only a prefix of a valid one' do + expect do + Database.retrieve('SI2.2.0_06267:x1-2') + end.to raise_error(SequenceServer::InvalidParameterError) + end end end end