savon 2.18.0 → 2.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -2
- data/lib/savon/transport/faraday.rb +4 -0
- data/lib/savon/transport/header_validation.rb +62 -0
- data/lib/savon/transport/httpi.rb +4 -0
- data/lib/savon/version.rb +1 -1
- data/lib/savon.rb +4 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cec32bb7e6cf1477f3d93ab0befe29d05386220c73c46d62ab747bf822f591f3
|
|
4
|
+
data.tar.gz: 2c18c7678d283d3f8e29cb18bdce837a515afa0615cfe254e3702063caebee93
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae62d330b6fb3ade1fd4a78a4cbec0a2e79f3511dc9b60ba234939f02c489588d9651953d3adbf4326aab07d0765be9eda8dc1c5aa9db0fc21a5093087660279
|
|
7
|
+
data.tar.gz: 6ad397a186b317a3e9a1ba9caf125b54476dccaffdc8fac599653824e32686fa772075dcd504da037545f5d1f60bbdccad3d89be279da5cb840832e9dccca9c4
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.18.1] - 2026-11-06
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
* The outbound `SOAPAction` header now rejects control characters to prevent HTTP header injection ([GHSA-57m8-w3r7-5p3w](https://github.com/savonrb/savon/security/advisories/GHSA-57m8-w3r7-5p3w)). A crafted WSDL can encode a carriage return or line feed in its `soapAction` attribute as the character references ` ` and ` `, which survive XML attribute normalization and reach the header as real control characters. The httpclient adapter sent such a header unchecked, so it split on the wire and let an attacker inject further request headers. HTTPI picks httpclient automatically whenever the gem is installed, so the default configuration was affected. Both transports now validate the outgoing headers before the request is sent. A `SOAPAction` containing CR, LF, or NUL raises `Savon::InvalidHeaderError`, whether it comes from the WSDL, the `:soap_action` option, or the `:headers` option under any key spelling. With the net/http adapter, CR and LF used to raise net/http's own `ArgumentError`. A leading or trailing newline in a `:headers` value was stripped silently, and NUL was sent as-is. All of these now raise `Savon::InvalidHeaderError`. Thanks to @SDX442 for securely disclosing this.
|
|
13
|
+
|
|
8
14
|
## [2.18.0] - 2026-11-05
|
|
9
15
|
|
|
10
16
|
### Added
|
|
@@ -15,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
21
|
|
|
16
22
|
### Security
|
|
17
23
|
|
|
18
|
-
* `Savon::Model.operations` and `.all_operations` no longer rely on a hand-maintained list of reserved names. The guard now derives its denylist from the model's own ancestor chains — every method instances respond to plus every method the class object responds to — so a WSDL operation can never shadow an existing method. This closes the `class`/`public_send` bypass (unbounded recursion via the generated `self.class.public_send(...)` bodies, hijacked reflective dispatch) and the whole class of follow-on reports (`new`, `freeze`, `send`, …) by construction instead of one advisory at a time. Re-registering an already-defined operation is still allowed; anything else that collides raises `ArgumentError`. Call such operations explicitly through `model.client.call(:operation, ...)`.
|
|
24
|
+
* `Savon::Model.operations` and `.all_operations` no longer rely on a hand-maintained list of reserved names ([GHSA-hjvq-xxqv-7jxh](https://github.com/savonrb/savon/security/advisories/GHSA-hjvq-xxqv-7jxh)). The guard now derives its denylist from the model's own ancestor chains — every method instances respond to plus every method the class object responds to — so a WSDL operation can never shadow an existing method. This closes the `class`/`public_send` bypass (unbounded recursion via the generated `self.class.public_send(...)` bodies, hijacked reflective dispatch) and the whole class of follow-on reports (`new`, `freeze`, `send`, …) by construction instead of one advisory at a time. Re-registering an already-defined operation is still allowed; anything else that collides raises `ArgumentError`. Call such operations explicitly through `model.client.call(:operation, ...)`. Thanks to @manus-pi for securely disclosing this.
|
|
19
25
|
|
|
20
26
|
### Fixed
|
|
21
27
|
|
|
@@ -37,7 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
37
43
|
|
|
38
44
|
### Security
|
|
39
45
|
|
|
40
|
-
* `Savon::Model.operations` and `.all_operations` now reject names that normalize to reserved model methods with `ArgumentError`, before defining any methods. This prevents a WSDL operation named `client` from overwriting the client accessor and causing unbounded recursion. Call operations with reserved names explicitly through `model.client.call(:client, ...)`.
|
|
46
|
+
* `Savon::Model.operations` and `.all_operations` now reject names that normalize to reserved model methods with `ArgumentError`, before defining any methods ([GHSA-x99v-rwvj-g9p5](https://github.com/savonrb/savon/security/advisories/GHSA-x99v-rwvj-g9p5)). This prevents a WSDL operation named `client` from overwriting the client accessor and causing unbounded recursion. Call operations with reserved names explicitly through `model.client.call(:client, ...)`. Thanks to @arpitjain099 for securely disclosing this.
|
|
41
47
|
|
|
42
48
|
## [2.17.4] - 2026-07-03
|
|
43
49
|
|
|
@@ -1296,6 +1302,9 @@ Pay attention to the following list and read the updated Wiki: http://wiki.githu
|
|
|
1296
1302
|
|
|
1297
1303
|
* Complete rewrite and public release.
|
|
1298
1304
|
|
|
1305
|
+
[2.18.1]: https://github.com/savonrb/savon/compare/v2.18.0...v2.18.1
|
|
1306
|
+
[2.18.0]: https://github.com/savonrb/savon/compare/v2.17.5...v2.18.0
|
|
1307
|
+
[2.17.5]: https://github.com/savonrb/savon/compare/v2.17.4...v2.17.5
|
|
1299
1308
|
[2.17.4]: https://github.com/savonrb/savon/compare/v2.17.3...v2.17.4
|
|
1300
1309
|
[2.17.3]: https://github.com/savonrb/savon/compare/v2.17.2...v2.17.3
|
|
1301
1310
|
[2.17.2]: https://github.com/savonrb/savon/compare/v2.17.1...v2.17.2
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "savon/transport/logging"
|
|
4
4
|
require "savon/transport/response"
|
|
5
|
+
require "savon/transport/header_validation"
|
|
5
6
|
|
|
6
7
|
module Savon
|
|
7
8
|
module Transport
|
|
@@ -15,6 +16,7 @@ module Savon
|
|
|
15
16
|
# the caller's responsibility via client.faraday before any call is made.
|
|
16
17
|
class Faraday
|
|
17
18
|
include Logging
|
|
19
|
+
include HeaderValidation
|
|
18
20
|
|
|
19
21
|
# @param connection [Faraday::Connection] the memoized connection from client.faraday
|
|
20
22
|
# @param globals [Savon::GlobalOptions] the client-level options
|
|
@@ -79,6 +81,8 @@ module Savon
|
|
|
79
81
|
# soap_headers are lowest priority
|
|
80
82
|
soap_headers.each do |k, v| headers[k] ||= v end
|
|
81
83
|
|
|
84
|
+
validate_soap_action_header!(headers)
|
|
85
|
+
|
|
82
86
|
cookie_header = format_cookies(locals[:cookies])
|
|
83
87
|
headers["Cookie"] = cookie_header if cookie_header
|
|
84
88
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Savon
|
|
4
|
+
module Transport
|
|
5
|
+
# Validates outbound HTTP header values before a transport sends them.
|
|
6
|
+
#
|
|
7
|
+
# A value that carries control characters cannot be sent safely.
|
|
8
|
+
# A carriage return or line feed splits the header on the wire,
|
|
9
|
+
# which lets an attacker inject further request headers.
|
|
10
|
+
# A header value can be assembled from input the caller does not fully control,
|
|
11
|
+
# so it is checked here rather than trusted where it is built.
|
|
12
|
+
#
|
|
13
|
+
# The check runs at the transport layer on the fully merged header set,
|
|
14
|
+
# so it covers every source of a value alike. It is the policing half of
|
|
15
|
+
# a deliberate split: the request headers are built elsewhere, this module
|
|
16
|
+
# decides whether they may be sent.
|
|
17
|
+
#
|
|
18
|
+
# RFC 9110 §5.5 (field values)
|
|
19
|
+
module HeaderValidation
|
|
20
|
+
# Control characters that must never appear in an HTTP header value.
|
|
21
|
+
# A carriage return or line feed splits the header, a NUL truncates it.
|
|
22
|
+
ILLEGAL_HEADER_CHARACTERS = /[\r\n\0]/
|
|
23
|
+
private_constant :ILLEGAL_HEADER_CHARACTERS
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Validates every SOAPAction header and raises if one is unsafe.
|
|
28
|
+
#
|
|
29
|
+
# The SOAPAction value can come from untrusted WSDL input, which may encode
|
|
30
|
+
# a carriage return or line feed as the character references + + and + +.
|
|
31
|
+
# These survive XML attribute-value normalization and reach the header as real
|
|
32
|
+
# control characters.
|
|
33
|
+
#
|
|
34
|
+
# The merged headers are a plain Hash, so a caller can add a SOAPAction under
|
|
35
|
+
# a Symbol or a differently cased key next to the default String key.
|
|
36
|
+
# Some adapters send both, so every key that names SOAPAction is checked,
|
|
37
|
+
# matched case-insensitively.
|
|
38
|
+
#
|
|
39
|
+
# Pure validation. The value is never trimmed, stripped, or encoded.
|
|
40
|
+
# A header that cannot be sent safely is rejected, not repaired.
|
|
41
|
+
# Leading, interior, and trailing control characters all fail.
|
|
42
|
+
# The value is checked as raw bytes, so a non-String value or a string
|
|
43
|
+
# with an invalid encoding never raises here.
|
|
44
|
+
# The offending value is included in the message because it's not a secret.
|
|
45
|
+
#
|
|
46
|
+
# SOAP 1.1 §6.1.1 (SOAPAction HTTP header)
|
|
47
|
+
#
|
|
48
|
+
# @param headers [Hash{String, Symbol => Object}] the merged outbound HTTP headers
|
|
49
|
+
# @return [void]
|
|
50
|
+
# @raise [Savon::InvalidHeaderError] when a SOAPAction contains CR, LF, or NUL
|
|
51
|
+
def validate_soap_action_header!(headers)
|
|
52
|
+
headers.each do |key, value|
|
|
53
|
+
next unless value && key.to_s.casecmp?("SOAPAction")
|
|
54
|
+
next unless ILLEGAL_HEADER_CHARACTERS.match?(value.to_s.b)
|
|
55
|
+
|
|
56
|
+
raise InvalidHeaderError, "The SOAPAction header contains illegal control " \
|
|
57
|
+
"characters (CR, LF, or NUL): #{value.inspect}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "httpi"
|
|
4
4
|
require "savon/transport/logging"
|
|
5
5
|
require "savon/transport/response"
|
|
6
|
+
require "savon/transport/header_validation"
|
|
6
7
|
|
|
7
8
|
module Savon
|
|
8
9
|
module Transport
|
|
@@ -14,6 +15,7 @@ module Savon
|
|
|
14
15
|
# * request execution
|
|
15
16
|
class HTTPI
|
|
16
17
|
include Logging
|
|
18
|
+
include HeaderValidation
|
|
17
19
|
|
|
18
20
|
# @param globals [Savon::GlobalOptions] the client-level options
|
|
19
21
|
def initialize(globals)
|
|
@@ -63,6 +65,8 @@ module Savon
|
|
|
63
65
|
# soap_headers are lowest priority
|
|
64
66
|
soap_headers.each do |k, v| headers[k] ||= v end
|
|
65
67
|
|
|
68
|
+
validate_soap_action_header!(headers)
|
|
69
|
+
|
|
66
70
|
http_request = ::HTTPI::Request.new
|
|
67
71
|
http_request.url = url
|
|
68
72
|
http_request.body = body
|
data/lib/savon/version.rb
CHANGED
data/lib/savon.rb
CHANGED
|
@@ -7,6 +7,10 @@ module Savon
|
|
|
7
7
|
UnknownOperationError = Class.new(Error)
|
|
8
8
|
InvalidResponseError = Class.new(Error)
|
|
9
9
|
|
|
10
|
+
# Raised when a resolved HTTP header value cannot be sent safely,
|
|
11
|
+
# for example a SOAPAction carrying control characters from a crafted WSDL.
|
|
12
|
+
InvalidHeaderError = Class.new(Error)
|
|
13
|
+
|
|
10
14
|
def self.client(globals = {}, &block)
|
|
11
15
|
Client.new(globals, &block)
|
|
12
16
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: savon
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.18.
|
|
4
|
+
version: 2.18.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Harrington
|
|
@@ -291,6 +291,7 @@ files:
|
|
|
291
291
|
- lib/savon/soap_fault.rb
|
|
292
292
|
- lib/savon/string_utils.rb
|
|
293
293
|
- lib/savon/transport/faraday.rb
|
|
294
|
+
- lib/savon/transport/header_validation.rb
|
|
294
295
|
- lib/savon/transport/httpi.rb
|
|
295
296
|
- lib/savon/transport/logging.rb
|
|
296
297
|
- lib/savon/transport/response.rb
|