resque-mcp 0.3.0 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 890b5b8eb96c160226ce467a2a626861f25e774e5cd80402a4e8256a5aa361f1
4
- data.tar.gz: fb3e64270e1d0dd2fca69d4ecedbd9c06b72ff146dd6160b5fc305420bc49117
3
+ metadata.gz: 0415f5a9bbc7dddcc1c4def5de45835d49df86158b5261567af19ece94d6d36c
4
+ data.tar.gz: 6f4f8511f80a3107abd83e7e24053d0322dabb5feec212e52271bfe7bd844684
5
5
  SHA512:
6
- metadata.gz: d066b59609ab3e12a1c0e7458bbf9503751b94f548c858e51aeb5a1cd921aa0675f1643d6d64c2bf83cc524314961104f9d11b5c25ec874731e5726f29799edf
7
- data.tar.gz: 5e84b9b0249ee94416e9c505abba4637f01d8669dec570bab97a4bedb556a6008497525b4d0f19233c5b002f9228aede5c2ee8ae5e1467ae7a5c17d14d458c32
6
+ metadata.gz: 95a256a4a7e92257568a474254fbd052306f0e915cebad24bfc6ff237a4a678ead0ddfae17611b0f6ab0f4b2f9a195ace9cc4eda54cd0b2d5122afc0827ebdb3
7
+ data.tar.gz: 2494d7df431b2b14f5a27eabc148f6a19670246afbb7051ad0144c2637773c52acaeb5d022b86e0e47d87cf691d044f6e6e1574c8640dd0389f8356189a494cf
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.4.0]
6
+
7
+ - Security: bump the minimum `mcp` dependency to `>= 0.23, < 1` for the DNS-rebinding fix (CVE-2026-63118); `<= 0.22` did not validate `Host`/`Origin` headers on the Streamable HTTP transport. Protection is now on by default, with loopback always allowed.
8
+ - Config: `allowed_hosts` and `allowed_origins` for the DNS-rebinding allowlists. `allowed_hosts` defaults to inheriting the host's Rails `config.hosts` string entries, so apps that already configure Rails host authorization need nothing extra; an explicit list replaces it, and `[]` allows loopback only.
9
+ - Config: `mcp_transport_options` forwards arbitrary settings to the mcp gem's transport (e.g. `max_request_bytes`); the DNS-rebinding keys always override it, so it can tune but never weaken protection.
10
+
5
11
  ## [0.3.0]
6
12
 
7
13
  - `worker_stats` tool: list registered workers (state, subscribed queues, per-worker processed/failed counts, start time, current job with filtered args preview) with a `state` filter (`working`/`idle`/`all`), global counts including `heartbeat_expired`, and the standard pagination envelope. Workers whose heartbeat is older than `Resque.prune_interval` are flagged as likely dead.
data/README.md CHANGED
@@ -34,6 +34,22 @@ Resque::Mcp.configure do |c|
34
34
  # token could be created by e.g.: `bin/rails runner 'puts SecureRandom.base58(32)'`
35
35
  c.auth_token = Rails.application.credentials.dig(:resque_mcp, :token)
36
36
 
37
+ # DNS-rebinding protection (CVE-2026-63118) rejects any Host outside the
38
+ # allowlist with 403; loopback (127.0.0.1/::1/localhost) is always allowed.
39
+ # By default the allowlist is inherited from your Rails config.hosts, so if
40
+ # that already lists your domain(s) you need nothing here. Only plain
41
+ # hostname strings are honored (here and when inherited) — regexps, IPAddrs,
42
+ # and ".sub.domain" wildcards are dropped, so give concrete hostnames:
43
+ # c.allowed_hosts = ["resque.example.com"]
44
+
45
+ # Optional: extra permitted Origin values beyond same-origin.
46
+ # c.allowed_origins = ["https://resque.example.com"]
47
+
48
+ # Optional: escape hatch for mcp settings this gem doesn't expose directly,
49
+ # forwarded to the transport (the DNS-rebinding keys above always take
50
+ # precedence and can't be weakened here):
51
+ # c.mcp_transport_options = { max_request_bytes: 8 * 1024 * 1024 }
52
+
37
53
  # Optional: which job-args keys to mask as [FILTERED] in tool responses.
38
54
  # Defaults to your app's config.filter_parameters; an explicit list
39
55
  # replaces it (merge yourself if you want both):
@@ -41,7 +57,7 @@ Resque::Mcp.configure do |c|
41
57
  end
