From 2c7f550eaed3312bc0550db9f4402524074874ab Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:59:21 +0000 Subject: [PATCH 1/3] fix: Ignore FDv2 objects with unrecognized kinds put-object and delete-object events whose kind is not flag or segment are now dropped by the streaming and polling parsers before the object is decoded. Previously they were forwarded to the store, which filed any non-flag kind under segments and could overwrite or delete a real segment with the same key. --- lib/ldclient-rb/impl/data_system/polling.rb | 4 ++ .../impl/data_system/protocolv2.rb | 21 ++++++ lib/ldclient-rb/impl/data_system/streaming.rb | 14 ++-- .../polling_payload_parsing_spec.rb | 22 +++++++ .../streaming_synchronizer_spec.rb | 66 +++++++++++++++++++ 5 files changed, 123 insertions(+), 4 deletions(-) diff --git a/lib/ldclient-rb/impl/data_system/polling.rb b/lib/ldclient-rb/impl/data_system/polling.rb index 8c5d1266..629e8e9d 100644 --- a/lib/ldclient-rb/impl/data_system/polling.rb +++ b/lib/ldclient-rb/impl/data_system/polling.rb @@ -497,6 +497,8 @@ def self.polling_payload_to_changeset(data) builder.start(server_intent.payload.code) when LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT + next if LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(event[:data]) + begin put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.from_h(event[:data]) rescue ArgumentError => e @@ -506,6 +508,8 @@ def self.polling_payload_to_changeset(data) builder.add_put(put.kind, put.key, put.version, put.object) when LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT + next if LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(event[:data]) + begin delete_object = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.from_h(event[:data]) rescue ArgumentError => e diff --git a/lib/ldclient-rb/impl/data_system/protocolv2.rb b/lib/ldclient-rb/impl/data_system/protocolv2.rb index ef6b5ad3..da1cfd02 100644 --- a/lib/ldclient-rb/impl/data_system/protocolv2.rb +++ b/lib/ldclient-rb/impl/data_system/protocolv2.rb @@ -1,4 +1,5 @@ require 'json' +require 'ldclient-rb/interfaces/data_system' module LaunchDarkly module Impl @@ -9,6 +10,26 @@ module ProtocolV2 # LaunchDarkly data system version 2 (FDv2). # + SUPPORTED_KINDS = [ + LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG, + LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT, + ].freeze + + # + # Returns true if put-object or delete-object data names a kind this SDK + # does not support. Such objects are ignored so that payloads carrying + # newer kinds can still be applied. + # + # @param data [Hash] The hash representation of the event data + # @return [Boolean] + # + def self.unsupported_kind?(data) + return false unless data.is_a?(Hash) + + kind = data[:kind] + !kind.nil? && !SUPPORTED_KINDS.include?(kind) + end + # # DeleteObject specifies the deletion of a particular object. # diff --git a/lib/ldclient-rb/impl/data_system/streaming.rb b/lib/ldclient-rb/impl/data_system/streaming.rb index ae37bcae..532fef21 100644 --- a/lib/ldclient-rb/impl/data_system/streaming.rb +++ b/lib/ldclient-rb/impl/data_system/streaming.rb @@ -246,13 +246,19 @@ def stop nil when LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT - put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.from_h(JSON.parse(message.data, symbolize_names: true)) - change_set_builder.add_put(put.kind, put.key, put.version, put.object) + data = JSON.parse(message.data, symbolize_names: true) + unless LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(data) + put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.from_h(data) + change_set_builder.add_put(put.kind, put.key, put.version, put.object) + end nil when LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT - delete_object = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.from_h(JSON.parse(message.data, symbolize_names: true)) - change_set_builder.add_delete(delete_object.kind, delete_object.key, delete_object.version) + data = JSON.parse(message.data, symbolize_names: true) + unless LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(data) + delete_object = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.from_h(data) + change_set_builder.add_delete(delete_object.kind, delete_object.key, delete_object.version) + end nil when LaunchDarkly::Interfaces::DataSystem::EventName::GOODBYE diff --git a/spec/impl/data_system/polling_payload_parsing_spec.rb b/spec/impl/data_system/polling_payload_parsing_spec.rb index 877ad5ce..e590285a 100644 --- a/spec/impl/data_system/polling_payload_parsing_spec.rb +++ b/spec/impl/data_system/polling_payload_parsing_spec.rb @@ -100,6 +100,28 @@ module DataSystem expect(result.value.selector.version).to eq(461) end + it "ignores put and delete objects of unknown kinds" do + payload_str = '{"events":[ {"event":"server-intent","data":{"payloads":[ {"id":"5A46PZ79FQ9D08YYKT79DECDNV","target":461,"intentCode":"xfer-full","reason":"payload-missing"}]}},{"event": "put-object","data": {"key":"sampleflag","kind":"flag","version":461,"object":{"key":"sampleflag","version":112}}},{"event": "put-object","data": {"key":"sampleskill","kind":"some-future-kind","version":461,"object":{"foo":1}}},{"event": "delete-object","data": {"key":"sampleskill","kind":"some-future-kind","version":461}},{"event": "delete-object","data": {"key":"samplesegment","kind":"segment","version":461}},{"event":"payload-transferred","data":{"state":"(p:5A46PZ79FQ9D08YYKT79DECDNV:461)","id":"5A46PZ79FQ9D08YYKT79DECDNV","version":461}}]}' # rubocop:disable Layout/LineLength + + # Parse a payload that mixes known and unknown object kinds. + result = LaunchDarkly::Impl::DataSystem.polling_payload_to_changeset(JSON.parse(payload_str, symbolize_names: true)) + + # The payload completes with only the known objects. + expect(result.success?).to eq(true) + expect(result.value.intent_code).to eq(LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_FULL) + expect(result.value.changes.length).to eq(2) + + expect(result.value.changes[0].action).to eq(LaunchDarkly::Interfaces::DataSystem::ChangeType::PUT) + expect(result.value.changes[0].kind).to eq(LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG) + expect(result.value.changes[0].key).to eq(:sampleflag) + + expect(result.value.changes[1].action).to eq(LaunchDarkly::Interfaces::DataSystem::ChangeType::DELETE) + expect(result.value.changes[1].kind).to eq(LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT) + expect(result.value.changes[1].key).to eq(:samplesegment) + + expect(result.value.selector.state).to eq("(p:5A46PZ79FQ9D08YYKT79DECDNV:461)") + end + it "handles invalid put object" do payload_str = '{"events":[ {"event":"server-intent","data":{"payloads":[ {"id":"5A46PZ79FQ9D08YYKT79DECDNV","target":461,"intentCode":"xfer-full","reason":"payload-missing"}]}},{"event": "put-object","data": {}},{"event":"payload-transferred","data":{"state":"(p:5A46PZ79FQ9D08YYKT79DECDNV:461)","id":"5A46PZ79FQ9D08YYKT79DECDNV","version":461}}]}' # rubocop:disable Layout/LineLength result = LaunchDarkly::Impl::DataSystem.polling_payload_to_changeset(JSON.parse(payload_str, symbolize_names: true)) diff --git a/spec/impl/data_system/streaming_synchronizer_spec.rb b/spec/impl/data_system/streaming_synchronizer_spec.rb index 513c5cf7..860c5029 100644 --- a/spec/impl/data_system/streaming_synchronizer_spec.rb +++ b/spec/impl/data_system/streaming_synchronizer_spec.rb @@ -215,6 +215,72 @@ def initialize(type, data = nil) expect(update.change_set.changes[0].version).to eq(101) end + it "ignores put and delete objects of unknown kinds" do + server_intent = LaunchDarkly::Interfaces::DataSystem::ServerIntent.new( + payload: LaunchDarkly::Interfaces::DataSystem::Payload.new( + id: "id", + target: 300, + code: LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_FULL, + reason: "cant-catchup" + ) + ) + known_put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.new( + version: 100, + kind: LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG, + key: "flagkey", + object: { key: "flagkey" } + ) + unknown_put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.new( + version: 100, + kind: "some-future-kind", + key: "otherkey", + object: { foo: 1 } + ) + unknown_delete = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.new( + version: 101, + kind: "some-future-kind", + key: "otherkey" + ) + known_delete = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.new( + version: 101, + kind: LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT, + key: "segmentkey" + ) + selector = LaunchDarkly::Interfaces::DataSystem::Selector.new(state: "p:SOMETHING:300", version: 300) + + expect(logger).not_to receive(:warn) + expect(logger).not_to receive(:error) + + # Process a payload that mixes known and unknown object kinds + synchronizer.send(:process_message, MockSSEEvent.new(LaunchDarkly::Interfaces::DataSystem::EventName::SERVER_INTENT, JSON.generate(server_intent.to_h)), +change_set_builder, envid) + [known_put, unknown_put].each do |put| + result = synchronizer.send(:process_message, MockSSEEvent.new(LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT, JSON.generate(put.to_h)), +change_set_builder, envid) + expect(result).to be_nil + end + [unknown_delete, known_delete].each do |delete_object| + result = synchronizer.send(:process_message, MockSSEEvent.new(LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT, JSON.generate(delete_object.to_h)), +change_set_builder, envid) + expect(result).to be_nil + end + update = synchronizer.send(:process_message, MockSSEEvent.new(LaunchDarkly::Interfaces::DataSystem::EventName::PAYLOAD_TRANSFERRED, JSON.generate(selector.to_h)), +change_set_builder, envid) + + # The payload completes as valid with only the known objects + expect(update).not_to be_nil + expect(update.state).to eq(LaunchDarkly::Interfaces::DataSource::Status::VALID) + expect(update.error).to be_nil + expect(update.change_set).not_to be_nil + expect(update.change_set.changes.length).to eq(2) + expect(update.change_set.changes[0].action).to eq(LaunchDarkly::Interfaces::DataSystem::ChangeType::PUT) + expect(update.change_set.changes[0].kind).to eq(LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG) + expect(update.change_set.changes[0].key).to eq(:flagkey) + expect(update.change_set.changes[1].action).to eq(LaunchDarkly::Interfaces::DataSystem::ChangeType::DELETE) + expect(update.change_set.changes[1].kind).to eq(LaunchDarkly::Interfaces::DataSystem::ObjectKind::SEGMENT) + expect(update.change_set.changes[1].key).to eq(:segmentkey) + end + it "swallows goodbye events" do server_intent = LaunchDarkly::Interfaces::DataSystem::ServerIntent.new( payload: LaunchDarkly::Interfaces::DataSystem::Payload.new( From 2508a6aeee37382c390617357ded107b78e488e4 Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Tue, 27 Oct 2026 19:04:15 +0000 Subject: [PATCH 2/3] fix: Reject non-object put and delete data before checking kind --- lib/ldclient-rb/impl/data_system/polling.rb | 8 ++++++++ lib/ldclient-rb/impl/data_system/protocolv2.rb | 2 -- lib/ldclient-rb/impl/data_system/streaming.rb | 4 ++++ .../data_system/polling_payload_parsing_spec.rb | 11 +++++++++++ .../impl/data_system/streaming_synchronizer_spec.rb | 13 +++++++++++++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/ldclient-rb/impl/data_system/polling.rb b/lib/ldclient-rb/impl/data_system/polling.rb index 629e8e9d..e6a304e8 100644 --- a/lib/ldclient-rb/impl/data_system/polling.rb +++ b/lib/ldclient-rb/impl/data_system/polling.rb @@ -497,6 +497,10 @@ def self.polling_payload_to_changeset(data) builder.start(server_intent.payload.code) when LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT + unless event[:data].is_a?(Hash) + return LaunchDarkly::Result.fail("Invalid payload: 'put-object' data must be an object") + end + next if LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(event[:data]) begin @@ -508,6 +512,10 @@ def self.polling_payload_to_changeset(data) builder.add_put(put.kind, put.key, put.version, put.object) when LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT + unless event[:data].is_a?(Hash) + return LaunchDarkly::Result.fail("Invalid payload: 'delete-object' data must be an object") + end + next if LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(event[:data]) begin diff --git a/lib/ldclient-rb/impl/data_system/protocolv2.rb b/lib/ldclient-rb/impl/data_system/protocolv2.rb index da1cfd02..3e5c6158 100644 --- a/lib/ldclient-rb/impl/data_system/protocolv2.rb +++ b/lib/ldclient-rb/impl/data_system/protocolv2.rb @@ -24,8 +24,6 @@ module ProtocolV2 # @return [Boolean] # def self.unsupported_kind?(data) - return false unless data.is_a?(Hash) - kind = data[:kind] !kind.nil? && !SUPPORTED_KINDS.include?(kind) end diff --git a/lib/ldclient-rb/impl/data_system/streaming.rb b/lib/ldclient-rb/impl/data_system/streaming.rb index 532fef21..ab581f0f 100644 --- a/lib/ldclient-rb/impl/data_system/streaming.rb +++ b/lib/ldclient-rb/impl/data_system/streaming.rb @@ -247,6 +247,8 @@ def stop when LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT data = JSON.parse(message.data, symbolize_names: true) + raise ArgumentError, "put-object data must be an object" unless data.is_a?(Hash) + unless LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(data) put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.from_h(data) change_set_builder.add_put(put.kind, put.key, put.version, put.object) @@ -255,6 +257,8 @@ def stop when LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT data = JSON.parse(message.data, symbolize_names: true) + raise ArgumentError, "delete-object data must be an object" unless data.is_a?(Hash) + unless LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(data) delete_object = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.from_h(data) change_set_builder.add_delete(delete_object.kind, delete_object.key, delete_object.version) diff --git a/spec/impl/data_system/polling_payload_parsing_spec.rb b/spec/impl/data_system/polling_payload_parsing_spec.rb index e590285a..7112cc8b 100644 --- a/spec/impl/data_system/polling_payload_parsing_spec.rb +++ b/spec/impl/data_system/polling_payload_parsing_spec.rb @@ -134,6 +134,17 @@ module DataSystem expect(result.success?).to eq(false) end + it "handles put and delete objects whose data is not an object" do + %w[put-object delete-object].each do |event_name| + ['"x"', "[]", "null", "5"].each do |data| + payload_str = '{"events":[ {"event":"server-intent","data":{"payloads":[ {"id":"5A46PZ79FQ9D08YYKT79DECDNV","target":461,"intentCode":"xfer-full","reason":"payload-missing"}]}},{"event": "' + event_name + '","data": ' + data + '},{"event":"payload-transferred","data":{"state":"(p:5A46PZ79FQ9D08YYKT79DECDNV:461)","id":"5A46PZ79FQ9D08YYKT79DECDNV","version":461}}]}' # rubocop:disable Layout/LineLength + result = LaunchDarkly::Impl::DataSystem.polling_payload_to_changeset(JSON.parse(payload_str, symbolize_names: true)) + expect(result.success?).to eq(false) + expect(result.error).to eq("Invalid payload: '#{event_name}' data must be an object") + end + end + end + it "handles invalid payload transferred" do payload_str = '{"events":[ {"event":"server-intent","data":{"payloads":[ {"id":"5A46PZ79FQ9D08YYKT79DECDNV","target":461,"intentCode":"xfer-full","reason":"payload-missing"}]}},{"event":"payload-transferred","data":{}}]}' # rubocop:disable Layout/LineLength result = LaunchDarkly::Impl::DataSystem.polling_payload_to_changeset(JSON.parse(payload_str, symbolize_names: true)) diff --git a/spec/impl/data_system/streaming_synchronizer_spec.rb b/spec/impl/data_system/streaming_synchronizer_spec.rb index 860c5029..b16b7d25 100644 --- a/spec/impl/data_system/streaming_synchronizer_spec.rb +++ b/spec/impl/data_system/streaming_synchronizer_spec.rb @@ -281,6 +281,19 @@ def initialize(type, data = nil) expect(update.change_set.changes[1].key).to eq(:segmentkey) end + it "rejects put and delete objects whose data is not an object" do + [ + LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT, + LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT, + ].each do |event_name| + ['"x"', "[]", "null", "5"].each do |data| + expect { + synchronizer.send(:process_message, MockSSEEvent.new(event_name, data), change_set_builder, envid) + }.to raise_error(ArgumentError, "#{event_name} data must be an object") + end + end + end + it "swallows goodbye events" do server_intent = LaunchDarkly::Interfaces::DataSystem::ServerIntent.new( payload: LaunchDarkly::Interfaces::DataSystem::Payload.new( From f60707b81e1647e47ef2ee241bc8d6ffe908fdaa Mon Sep 17 00:00:00 2001 From: Ryan Lamb <4955475+kinyoklion@users.noreply.github.com> Date: Thu, 29 Oct 2026 17:04:53 +0000 Subject: [PATCH 3/3] fix: Reject put and delete objects whose kind is missing or not a string --- lib/ldclient-rb/impl/data_system/polling.rb | 6 ++++++ lib/ldclient-rb/impl/data_system/protocolv2.rb | 5 ++--- lib/ldclient-rb/impl/data_system/streaming.rb | 2 ++ .../data_system/polling_payload_parsing_spec.rb | 13 +++++++++++++ .../data_system/streaming_synchronizer_spec.rb | 15 +++++++++++++++ 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/lib/ldclient-rb/impl/data_system/polling.rb b/lib/ldclient-rb/impl/data_system/polling.rb index e6a304e8..078352ef 100644 --- a/lib/ldclient-rb/impl/data_system/polling.rb +++ b/lib/ldclient-rb/impl/data_system/polling.rb @@ -500,6 +500,9 @@ def self.polling_payload_to_changeset(data) unless event[:data].is_a?(Hash) return LaunchDarkly::Result.fail("Invalid payload: 'put-object' data must be an object") end + unless event[:data][:kind].is_a?(String) + return LaunchDarkly::Result.fail("Invalid payload: 'put-object' kind must be a string") + end next if LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(event[:data]) @@ -515,6 +518,9 @@ def self.polling_payload_to_changeset(data) unless event[:data].is_a?(Hash) return LaunchDarkly::Result.fail("Invalid payload: 'delete-object' data must be an object") end + unless event[:data][:kind].is_a?(String) + return LaunchDarkly::Result.fail("Invalid payload: 'delete-object' kind must be a string") + end next if LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(event[:data]) diff --git a/lib/ldclient-rb/impl/data_system/protocolv2.rb b/lib/ldclient-rb/impl/data_system/protocolv2.rb index 3e5c6158..1ed4ee88 100644 --- a/lib/ldclient-rb/impl/data_system/protocolv2.rb +++ b/lib/ldclient-rb/impl/data_system/protocolv2.rb @@ -20,12 +20,11 @@ module ProtocolV2 # does not support. Such objects are ignored so that payloads carrying # newer kinds can still be applied. # - # @param data [Hash] The hash representation of the event data + # @param data [Hash] The hash representation of the event data, with a String kind # @return [Boolean] # def self.unsupported_kind?(data) - kind = data[:kind] - !kind.nil? && !SUPPORTED_KINDS.include?(kind) + !SUPPORTED_KINDS.include?(data[:kind]) end # diff --git a/lib/ldclient-rb/impl/data_system/streaming.rb b/lib/ldclient-rb/impl/data_system/streaming.rb index ab581f0f..d3c9a5f4 100644 --- a/lib/ldclient-rb/impl/data_system/streaming.rb +++ b/lib/ldclient-rb/impl/data_system/streaming.rb @@ -248,6 +248,7 @@ def stop when LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT data = JSON.parse(message.data, symbolize_names: true) raise ArgumentError, "put-object data must be an object" unless data.is_a?(Hash) + raise ArgumentError, "put-object kind must be a string" unless data[:kind].is_a?(String) unless LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(data) put = LaunchDarkly::Impl::DataSystem::ProtocolV2::PutObject.from_h(data) @@ -258,6 +259,7 @@ def stop when LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT data = JSON.parse(message.data, symbolize_names: true) raise ArgumentError, "delete-object data must be an object" unless data.is_a?(Hash) + raise ArgumentError, "delete-object kind must be a string" unless data[:kind].is_a?(String) unless LaunchDarkly::Impl::DataSystem::ProtocolV2.unsupported_kind?(data) delete_object = LaunchDarkly::Impl::DataSystem::ProtocolV2::DeleteObject.from_h(data) diff --git a/spec/impl/data_system/polling_payload_parsing_spec.rb b/spec/impl/data_system/polling_payload_parsing_spec.rb index 7112cc8b..74d671fe 100644 --- a/spec/impl/data_system/polling_payload_parsing_spec.rb +++ b/spec/impl/data_system/polling_payload_parsing_spec.rb @@ -145,6 +145,19 @@ module DataSystem end end + it "handles put and delete objects whose kind is not a string" do + %w[put-object delete-object].each do |event_name| + object_field = event_name == "put-object" ? ',"object":{}' : "" + ["", '"kind":null,', '"kind":5,', '"kind":["flag"],'].each do |kind_field| + data = '{' + kind_field + '"key":"sampleflag","version":461' + object_field + '}' + payload_str = '{"events":[ {"event":"server-intent","data":{"payloads":[ {"id":"5A46PZ79FQ9D08YYKT79DECDNV","target":461,"intentCode":"xfer-full","reason":"payload-missing"}]}},{"event": "' + event_name + '","data": ' + data + '},{"event":"payload-transferred","data":{"state":"(p:5A46PZ79FQ9D08YYKT79DECDNV:461)","id":"5A46PZ79FQ9D08YYKT79DECDNV","version":461}}]}' # rubocop:disable Layout/LineLength + result = LaunchDarkly::Impl::DataSystem.polling_payload_to_changeset(JSON.parse(payload_str, symbolize_names: true)) + expect(result.success?).to eq(false) + expect(result.error).to eq("Invalid payload: '#{event_name}' kind must be a string") + end + end + end + it "handles invalid payload transferred" do payload_str = '{"events":[ {"event":"server-intent","data":{"payloads":[ {"id":"5A46PZ79FQ9D08YYKT79DECDNV","target":461,"intentCode":"xfer-full","reason":"payload-missing"}]}},{"event":"payload-transferred","data":{}}]}' # rubocop:disable Layout/LineLength result = LaunchDarkly::Impl::DataSystem.polling_payload_to_changeset(JSON.parse(payload_str, symbolize_names: true)) diff --git a/spec/impl/data_system/streaming_synchronizer_spec.rb b/spec/impl/data_system/streaming_synchronizer_spec.rb index b16b7d25..6a7ba32e 100644 --- a/spec/impl/data_system/streaming_synchronizer_spec.rb +++ b/spec/impl/data_system/streaming_synchronizer_spec.rb @@ -294,6 +294,21 @@ def initialize(type, data = nil) end end + it "rejects put and delete objects whose kind is not a string" do + [ + LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT, + LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT, + ].each do |event_name| + object_field = event_name == LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT ? ',"object":{}' : "" + ["", '"kind":null,', '"kind":5,', '"kind":["flag"],'].each do |kind_field| + data = '{' + kind_field + '"key":"flagkey","version":100' + object_field + '}' + expect { + synchronizer.send(:process_message, MockSSEEvent.new(event_name, data), change_set_builder, envid) + }.to raise_error(ArgumentError, "#{event_name} kind must be a string") + end + end + end + it "swallows goodbye events" do server_intent = LaunchDarkly::Interfaces::DataSystem::ServerIntent.new( payload: LaunchDarkly::Interfaces::DataSystem::Payload.new(