diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc7ac68..a2cb5d42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,14 +24,20 @@ Please file a bug if you notice a violation of semantic versioning. ### Changed +- auth-sanitizer v0.1.3 + ### Deprecated ### Removed ### Fixed +- [gh!721][gh!721] Load `auth-sanitizer` through an internal isolated loader so requiring `oauth2` does not add top-level `Auth` or `AuthSanitizer` constants that may collide with downstream applications by @pboling + ### Security +[gh!721]: https://github.com/ruby-oauth/oauth2/pull/721 + ## [2.0.19] - 2026-05-15 - TAG: [v2.0.19][2.0.19t] diff --git a/Gemfile.lock b/Gemfile.lock index a4889eec..2a79c7bd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,14 @@ PATH remote: . specs: - oauth2 (2.0.19) - auth-sanitizer (~> 0.1) + oauth2 (2.0.20) + auth-sanitizer (~> 0.1, >= 0.1.3) faraday (>= 0.17.3, < 4.0) jwt (>= 1.0, < 4.0) logger (~> 1.2) multi_xml (~> 0.5) rack (>= 1.2, < 4) - snaky_hash (~> 2.0, >= 2.0.3) + snaky_hash (~> 2.0, >= 2.0.4) version_gem (~> 1.1, >= 1.1.9) GEM @@ -22,7 +22,7 @@ GEM rake (>= 10) thor (>= 0.14) ast (2.4.3) - auth-sanitizer (0.1.2) + auth-sanitizer (0.1.3) version_gem (~> 1.1, >= 1.1.9) backports (3.25.3) base64 (0.3.0) @@ -74,7 +74,7 @@ GEM faraday-net_http (>= 2.0, < 3.5) json logger - faraday-net_http (3.4.2) + faraday-net_http (3.4.3) net-http (~> 0.5) gem_bench (2.0.5) bundler (>= 1.14) @@ -280,7 +280,7 @@ GEM simplecov-rcov (0.3.7) simplecov (>= 0.4.1) simplecov_json_formatter (0.1.4) - snaky_hash (2.0.3) + snaky_hash (2.0.4) hashie (>= 0.1.0, < 6) version_gem (>= 1.1.8, < 3) standard (1.54.0) @@ -330,7 +330,7 @@ GEM yard yard-relative_markdown_links (0.5.0) nokogiri (>= 1.14.3, < 2) - zeitwerk (2.7.5) + zeitwerk (2.8.1) zlib (3.2.3) PLATFORMS @@ -348,7 +348,7 @@ DEPENDENCIES appraisal2 (~> 3.0, >= 3.0.6) backports (~> 3.25, >= 3.25.1) benchmark (~> 0.4, >= 0.4.1) - bundler-audit (~> 0.9.2) + bundler-audit (~> 0.9.3) debug (>= 1.1) erb (~> 5.0) gem_bench (~> 2.0, >= 2.0.5) @@ -386,7 +386,7 @@ CHECKSUMS ansi (1.6.0) sha256=ac9ea0c0ea8d32fb4e271348e609963ac78882f34b73836c2a02b3622e666658 appraisal2 (3.0.6) sha256=09387896b6c8c8c0ff0749af691ddff5e3168de2f06b591a80d8fd8b6394d147 ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 - auth-sanitizer (0.1.2) sha256=29f7638d74b2a19ff890008f1561165668a78969a4d90bc85e991128825a7c03 + auth-sanitizer (0.1.3) sha256=51002d96e921eb9610b197f39de92ae63df11eeb3c5f2bfd7829feb725911487 backports (3.25.3) sha256=94298d32dc3c40ca15633b54e282780b49e2db0c045f602ea1907e4f63a17235 base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c @@ -408,7 +408,7 @@ CHECKSUMS dry-types (1.9.1) sha256=baebeecdb9f8395d6c9d227b62011279440943e3ef2468fe8ccc1ba11467f178 erb (5.1.3) sha256=566e53057b6ba48699f824b578473b391fa8aef100aa14afad1c46725fae0e67 faraday (2.14.2) sha256=73ccb9994a9e8648f010e32eca2ae82e41c57860aa10932cda29418b9e0223ad - faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c + faraday-net_http (3.4.3) sha256=9db13becec9312f345a769eeeecf9049c9287d54c0ae053d7235228993a4eec1 gem_bench (2.0.5) sha256=0dc0fb44a5a5eb7b2f5c1c68a5b0164d72007132822c012bac3abe976b199ead gitmoji-regex (1.0.3) sha256=538c6f49f5af6dc36d1630edb89a5a66f6e14ec5850d7fd071e0331f940e553f hashie (5.1.0) sha256=c266471896f323c446ea8207f8ffac985d2718df0a0ba98651a3057096ca3870 @@ -436,7 +436,7 @@ CHECKSUMS nokogiri (1.19.3-x86_64-darwin) sha256=77f3fba57d46c53ab31e62fc6c28f705109d1bf6264356c76f132b2be5728d4d nokogiri (1.19.3-x86_64-linux-gnu) sha256=2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976 nokogiri (1.19.3-x86_64-linux-musl) sha256=248c906d2166eca5efb56d52fdee5f9a1f51d69a72e2b64fdac647b4ce39ea3f - oauth2 (2.0.19) + oauth2 (2.0.20) ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 parallel (1.28.0) sha256=33e6de1484baf2524792d178b0913fc8eb94c628d6cfe45599ad4458c638c970 parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54 @@ -489,7 +489,7 @@ CHECKSUMS simplecov-lcov (0.9.0) sha256=7a77a31e200a595ed4b0249493056efd0c920601f53d2ef135ca34ee796346cd simplecov-rcov (0.3.7) sha256=372f50bf6df6b6350b7d0c840f2f8bdabe021861a43c26877b747c9ac96139fc simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428 - snaky_hash (2.0.3) sha256=25a3d299566e8153fb02fa23fd9a9358845950f7a523ddbbe1fa1e0d79a6d456 + snaky_hash (2.0.4) sha256=2b12758c57defa6796341a1620f84b1a23737421d8d7e2575d0550b53cc4fece standard (1.54.0) sha256=7a4b08f83d9893083c8f03bc486f0feeb6a84d48233b40829c03ef4767ea0100 standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2 @@ -509,7 +509,7 @@ CHECKSUMS yard-fence (0.8.2) sha256=4efa58d682fc0eb57258a6d6a8463b61af0890082cdea45f19088b8634d9ab6f yard-junk (0.1.0) sha256=e85fe2ec1afa47313decd333447b53458cb1ed49b510b70015fdc3041a94bcdd yard-relative_markdown_links (0.5.0) sha256=d5158786196bfb82ed8f6880cefea2ef3072cc9e774ecebd7803e0db9bbb3a71 - zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd + zeitwerk (2.8.1) sha256=1c85e0f28954d68cd16e575da37f26846f609b68d80b5942ccfd31030c2449d5 zlib (3.2.3) sha256=5bd316698b32f31a64ab910a8b6c282442ca1626a81bbd6a1674e8522e319c20 BUNDLED WITH diff --git a/lib/oauth2.rb b/lib/oauth2.rb index 4a0f3aeb..63e7975b 100644 --- a/lib/oauth2.rb +++ b/lib/oauth2.rb @@ -5,12 +5,12 @@ require "time" # third party gems -require "auth/sanitizer" require "snaky_hash" require "version_gem" # includes gem files require_relative "oauth2/version" +require_relative "oauth2/auth_sanitizer" require_relative "oauth2/filtered_attributes" require_relative "oauth2/error" require_relative "oauth2/authenticator" @@ -92,10 +92,10 @@ def configure end end -# Wire Auth::Sanitizer's label provider to read from OAuth2.config so that -# FilteredAttributes-bearing objects and Auth::Sanitizer::SanitizedLogger instances +# Wire OAuth2::AUTH_SANITIZER's label provider to read from OAuth2.config so that +# FilteredAttributes-bearing objects and OAuth2::AUTH_SANITIZER::SanitizedLogger instances # pick up OAuth2.config[:filtered_label] at their initialization time. -Auth::Sanitizer.filtered_label_provider = -> { OAuth2.config[:filtered_label] } +OAuth2::AUTH_SANITIZER.filtered_label_provider = -> { OAuth2.config[:filtered_label] } # Extend OAuth2::Version with VersionGem helpers to provide semantic version helpers. OAuth2::Version.class_eval do diff --git a/lib/oauth2/auth_sanitizer.rb b/lib/oauth2/auth_sanitizer.rb new file mode 100644 index 00000000..3397d627 --- /dev/null +++ b/lib/oauth2/auth_sanitizer.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module OAuth2 + AUTH_SANITIZER = begin + auth_sanitizer_requirement = Gem::Requirement.new("~> 0.1", ">= 0.1.3") + auth_sanitizer_spec = Gem.loaded_specs["auth-sanitizer"] + unless auth_sanitizer_spec && auth_sanitizer_requirement.satisfied_by?(auth_sanitizer_spec.version) + auth_sanitizer_spec = Gem::Specification.find_by_name("auth-sanitizer", auth_sanitizer_requirement) + end + + auth_sanitizer_loader_path = File.join( + auth_sanitizer_spec.full_gem_path, + "lib/auth_sanitizer/loader.rb", + ) + unless File.file?(auth_sanitizer_loader_path) + raise LoadError, "oauth2 requires auth-sanitizer #{auth_sanitizer_requirement}; " \ + "loader not found at #{auth_sanitizer_loader_path}" + end + + auth_sanitizer_loader_namespace = Module.new + auth_sanitizer_loader_namespace.module_eval( + File.read(auth_sanitizer_loader_path), + auth_sanitizer_loader_path, + 1, + ) + + auth_sanitizer_loader_namespace. + const_get(:AuthSanitizer). + const_get(:Loader). + load_isolated + end +end diff --git a/lib/oauth2/client.rb b/lib/oauth2/client.rb index 5504ecba..83858964 100644 --- a/lib/oauth2/client.rb +++ b/lib/oauth2/client.rb @@ -42,7 +42,7 @@ class Client # rubocop:disable Metrics/ClassLength # @option options [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday # @option options [Boolean] :raise_errors (true) whether to raise an OAuth2::Error on responses with 400+ status codes # @option options [Integer] :max_redirects (5) maximum number of redirects to follow - # @option options [Logger] :logger (::Logger.new($stdout)) Logger instance for HTTP request/response output; requires OAUTH_DEBUG to be true. When debug logging is enabled, sensitive values are filtered using {Auth::Sanitizer::SanitizedLogger} initialized from `OAuth2.config[:filtered_label]` and the key names in `OAuth2.config[:filtered_debug_keys]`. + # @option options [Logger] :logger (::Logger.new($stdout)) Logger instance for HTTP request/response output; requires OAUTH_DEBUG to be true. When debug logging is enabled, sensitive values are filtered using {OAuth2::AUTH_SANITIZER::SanitizedLogger} initialized from `OAuth2.config[:filtered_label]` and the key names in `OAuth2.config[:filtered_debug_keys]`. # @option options [Class] :access_token_class (AccessToken) class to use for access tokens; you can subclass OAuth2::AccessToken, @version 2.0+ # @option options [Hash] :ssl SSL options for Faraday # @@ -565,7 +565,7 @@ def build_access_token_legacy(response, access_token_opts, extract_access_token) def oauth_debug_logging(builder) builder.response( :logger, - Auth::Sanitizer::SanitizedLogger.new( + OAuth2::AUTH_SANITIZER::SanitizedLogger.new( options[:logger], filtered_keys: OAuth2.config[:filtered_debug_keys], label: OAuth2.config[:filtered_label], diff --git a/lib/oauth2/filtered_attributes.rb b/lib/oauth2/filtered_attributes.rb index cc470099..4c077047 100644 --- a/lib/oauth2/filtered_attributes.rb +++ b/lib/oauth2/filtered_attributes.rb @@ -1,13 +1,10 @@ # frozen_string_literal: true module OAuth2 - # Permanent alias for {Auth::Sanitizer::FilteredAttributes}. + # Permanent alias for {OAuth2::AUTH_SANITIZER::FilteredAttributes}. # # This constant is intentionally kept in the `OAuth2` namespace because it # was part of the public API before the implementation was extracted into the # `auth-sanitizer` gem. It will **not** be deprecated or removed. - # - # New code that does not need the `OAuth2::` namespace can use - # {Auth::Sanitizer::FilteredAttributes} directly. - FilteredAttributes = Auth::Sanitizer::FilteredAttributes + FilteredAttributes = OAuth2::AUTH_SANITIZER::FilteredAttributes end diff --git a/lib/oauth2/version.rb b/lib/oauth2/version.rb index d842d083..c5dbb273 100644 --- a/lib/oauth2/version.rb +++ b/lib/oauth2/version.rb @@ -2,7 +2,7 @@ module OAuth2 module Version - VERSION = "2.0.19" + VERSION = "2.0.20" end VERSION = Version::VERSION # Traditional Constant Location end diff --git a/oauth2.gemspec b/oauth2.gemspec index 778d86fa..e519071b 100644 --- a/oauth2.gemspec +++ b/oauth2.gemspec @@ -126,14 +126,14 @@ Thanks, @pboling / @galtzo spec.executables = [] # Utilities - spec.add_dependency("auth-sanitizer", "~> 0.1") # ruby >= 2.2.0 - spec.add_dependency("faraday", [">= 0.17.3", "< 4.0"]) # ruby >= 1.9 - spec.add_dependency("jwt", [">= 1.0", "< 4.0"]) # ruby >= 0 - spec.add_dependency("logger", "~> 1.2") # ruby >= 0 - spec.add_dependency("multi_xml", "~> 0.5") # ruby >= 0 - spec.add_dependency("rack", [">= 1.2", "< 4"]) # ruby >= 0 - spec.add_dependency("snaky_hash", "~> 2.0", ">= 2.0.3") # ruby >= 2.2 - spec.add_dependency("version_gem", "~> 1.1", ">= 1.1.9") # ruby >= 2.2.0 + spec.add_dependency("auth-sanitizer", "~> 0.1", ">= 0.1.3") # ruby >= 2.2.0 + spec.add_dependency("faraday", [">= 0.17.3", "< 4.0"]) # ruby >= 1.9 + spec.add_dependency("jwt", [">= 1.0", "< 4.0"]) # ruby >= 0 + spec.add_dependency("logger", "~> 1.2") # ruby >= 0 + spec.add_dependency("multi_xml", "~> 0.5") # ruby >= 0 + spec.add_dependency("rack", [">= 1.2", "< 4"]) # ruby >= 0 + spec.add_dependency("snaky_hash", "~> 2.0", ">= 2.0.4") # ruby >= 2.2.0 + spec.add_dependency("version_gem", "~> 1.1", ">= 1.1.9") # ruby >= 2.2.0 # NOTE: It is preferable to list development dependencies in the gemspec due to increased # visibility and discoverability. @@ -156,7 +156,7 @@ Thanks, @pboling / @galtzo spec.add_development_dependency("kettle-dev", "~> 2.0") # ruby >= 2.3.0 # Security - spec.add_development_dependency("bundler-audit", "~> 0.9.2") # ruby >= 2.0.0 + spec.add_development_dependency("bundler-audit", "~> 0.9.3") # ruby >= 2.0.0 # Tasks spec.add_development_dependency("rake", "~> 13.0") # ruby >= 2.2.0 diff --git a/spec/auth/sanitizer/thing_filter_spec.rb b/spec/auth/sanitizer/thing_filter_spec.rb index d7a7e1b6..07b2fe20 100644 --- a/spec/auth/sanitizer/thing_filter_spec.rb +++ b/spec/auth/sanitizer/thing_filter_spec.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true -RSpec.describe Auth::Sanitizer::ThingFilter do +RSpec.describe "OAuth2::AUTH_SANITIZER::ThingFilter" do + let(:thing_filter_class) { OAuth2::AUTH_SANITIZER::ThingFilter } + describe "#initialize" do it "does not freeze caller-owned string inputs" do things = [String.new("secret")] label = String.new("[FILTERED]") - described_class.new(things, label: label) + thing_filter_class.new(things, label: label) expect(things.first).not_to be_frozen expect(label).not_to be_frozen @@ -16,7 +18,7 @@ original_thing = String.new("secret") original_label = String.new("[FILTERED]") - filter = described_class.new([original_thing], label: original_label) + filter = thing_filter_class.new([original_thing], label: original_label) original_thing.replace("token") original_label.replace("[REDACTED]") @@ -30,7 +32,7 @@ describe "FilteredAttributes integration" do let(:poro_class) do Class.new do - include Auth::Sanitizer::FilteredAttributes + include OAuth2::AUTH_SANITIZER::FilteredAttributes attr_reader :secret, :name filtered_attributes :secret diff --git a/spec/oauth2/auth_sanitizer_spec.rb b/spec/oauth2/auth_sanitizer_spec.rb new file mode 100644 index 00000000..dfc72208 --- /dev/null +++ b/spec/oauth2/auth_sanitizer_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +RSpec.describe "OAuth2::AUTH_SANITIZER" do + it "keeps auth-sanitizer constants isolated inside the OAuth2 namespace" do + expect(Object.const_defined?(:Auth, false)).to be(false) + expect(Object.const_defined?(:AuthSanitizer, false)).to be(false) + end + + it "preserves the public OAuth2::FilteredAttributes alias" do + expect(OAuth2::FilteredAttributes).to be(OAuth2::AUTH_SANITIZER::FilteredAttributes) + end +end