42
58
  ```
43
59
 
44
- The token is **required** — the endpoint answers `503` until one is configured, and `401` on any request without a matching `Authorization: Bearer` header. The engine talks to whatever `Resque.redis` your app already configured; it never opens its own Redis connection.
60
+ The token is **required** — the endpoint answers `503` until one is configured, and `401` on any request without a matching `Authorization: Bearer` header. The endpoint also validates the `Host`/`Origin` headers against DNS rebinding: the allowed hosts default to your Rails `config.hosts`, so a non-loopback request gets `403` unless its Host is in that list (or in an explicit `allowed_hosts`). The engine talks to whatever `Resque.redis` your app already configured; it never opens its own Redis connection.
45
61
 
46
62
  Job arguments shown by any tool are filtered through `ActiveSupport::ParameterFilter` **before** preview/truncation, using your Rails `filter_parameters` by default — the same keys you hide from your logs are hidden from the model. Filters match hash keys (at any depth, same semantics as Rails log filtering, including anchored dot-notation like `/\Acredit_card\.code\z/`); positional scalar args have no key and pass through. Set `c.filter_parameters = []` to disable.
47
63
 
@@ -7,9 +7,17 @@ module Resque
7
7
 
8
8
  def handle
9
9
  server = ServerFactory.build(environment: Rails.env.to_s)
10
- transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(
11
- server, stateless: true, enable_json_response: true
10
+ config = Resque::Mcp.config
11
+ # Passthrough first; our security-critical keys override, so nothing
12
+ # in mcp_transport_options can weaken the DNS-rebinding posture.
13
+ options = config.mcp_transport_options.merge(
14
+ stateless: true,
15
+ enable_json_response: true,
16
+ dns_rebinding_protection: true,
17
+ allowed_hosts: config.allowed_hosts,
18
+ allowed_origins: config.allowed_origins
12
19
  )
20
+ transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(server, **options)
13
21
 
14
22
  status, headers, body = transport.handle_request(request)
15
23
  headers.each { |key, value| response.set_header(key, value) }
@@ -4,6 +4,43 @@ module Resque
4
4
  module Mcp
5
5
  class Configuration
6
6
  attr_accessor :auth_token
7
+
8
+ # DNS-rebinding protection (mcp >= 0.23, CVE-2026-63118). The transport
9
+ # validates Host/Origin headers; loopback hosts are always allowed.
10
+ #
11
+ # nil (default) inherits the host's Rails config.hosts; an explicit list
12
+ # replaces it; [] means loopback only. Either way, only plain hostname
13
+ # strings are honored — regexps, IPAddrs, and leading-dot subdomain
14
+ # wildcards (".example.com") can't map to the SDK's exact-hostname
15
+ # matching (and a non-string would crash its downcase), so they're
16
+ # dropped from both paths. allowed_origins adds extra permitted Origin
17
+ # values beyond same-origin.
18
+ attr_writer :allowed_hosts, :allowed_origins
19
+
20
+ # Internal seam: a callable returning the host's config.hosts, read
21
+ # lazily (the engine wires it) so post-boot changes are never missed.
22
+ attr_accessor :default_allowed_hosts
23
+
24
+ def allowed_hosts
25
+ list = (defined?(@allowed_hosts) && @allowed_hosts) ? @allowed_hosts : default_allowed_hosts&.call
26
+ Array(list).select { |h| h.is_a?(String) && !h.start_with?(".") }
27
+ end
28
+
29
+ def allowed_origins
30
+ @allowed_origins || []
31
+ end
32
+
33
+ # Extra settings for the mcp gem, forwarded verbatim to its
34
+ # StreamableHTTPTransport (e.g. max_request_bytes:). Tuning only — the
35
+ # controller applies the security-critical keys (allowed_hosts,
36
+ # allowed_origins, stateless, dns_rebinding_protection) *after* these,
37
+ # so a passthrough value can never weaken them.
38
+ attr_writer :mcp_transport_options
39
+
40
+ def mcp_transport_options
41
+ @mcp_transport_options || {}
42
+ end
43
+
7
44
  # nil = inherit the host default (the engine wires Rails'
8
45
  # filter_parameters as a lazy source); an explicit list replaces it
9
46
  # ([] disables filtering).
@@ -14,6 +14,13 @@ module Resque
14
14
  initializer "resque_mcp.filter_parameters" do
15
15
  Resque::Mcp.config.default_filter_parameters = -> { Rails.application.config.filter_parameters }
16
16
  end
17
+
18
+ # allowed_hosts inherits the host's config.hosts (the same list Rails'
19
+ # own host authorization uses) unless explicitly configured, read
20
+ # lazily for the same reason as the filter list above.
21
+ initializer "resque_mcp.allowed_hosts" do
22
+ Resque::Mcp.config.default_allowed_hosts = -> { Rails.application.config.hosts }
23
+ end
17
24
  end
18
25
  end
19
26
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Resque
4
4
  module Mcp
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-mcp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josch Bockler
@@ -33,16 +33,22 @@ dependencies:
33
33
  name: mcp
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
- - - "~>"
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0.23'
39
+ - - "<"
37
40
  - !ruby/object:Gem::Version
38
- version: '0.22'
41
+ version: '1'
39
42
  type: :runtime
40
43
  prerelease: false
41
44
  version_requirements: !ruby/object:Gem::Requirement
42
45
  requirements:
43
- - - "~>"
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0.23'
49
+ - - "<"
44
50
  - !ruby/object:Gem::Version
45
- version: '0.22'
51
+ version: '1'
46
52
  - !ruby/object:Gem::Dependency
47
53
  name: activesupport
48
54
  requirement: !ruby/object:Gem::Requirement