graphql 2.6.8 → 2.6.9

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: 12ae3a5cc9e399639228a8ccfc1654c7bcaf73a6c81339ad72f7ce90b159eb9d
4
- data.tar.gz: a998bd1576174777857e365cbe67ffc7b68273bbaa17ad0cd1e609193254417f
3
+ metadata.gz: 6a359d53729477046ffd6b51ef9345099defcdc95521579711fffe5ab79c0b5e
4
+ data.tar.gz: 4c88aecd90fb26e02198f21f4bdf7a73f577ec37ac9ea9eb6c840c8a2957fc7a
5
5
  SHA512:
6
- metadata.gz: 2c95b6f2c0aec486d42170e970f59272ece87e9433c5ba8e3fff4838da802993f1de9fcaf3b2c2807fb3365674a7a915265c248470987bf0a9f47d13b90699d1
7
- data.tar.gz: 03eb4c0fb7cddfd2cebce9a50ce3a9264f6473d4ca037e8a60ddb5d14b3f880a54c6b249958b2b58100dca9ea9b3ad9de04ad25cd333f0d00e620fd724ff7ff6
6
+ metadata.gz: d40bb56d8b5e77ca6f126f1528e2a074bbf679996dfb8ea5a12d646d028d547848dc36dea3a5fb73273fb7252135a5c7e904ec7d0a38790fe05164c5e81701b5
7
+ data.tar.gz: e486b9492693cdc69375f8d3bc662e075a71ff45642f62cb2fd741959b2a2d1f9105fa14718b992ef2d3b3a809fd0f0d94ec66abf509137b3a7af05877527c23
@@ -2,6 +2,9 @@
2
2
 
3
3
  require 'graphql/version'
4
4
  require 'digest/sha2'
5
+ require 'openssl'
6
+ require 'securerandom'
7
+ require 'tempfile'
5
8
 
6
9
  module GraphQL
7
10
  module Language
@@ -9,7 +12,11 @@ module GraphQL
9
12
  #
10
13
  # With Rails, parser caching may enabled by setting `config.graphql.parser_cache = true` in your Rails application.
11
14
  #
12
- # The cache may be manually built by assigning `GraphQL::Language::Parser.cache = GraphQL::Language::Cache.new("some_dir")`.
15
+ # The cache may be manually built by assigning `GraphQL::Language::Parser.cache = GraphQL::Language::Cache.new(Pathname.new("some_dir"), secret: ENV.fetch("GRAPHQL_CACHE_SECRET"))`.
16
+ # The `secret` should be a stable value stored outside of the cache directory.
17
+ # When it isn't provided, a process-local secret is generated and cache entries
18
+ # are rebuilt after the process restarts. Pass `secret: nil` to disable cache
19
+ # signing. This should only be used when the cache directory is trusted.
13
20
  # This will create a directory (`tmp/cache/graphql` by default) that stores a cache of parsed files.
14
21
  #
15
22
  # Much like [bootsnap](https://github.com/Shopify/bootsnap), the parser cache needs to be cleaned up manually.
@@ -18,32 +25,95 @@ module GraphQL
18
25
  #
19
26
  # @see GraphQL::Railtie for simple Rails integration
20
27
  class Cache
21
- def initialize(path)
28
+ # @param path [Pathname] The directory where cache entries are stored.
29
+ # @param secret [String, nil] A stable secret for verifying cache entries. When omitted,
30
+ # a process-local secret is generated. Pass `nil` to disable cache signing.
31
+ def initialize(path, secret: SecureRandom.random_bytes(32))
22
32
  @path = path
33
+ @secret = secret
23
34
  end
24
35
 
25
36
  DIGEST = Digest::SHA256.new << GraphQL::VERSION
37
+ HMAC_SIZE = OpenSSL::Digest::SHA256.new.digest_length
38
+ InvalidCache = Class.new(StandardError)
39
+ private_constant :InvalidCache
26
40
 
