Skip to content

Commit c65c294

Browse files
committed
Merge branch 'main' into jb/update-private-docs
2 parents 26f64dc + aa56ee5 commit c65c294

File tree

10 files changed

+109
-5
lines changed

10 files changed

+109
-5
lines changed

.github/actions/setup/action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ runs:
1515
- uses: ruby/setup-ruby@v1
1616
with:
1717
ruby-version: ${{ inputs.version }}
18+
bundler: 2
1819

1920
- name: Install dependencies
2021
if: ${{ inputs.install-dependencies == 'true' }}

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "8.11.1"
2+
".": "8.11.2"
33
}

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All notable changes to the LaunchDarkly Ruby SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
44

5+
## [8.11.2](https://github.com/launchdarkly/ruby-server-sdk/compare/8.11.1...8.11.2) (2025-12-05)
6+
7+
8+
### Bug Fixes
9+
10+
* Fix diagnostic logging for connection results in stream ([#343](https://github.com/launchdarkly/ruby-server-sdk/issues/343)) ([d49eaa8](https://github.com/launchdarkly/ruby-server-sdk/commit/d49eaa895ca370c219439e726771e17154709aa3))
11+
* Prevent command injection in FileDataSourceImpl ([#341](https://github.com/launchdarkly/ruby-server-sdk/issues/341)) ([9ca4b98](https://github.com/launchdarkly/ruby-server-sdk/commit/9ca4b985c58c62f7b8b454d43a8548924fd4715f))
12+
513
## [8.11.1](https://github.com/launchdarkly/ruby-server-sdk/compare/8.11.0...8.11.1) (2025-10-10)
614

715

PROVENANCE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ To verify SLSA provenance attestations, we recommend using [slsa-verifier](https
99
<!-- x-release-please-start-version -->
1010
```
1111
# Set the version of the SDK to verify
12-
SDK_VERSION=8.11.1
12+
SDK_VERSION=8.11.2
1313
```
1414
<!-- x-release-please-end -->
1515

lib/ldclient-rb/events.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ class EventDispatcher
233233
def initialize(inbox, sdk_key, config, diagnostic_accumulator, event_sender)
234234
@sdk_key = sdk_key
235235
@config = config
236-
@diagnostic_accumulator = config.diagnostic_opt_out? ? nil : diagnostic_accumulator
236+
@diagnostic_accumulator = diagnostic_accumulator
237237
@event_sender = event_sender
238238
@sampler = LaunchDarkly::Impl::Sampler.new(Random.new)
239239

lib/ldclient-rb/impl/data_source/stream.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class StreamProcessor
2929
def initialize(sdk_key, config, diagnostic_accumulator = nil)
3030
@sdk_key = sdk_key
3131
@config = config
32+
@diagnostic_accumulator = diagnostic_accumulator
3233
@data_source_update_sink = config.data_source_update_sink
3334
@feature_store = config.feature_store
3435
@initialized = Concurrent::AtomicBoolean.new(false)

lib/ldclient-rb/impl/integrations/file_data_source.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def load_file(path, all_data)
102102
@last_version += 1
103103
}
104104

105-
parsed = parse_content(IO.read(path))
105+
parsed = parse_content(File.read(path))
106106
(parsed[:flags] || {}).each do |key, flag|
107107
flag[:version] = version
108108
add_item(all_data, Impl::DataStore::FEATURES, flag)

lib/ldclient-rb/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module LaunchDarkly
2-
VERSION = "8.11.1" # x-release-please-version
2+
VERSION = "8.11.2" # x-release-please-version
33
end

spec/impl/data_source/stream_spec.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,5 +67,71 @@ module LaunchDarkly
6767
expect(listener.statuses[1].last_error.kind).to eq(Interfaces::DataSource::ErrorInfo::INVALID_DATA)
6868
end
6969
end
70+
71+
describe '#log_connection_result' do
72+
it "logs successful connection when diagnostic_accumulator is provided" do
73+
diagnostic_accumulator = double("DiagnosticAccumulator")
74+
expect(diagnostic_accumulator).to receive(:record_stream_init).with(
75+
kind_of(Integer),
76+
false,
77+
kind_of(Integer)
78+
)
79+
80+
processor = subject.new("sdk_key", config, diagnostic_accumulator)
81+
processor.send(:log_connection_started)
82+
processor.send(:log_connection_result, true)
83+
end
84+
85+
it "logs failed connection when diagnostic_accumulator is provided" do
86+
diagnostic_accumulator = double("DiagnosticAccumulator")
87+
expect(diagnostic_accumulator).to receive(:record_stream_init).with(
88+
kind_of(Integer),
89+
true,
90+
kind_of(Integer)
91+
)
92+
93+
processor = subject.new("sdk_key", config, diagnostic_accumulator)
94+
processor.send(:log_connection_started)
95+
processor.send(:log_connection_result, false)
96+
end
97+
98+
it "logs connection metrics with correct timestamp and duration" do
99+
diagnostic_accumulator = double("DiagnosticAccumulator")
100+
101+
processor = subject.new("sdk_key", config, diagnostic_accumulator)
102+
103+
expect(diagnostic_accumulator).to receive(:record_stream_init) do |timestamp, failed, duration|
104+
expect(timestamp).to be_a(Integer)
105+
expect(timestamp).to be > 0
106+
expect(failed).to eq(false)
107+
expect(duration).to be_a(Integer)
108+
expect(duration).to be >= 0
109+
end
110+
111+
processor.send(:log_connection_started)
112+
sleep(0.01) # Small delay to ensure measurable duration
113+
processor.send(:log_connection_result, true)
114+
end
115+
116+
it "only logs once per connection attempt" do
117+
diagnostic_accumulator = double("DiagnosticAccumulator")
118+
expect(diagnostic_accumulator).to receive(:record_stream_init).once
119+
120+
processor = subject.new("sdk_key", config, diagnostic_accumulator)
121+
processor.send(:log_connection_started)
122+
processor.send(:log_connection_result, true)
123+
# Second call should not trigger another log
124+
processor.send(:log_connection_result, true)
125+
end
126+
127+
it "works gracefully when no diagnostic_accumulator is provided" do
128+
processor = subject.new("sdk_key", config, nil)
129+
130+
expect {
131+
processor.send(:log_connection_started)
132+
processor.send(:log_connection_result, true)
133+
}.not_to raise_error
134+
end
135+
end
70136
end
71137
end

spec/integrations/file_data_source_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,34 @@ def with_data_source(options, initialize_to_valid = false)
191191
end
192192
end
193193

194+
it "does not execute commands via malicious filenames" do
195+
# This tests that filenames containing shell metacharacters are treated as literal paths
196+
# and do not result in command execution. The file reading should use safe methods
197+
# that don't pass paths through a shell.
198+
marker_file = File.join(@tmp_dir, "command_injection_marker")
199+
200+
# Various command injection attempts - if any command executed, it would create the marker file
201+
malicious_paths = [
202+
"|touch #{marker_file}",
203+
";touch #{marker_file}",
204+
"$(touch #{marker_file})",
205+
"`touch #{marker_file}`",
206+
"& touch #{marker_file}",
207+
"\ntouch #{marker_file}\n",
208+
]
209+
210+
malicious_paths.each do |malicious_path|
211+
with_data_source({ paths: [malicious_path] }) do |ds|
212+
event = ds.start
213+
expect(event.set?).to eq(true)
214+
# Should fail to initialize because the file doesn't exist (treated as literal path)
215+
expect(ds.initialized?).to eq(false)
216+
# Most importantly: no command should have been executed
217+
expect(File.exist?(marker_file)).to eq(false), "Command injection detected with path: #{malicious_path}"
218+
end
219+
end
220+
end
221+
194222
it "sets start event and initialized on successful load" do
195223
file = make_temp_file(all_properties_json)
196224
with_data_source({ paths: [ file.path ] }) do |ds|

0 commit comments

Comments
 (0)