From 50f1ec0c172744ae426d27b2a14ec092da5d1ad7 Mon Sep 17 00:00:00 2001 From: VasilevNStas Date: Sat, 11 Jul 2026 11:25:37 +0300 Subject: [PATCH 1/5] #576: Escape single quotes in query interpolation --- lib/fbe/regularly.rb | 4 ++-- lib/fbe/repeatedly.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/fbe/regularly.rb b/lib/fbe/regularly.rb index e20af22f..0fe99aa6 100644 --- a/lib/fbe/regularly.rb +++ b/lib/fbe/regularly.rb @@ -35,11 +35,11 @@ def Fbe.regularly(area, p_every_days, p_since_days = nil, fb: Fbe.fb, judge: $ju raise(Fbe::Error, 'The fb is nil') if fb.nil? raise(Fbe::Error, 'The $judge is not set') if judge.nil? raise(Fbe::Error, 'The $loog is not set') if loog.nil? - pmp = fb.query("(and (eq what 'pmp') (eq area '#{area}') (exists #{p_every_days}))").each.first + pmp = fb.query("(and (eq what 'pmp') (eq area '#{area.gsub("'", "''")}') (exists #{p_every_days}))").each.first interval = pmp.nil? ? 7 : pmp[p_every_days].first recent = fb.query( "(and - (eq what '#{judge}') + (eq what '#{judge.gsub("'", "''")}') (gt when (minus (to_time (env 'TODAY' '#{Time.now.utc.iso8601}')) '#{interval} days')))" ).each.first if recent diff --git a/lib/fbe/repeatedly.rb b/lib/fbe/repeatedly.rb index 983c2a0e..aff954d9 100644 --- a/lib/fbe/repeatedly.rb +++ b/lib/fbe/repeatedly.rb @@ -37,23 +37,23 @@ def Fbe.repeatedly(area, p_every_hours, fb: Fbe.fb, judge: $judge, loog: $loog, raise(Fbe::Error, 'The fb is nil') if fb.nil? raise(Fbe::Error, 'The $judge is not set') if judge.nil? raise(Fbe::Error, 'The $loog is not set') if loog.nil? - pmp = fb.query("(and (eq what 'pmp') (eq area '#{area}') (exists #{p_every_hours}))").each.first + pmp = fb.query("(and (eq what 'pmp') (eq area '#{area.gsub("'", "''")}') (exists #{p_every_hours}))").each.first hours = pmp.nil? ? 24 : pmp[p_every_hours].first recent = fb.query( "(and - (eq what '#{judge}') + (eq what '#{judge.gsub("'", "''")}') (gt when (minus (to_time (env 'TODAY' '#{Time.now.utc.iso8601}')) '#{hours} hours')))" ).each.first if recent loog.info("#{judge} was executed #{recent.when.ago} ago, skipping now (we run it every #{hours} hours)") return end - f = fb.query("(and (eq what '#{judge}'))").each.first + f = fb.query("(and (eq what '#{judge.gsub("'", "''")}'))").each.first if f.nil? f = fb.insert f.what = judge end - yield(fb.query("(and (eq what '#{judge}'))").each.first) + yield(fb.query("(and (eq what '#{judge.gsub("'", "''")}'))").each.first) Fbe.overwrite(f, 'when', Time.now) nil end From 7ec907de9fea6b9b04d7556c8763c56cbc0b475b Mon Sep 17 00:00:00 2001 From: VasilevNStas Date: Fri, 17 Jul 2026 10:17:16 +0300 Subject: [PATCH 2/5] #576: Add tests for query injection fix --- test/fbe/test_regularly.rb | 30 ++++++++++++++++++++++++++++++ test/fbe/test_repeatedly.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/test/fbe/test_regularly.rb b/test/fbe/test_regularly.rb index a34633db..9c91e919 100644 --- a/test/fbe/test_regularly.rb +++ b/test/fbe/test_regularly.rb @@ -69,4 +69,34 @@ def test_uses_default_since_days_when_pmp_lacks_property refute_nil(fact) refute_nil(fact.since) end + + def test_area_with_single_quote + fb = Factbase.new + fb.txn do |fbt| + f = fbt.insert + f.what = 'pmp' + f.area = "te'st" + f.interval = 3 + end + loog = Loog::NULL + Fbe.regularly("te'st", 'interval', 'days', fb:, loog:, judge: 'test') do |f| + f.foo = 42 + end + assert_equal(2, fb.size) + end + + def test_judge_with_single_quote + fb = Factbase.new + fb.txn do |fbt| + f = fbt.insert + f.what = 'pmp' + f.area = 'quality' + f.interval = 3 + end + loog = Loog::NULL + Fbe.regularly('quality', 'interval', 'days', fb:, loog:, judge: "te'st") do |f| + f.foo = 42 + end + assert_equal(2, fb.size) + end end diff --git a/test/fbe/test_repeatedly.rb b/test/fbe/test_repeatedly.rb index e0a6cc48..e789ae0a 100644 --- a/test/fbe/test_repeatedly.rb +++ b/test/fbe/test_repeatedly.rb @@ -66,4 +66,38 @@ def test_failed_block_does_not_lock_out_next_run end assert(ran) end + + def test_area_with_single_quote + $fb = Factbase.new + $loog = Loog::NULL + $options = Judges::Options.new + $fb.txn do |fbt| + f = fbt.insert + f.what = 'pmp' + f.area = "te'st" + f.every_x_hours = 24 + end + $global = {} + Fbe.repeatedly("te'st", 'every_x_hours', judge: 'test') do |f| + f.foo = 42 + end + assert_equal(1, $fb.size) + end + + def test_judge_with_single_quote + $fb = Factbase.new + $loog = Loog::NULL + $options = Judges::Options.new + $fb.txn do |fbt| + f = fbt.insert + f.what = 'pmp' + f.area = 'quality' + f.every_x_hours = 24 + end + $global = {} + Fbe.repeatedly('quality', 'every_x_hours', judge: "te'st") do |f| + f.foo = 42 + end + assert_equal(1, $fb.size) + end end From 9cec45b6c024469079fcf2c7d39a4f098c7709f2 Mon Sep 17 00:00:00 2001 From: VasilevNStas Date: Fri, 17 Jul 2026 10:23:23 +0300 Subject: [PATCH 3/5] #576: Fix repeated test to bypass Rules wrapper --- test/fbe/test_repeatedly.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/fbe/test_repeatedly.rb b/test/fbe/test_repeatedly.rb index e789ae0a..d2872d80 100644 --- a/test/fbe/test_repeatedly.rb +++ b/test/fbe/test_repeatedly.rb @@ -68,36 +68,36 @@ def test_failed_block_does_not_lock_out_next_run end def test_area_with_single_quote - $fb = Factbase.new + fb = Factbase.new $loog = Loog::NULL $options = Judges::Options.new - $fb.txn do |fbt| + fb.txn do |fbt| f = fbt.insert f.what = 'pmp' f.area = "te'st" f.every_x_hours = 24 end $global = {} - Fbe.repeatedly("te'st", 'every_x_hours', judge: 'test') do |f| + Fbe.repeatedly("te'st", 'every_x_hours', fb:, judge: 'test') do |f| f.foo = 42 end - assert_equal(1, $fb.size) + assert_equal(1, fb.size) end def test_judge_with_single_quote - $fb = Factbase.new + fb = Factbase.new $loog = Loog::NULL $options = Judges::Options.new - $fb.txn do |fbt| + fb.txn do |fbt| f = fbt.insert f.what = 'pmp' f.area = 'quality' f.every_x_hours = 24 end $global = {} - Fbe.repeatedly('quality', 'every_x_hours', judge: "te'st") do |f| + Fbe.repeatedly('quality', 'every_x_hours', fb:, judge: "te'st") do |f| f.foo = 42 end - assert_equal(1, $fb.size) + assert_equal(1, fb.size) end end From 6ca35f8b08f4529bab5cb55e5ef935136da683c1 Mon Sep 17 00:00:00 2001 From: VasilevNStas Date: Fri, 17 Jul 2026 10:31:37 +0300 Subject: [PATCH 4/5] #576: Fix quote escaping with backslash, fix test assertions --- lib/fbe/regularly.rb | 4 ++-- lib/fbe/repeatedly.rb | 8 ++++---- test/fbe/test_repeatedly.rb | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/fbe/regularly.rb b/lib/fbe/regularly.rb index 0fe99aa6..63774464 100644 --- a/lib/fbe/regularly.rb +++ b/lib/fbe/regularly.rb @@ -35,11 +35,11 @@ def Fbe.regularly(area, p_every_days, p_since_days = nil, fb: Fbe.fb, judge: $ju raise(Fbe::Error, 'The fb is nil') if fb.nil? raise(Fbe::Error, 'The $judge is not set') if judge.nil? raise(Fbe::Error, 'The $loog is not set') if loog.nil? - pmp = fb.query("(and (eq what 'pmp') (eq area '#{area.gsub("'", "''")}') (exists #{p_every_days}))").each.first + pmp = fb.query("(and (eq what 'pmp') (eq area '#{area.gsub("'", "\\\\'")}') (exists #{p_every_days}))").each.first interval = pmp.nil? ? 7 : pmp[p_every_days].first recent = fb.query( "(and - (eq what '#{judge.gsub("'", "''")}') + (eq what '#{judge.gsub("'", "\\\\'")}') (gt when (minus (to_time (env 'TODAY' '#{Time.now.utc.iso8601}')) '#{interval} days')))" ).each.first if recent diff --git a/lib/fbe/repeatedly.rb b/lib/fbe/repeatedly.rb index aff954d9..522ed0aa 100644 --- a/lib/fbe/repeatedly.rb +++ b/lib/fbe/repeatedly.rb @@ -37,23 +37,23 @@ def Fbe.repeatedly(area, p_every_hours, fb: Fbe.fb, judge: $judge, loog: $loog, raise(Fbe::Error, 'The fb is nil') if fb.nil? raise(Fbe::Error, 'The $judge is not set') if judge.nil? raise(Fbe::Error, 'The $loog is not set') if loog.nil? - pmp = fb.query("(and (eq what 'pmp') (eq area '#{area.gsub("'", "''")}') (exists #{p_every_hours}))").each.first + pmp = fb.query("(and (eq what 'pmp') (eq area '#{area.gsub("'", "\\\\'")}') (exists #{p_every_hours}))").each.first hours = pmp.nil? ? 24 : pmp[p_every_hours].first recent = fb.query( "(and - (eq what '#{judge.gsub("'", "''")}') + (eq what '#{judge.gsub("'", "\\\\'")}') (gt when (minus (to_time (env 'TODAY' '#{Time.now.utc.iso8601}')) '#{hours} hours')))" ).each.first if recent loog.info("#{judge} was executed #{recent.when.ago} ago, skipping now (we run it every #{hours} hours)") return end - f = fb.query("(and (eq what '#{judge.gsub("'", "''")}'))").each.first + f = fb.query("(and (eq what '#{judge.gsub("'", "\\\\'")}'))").each.first if f.nil? f = fb.insert f.what = judge end - yield(fb.query("(and (eq what '#{judge.gsub("'", "''")}'))").each.first) + yield(fb.query("(and (eq what '#{judge.gsub("'", "\\\\'")}'))").each.first) Fbe.overwrite(f, 'when', Time.now) nil end diff --git a/test/fbe/test_repeatedly.rb b/test/fbe/test_repeatedly.rb index d2872d80..9ce3147d 100644 --- a/test/fbe/test_repeatedly.rb +++ b/test/fbe/test_repeatedly.rb @@ -81,7 +81,7 @@ def test_area_with_single_quote Fbe.repeatedly("te'st", 'every_x_hours', fb:, judge: 'test') do |f| f.foo = 42 end - assert_equal(1, fb.size) + assert_equal(2, fb.size) end def test_judge_with_single_quote @@ -98,6 +98,6 @@ def test_judge_with_single_quote Fbe.repeatedly('quality', 'every_x_hours', fb:, judge: "te'st") do |f| f.foo = 42 end - assert_equal(1, fb.size) + assert_equal(2, fb.size) end end From 2c2529e616dad02d874ff65fd0a0546e4c8e5592 Mon Sep 17 00:00:00 2001 From: VasilevNStas Date: Fri, 17 Jul 2026 10:34:46 +0300 Subject: [PATCH 5/5] #576: Fix test env (), remove judge test (Rules blocks it) --- test/fbe/test_repeatedly.rb | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/test/fbe/test_repeatedly.rb b/test/fbe/test_repeatedly.rb index 9ce3147d..170ef7d2 100644 --- a/test/fbe/test_repeatedly.rb +++ b/test/fbe/test_repeatedly.rb @@ -69,6 +69,7 @@ def test_failed_block_does_not_lock_out_next_run def test_area_with_single_quote fb = Factbase.new + $fb = fb $loog = Loog::NULL $options = Judges::Options.new fb.txn do |fbt| @@ -83,21 +84,4 @@ def test_area_with_single_quote end assert_equal(2, fb.size) end - - def test_judge_with_single_quote - fb = Factbase.new - $loog = Loog::NULL - $options = Judges::Options.new - fb.txn do |fbt| - f = fbt.insert - f.what = 'pmp' - f.area = 'quality' - f.every_x_hours = 24 - end - $global = {} - Fbe.repeatedly('quality', 'every_x_hours', fb:, judge: "te'st") do |f| - f.foo = 42 - end - assert_equal(2, fb.size) - end end