Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
This Ruby gem allows you to generate consistent random values tied to a specific name within a defined scope. It ensures that random behavior remains consistent within a particular context.
Consistent Random is designed to simplify feature rollouts and other scenarios where you need to generate random values, but need those values to remain consistent within defined contexts.
For example, consider rolling out a new feature to a subset of requests. You may want to do this to allow testing a new feature by only enabling it for 10% of requests. You want to randomize which requests get the new feature, but ensure that within each request, the feature is consistently enabled or disabled across all actions. This gem allows you to achieve that by tying random values to specific names and defining a scope. Within that scope, the same value will be consistently generated for each named variable.
To generate consistent random values, you need to define a scope. Scopes are defined with the ConsistentRandom.scope
method. Within the scope block, calls to ConsistentRandom
will return the same random values for the same name. Scopes are isolated to the block in which they're defined, meaning random values are consistent within each scoped block but independent across threads or separate invocations.
ConsistentRandom.scope do
random = ConsistentRandom.new("foobar")
a = random.rand(100) # Generates a random number between 0 and 99 tied to "foobar"
b = random.rand(100) # Same random number as 'a', because "foobar" is reused
a == b # => true
end
This can be used to implement things like feature flags for rolling out new features on a percentage of your requests.
class FeatureFlag
def initialize(name, roll_out_percentage)
@name = name
@roll_out_percentage = roll_out_percentage
end
def enabled?
ConsistentRandom.new("FeatureFlag.#{@name}").rand < @roll_out_percentage
end
end
Checking a feature flag will return the same value within a scope.
class MyService
def call(arg)
if FeatureFlag.new("new_feature", 0.1).enabled?
# Do something new 10% of the time
else
# Do something else
end
end
end
ConsistentRandom.scope do
things.each do |thing|
MyService.new.call(thing) # You won't get a mix of new and old behavior within this iteration
end
end
If there is no scope defined, the random values will be different each time for different instances of ConsistentRandom
. So, if your code is executed outside of a scope, it will still work but with random values being generated rather than consistent values.
random = ConsistentRandom.new("foobar")
random.rand != random.rand # => true
The gem provides built-in middlewares for Rack, Sidekiq, and ActiveJob. These middlewares allow you to automatically scope web requests and propagate consistent random values from the original request to asynchronous jobs.
In a Rack application:
Rack::Builder.app do
use ConsistentRandom::RackMiddleware
run MyApp
end
Or in a Rails application:
# config/application.rb
config.middleware.use ConsistentRandom::RackMiddleware
You can also specify a seed value based on the request. This can be useful if you want to generate random values based on a specific request attribute, such as the current user.
Rack::Builder.app do
use ConsistentRandom::RackMiddleware, ->(env) { env["warden"].user.id }
run MyApp
end
If the seed block returns nil
, then a random seed will be generated for the request.
Add the middlewares to your Sidekiq in an initializer:
ConsistentRandom::SidekiqMiddleware.install
This will install both the client and server middleware. You can also install them manually if you need more control on the order of the middlewares. You should install the client middleware on both the server and client configurations.
Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.prepend ConsistentRandom::SidekiqMiddleware
end
config.client_middleware do |chain|
chain.add ConsistentRandom::SidekiqClientMiddleware
end
end
Sidekiq.configure_client do |config|
config.client_middleware do |chain|
chain.add ConsistentRandom::SidekiqClientMiddleware
end
end
Consistent random values will be propagated from the original request to any Sidekiq jobs so you will get consistent behavior on any ansynchronous jobs. You can disable this behavior on a job by setting the conistent_random
sidekiq option to false
:
class MyWorker
include Sidekiq::Job
sidekiq_options consistent_random: false
def perform
# Each job will use it's own random scope.
end
end
You can still specify a custom seed value in your worker if, for example, you want to ensure that values are consistent based on a user when the job is not enqueued from a Rack request.
class MyWorker
include Sidekiq::Job
def perform(user_id)
ConsistentRandom.scope(user_id) do
...
end
end
end
You can use consistent random values in your ActiveJob jobs by including the ConsistentRandom::ActiveJob
module.
class MyJob < ApplicationJob
include ConsistentRandom::ActiveJob
def perform
# Job will use consistent random values using the same scope from when it was enqueued.
end
end
Jobs will inherit the same consistent random values as the request that spawned the job. You can force a job to use it's own random scope by setting the consistent_random
option to false
:
class MyJob < ApplicationJob
include ConsistentRandom::ActiveJob
self.inherit_consistent_random_scope = false
def perform
# Job will use it's own random scope.
end
end
You can still specify a custom seed value in your worker if, for example, you want to ensure that values are consistent based on a user when the job is not enqueued from a Rack request.
class MyJob < ApplicationJob
def perform(user_id)
ConsistentRandom.scope(user_id) do
...
end
end
end
The gem provides a ConsistentRandom.testing
method to allow for deterministic testing of random values. This method can be used to set fixed values within the block so that your tests will produce consistent results.
# Specify that all random values should be 0.5
ConsistentRandom.testing.rand(0.5) do
expect(ConsistentRandom.new("foo").rand).to eq(0.5)
expect(ConsistentRandom.new("bar").rand).to eq(0.5)
# The rand value must be between 0 and 1, but it will be scaled to fit
# any size or range specified for `rand`.
expect(ConsistentRandom.new("foo").rand(10)).to eq(5)
end
# You can also specify values for specific names.
# If a values isn't specified, it will return a random value.
ConsistentRandom.testing(foo: 0.5, bar: 0.8) do
expect(ConsistentRandom.new("foo").rand).to eq(0.5)
expect(ConsistentRandom.new("bar").rand).to eq(0.8)
end
# You can also specify values for the `bytes` and `seed` methods. The methods
# for setting test valus can be chained together.
ConsistentRandom.testing.bytes(foo: "bar").seed(baz: 123) do
expect(ConsistentRandom.new("foo").bytes(6)).to eq("barbar")
expect(ConsistentRandom.new("baz").seed).to eq(123)
end
Add this line to your application's Gemfile:
gem "consistent_random"
Then execute:
$ bundle
Or install it yourself as:
$ gem install consistent_random
Open a pull request on GitHub.
Please use the standardrb syntax and lint your code with standardrb --fix
before submitting.
The gem is available as open source under the terms of the MIT License.
FAQs
Unknown package
We found that consistent_random demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.