27
41
  def fetch(filename)
28
- hash = DIGEST.dup << filename
42
+ cache_key = cache_key_for(filename)
43
+ return yield unless cache_key
44
+
45
+ cache_path = @path.join(cache_key)
46
+
47
+ begin
48
+ return load_cache(cache_path, cache_key) if cache_path.file?
49
+ rescue InvalidCache, SystemCallError
50
+ # Rebuild caches created by older versions or with an invalid signature.
51
+ end
52
+
53
+ payload = yield
29
54
  begin
30
- hash << File.mtime(filename).to_i.to_s
55
+ write_cache(cache_path, cache_key, payload)
31
56
  rescue SystemCallError
32
- return yield
57
+ # Parser caching is best-effort; return the parsed payload if the cache cannot be written.
33
58
  end
34
- cache_path = @path.join(hash.to_s)
59
+ payload
60
+ end
61
+
62
+ private
63
+
64
+ def cache_key_for(filename)
65
+ content_digest = Digest::SHA256.file(filename).hexdigest
66
+ (DIGEST.dup << filename << content_digest).to_s
67
+ rescue SystemCallError
68
+ nil
69
+ end
70
+
71
+ def load_cache(cache_path, cache_key)
72
+ cache_data = cache_path.binread
73
+ return Marshal.load(cache_data) unless @secret
74
+
75
+ signature = cache_data.byteslice(0, HMAC_SIZE)
76
+ payload = cache_data.byteslice(HMAC_SIZE..-1)
77
+ raise InvalidCache unless signature && payload
35
78
 
36
- if cache_path.exist?
37
- Marshal.load(cache_path.read)
79
+ expected_signature = signature_for(cache_key, payload)
80
+ unless secure_compare(signature, expected_signature)
81
+ raise InvalidCache
82
+ end
83
+ Marshal.load(payload)
84
+ end
85
+
86
+ def write_cache(cache_path, cache_key, payload)
87
+ @path.mkpath
88
+ serialized_payload = Marshal.dump(payload)
89
+ cache_data = if @secret
90
+ signature_for(cache_key, serialized_payload) + serialized_payload
38
91
  else
39
- payload = yield
40
- tmp_path = "#{cache_path}.#{rand}"
92
+ serialized_payload
93
+ end
94
+
95
+ Tempfile.create(['graphql-cache-', '.tmp'], @path.to_s) do |tempfile|
96
+ tempfile.binmode
97
+ tempfile.write(cache_data)
98
+ tempfile.flush
99
+ tempfile.fsync
100
+ tempfile.close
101
+ File.rename(tempfile.path, cache_path.to_s)
102
+ end
103
+ end
104
+
105
+ def signature_for(cache_key, payload)
106
+ OpenSSL::HMAC.digest('SHA256', @secret, cache_key + payload)
107
+ end
108
+
109
+ def secure_compare(left, right)
110
+ return false unless left.bytesize == right.bytesize
41
111
 
42
- @path.mkpath
43
- File.binwrite(tmp_path, Marshal.dump(payload))
44
- File.rename(tmp_path, cache_path.to_s)
45
- payload
112
+ result = 0
113
+ left.bytes.each_with_index do |byte, index|
114
+ result |= byte ^ right.getbyte(index)
46
115
  end
116
+ result.zero?
47
117
  end
48
118
  end
49
119
  end
@@ -15,7 +15,8 @@ module GraphQL
15
15
  initializer("graphql.cache") do |app|
16
16
  if config.graphql.parser_cache
17
17
  Language::Parser.cache ||= Language::Cache.new(
18
- app.root.join("tmp/cache/graphql")
18
+ app.root.join("tmp/cache/graphql"),
19
+ secret: app.secret_key_base
19
20
  )
20
21
  end
21
22
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.6.8"
3
+ VERSION = "2.6.9"
4
4
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.8
4
+ version: 2.6.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-08-13 00:00:00.000000000 Z
10
+ date: 2026-08-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64