shrine 3.10.0 → 3.10.2

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: 5601661d53737bf3290ec5897dc9d33cfe5edac60a7468e67de7191649671a14
4
- data.tar.gz: bf2a26e2162a0c1805f2e8d174128e8ee5008060fdbbb9b8e358d41bfdf680a2
3
+ metadata.gz: 2027c3833ffb021cf9fe9be3761c3238ca807eb1591ed910b585b226cad80497
4
+ data.tar.gz: fd9ce3716ac5c110a306f4823acf90de7cf673132ef90b181e8d7040cc8d74d0
5
5
  SHA512:
6
- metadata.gz: 7eb43ca8a78d4dedec0d24422ca4bc9f62eb6e454aa61ac7871760c4c398973a9787591295c39799f6b3250d4b3af9e80f7082c659fb6ed67435d775846ca5e2
7
- data.tar.gz: 636a7343486f6baaa3fa841abcef8439eb0df5d7183d6ac39a756a508de544dd356bca2562a9e01ac76b871a29455d2a40601f8086e8050dcc793bd57fc568e0
6
+ metadata.gz: 9181db3a505b16fba406a6d98cc2f4a37222fcbacdf47427d5ec80d087ef3746b0db2152d21685b89f8cfc77db766b0ed13f1b976241354dc48b9e82aaa1fd35
7
+ data.tar.gz: 4f891d6fcfd5cdd29a39e04479d42d20bc7a6511557a2f82f85dc6889536bf38be9b5f281389fe6751ce7e26271bd1c85897c6e899b6ba3e08d3070ffc1e717e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 3.10.2 (2026-10-11)
2
+
3
+ * `derivation_endpoint` – Prevent bypassing CDN caching by modifying the encoding or structure of the signed URL query string (@Antsiscool, @janko)
4
+
5
+ ## 3.10.1 (2026-10-10)
6
+
7
+ * `model` – Make `Attacher#write` a no-op when the attacher isn't backed by a model, fixing `remove_invalid` raising `NoMethodError` on attachers without a record (@janko)
8
+
1
9
  ## 3.10.0 (2026-09-20)
2
10
 
3
11
  * Add `fallback_storage` plugin for reading files from a secondary storage when they're missing in the primary storage (@yegorov, @janko)
data/doc/advantages.md CHANGED
@@ -389,19 +389,19 @@ and Active Storage.
389
389
 
390
390
  Uppy supports direct uploads to [AWS S3][Uppy AwsS3] or to a [custom
391
391
  endpoint][Uppy XHRUpload]. It also supports **resumable** uploads, either
392
- [directly to S3][Uppy AwsS3Multipart] or via the [tus protocol][tus]. For the
392
+ [directly to S3][Uppy AwsS3 multipart] or via the [tus protocol][tus]. For the
393
393
  UI you can choose from various components, ranging from a simple [status
394
394
  bar][Uppy StatusBar] to a full-featured [dashboard][Uppy Dashboard].
395
395
 
396
396
  Shrine provides server side components for each type of upload. They are built
397
397
  on top of Rack, so that they can be used with any Ruby web framework.
398
398
 
399
- | Uppy | Shrine |
400
- | :--- | :----- |
401
- | [XHRUpload][Uppy XHRUpload] | [`upload_endpoint`][upload_endpoint] |
402
- | [AwsS3][Uppy AwsS3] | [`presign_endpoint`][presign_endpoint] |
403
- | [AwsS3Multipart][Uppy AwsS3Multipart] | [`uppy-s3_multipart`][uppy-s3_multipart] |
404
- | [Tus][Uppy Tus] | [`tus-ruby-server`][tus-ruby-server] |
399
+ | Uppy | Shrine |
400
+ | :--- | :----- |
401
+ | [XHRUpload][Uppy XHRUpload] | [`upload_endpoint`][upload_endpoint] |
402
+ | [AwsS3][Uppy AwsS3] | [`presign_endpoint`][presign_endpoint] |
403
+ | [AwsS3 (multipart)][Uppy AwsS3 multipart] | [`uppy-s3_multipart`][uppy-s3_multipart] |
404
+ | [Tus][Uppy Tus] | [`tus-ruby-server`][tus-ruby-server] |
405
405
 
406
406
  [Rack]: https://rack.github.io
407
407
  [Sinatra]: http://sinatrarb.com
@@ -440,10 +440,10 @@ on top of Rack, so that they can be used with any Ruby web framework.
440
440
  [Uppy XHRUpload]: https://uppy.io/docs/xhr-upload/
441
441
  [Uppy AwsS3]: https://uppy.io/docs/aws-s3/
442
442
  [Uppy Tus]: https://uppy.io/docs/tus/
443
- [Uppy AwsS3Multipart]: https://uppy.io/docs/aws-s3-multipart/
443
+ [Uppy AwsS3 multipart]: https://uppy.io/docs/aws-s3/#shouldusemultipartfile
444
444
  [tus]: https://tus.io
445
- [Uppy StatusBar]: https://uppy.io/examples/statusbar/
446
- [Uppy Dashboard]: https://uppy.io/examples/dashboard/
445
+ [Uppy StatusBar]: https://uppy.io/docs/status-bar/
446
+ [Uppy Dashboard]: https://uppy.io/docs/dashboard/
447
447
  [tus-ruby-server]: https://github.com/janko/tus-ruby-server
448
448
  [uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart
449
449
  [backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
data/doc/direct_s3.md CHANGED
@@ -174,11 +174,21 @@ and request headers.
174
174
  }
175
175
  ```
176
176
 
177
- Uppy's [AWS S3][uppy aws-s3] plugin would then make a request to this endpoint
178
- and use these parameters to upload the file directly to S3. Once the file has
179
- been uploaded, you can generate a JSON representation of the uploaded file on
180
- the client side, and write it to the hidden attachment field (or send it
181
- directly in an AJAX request).
177
+ Uppy's [AWS S3][uppy aws-s3] plugin in Companion mode will then fetch these
178
+ parameters from `<companionEndpoint>/s3/params` and use them to upload the file
179
+ directly to S3:
180
+
181
+ ```js
182
+ uppy.use(AwsS3, {
183
+ companionEndpoint: '', // relative to the current host
184
+ shouldUseMultipart: false, // Shrine's presign endpoint only signs single-request uploads
185
+ })
186
+ ```
187
+
188
+ Once the file has been uploaded, you can generate a JSON representation of the
189
+ uploaded file on the client side (the S3 object key is available as
190
+ `response.body.key` in the `upload-success` event), and write it to the hidden
191
+ attachment field (or send it directly in an AJAX request).
182
192
 
183
193
  ```rb
184
194
  {
@@ -203,8 +213,8 @@ as the [Roda][roda demo] / [Rails][rails demo] demo app for a complete example
203
213
  of multiple direct S3 uploads.
204
214
 
205
215
  Also, if you're dealing with larger files, you may want to make the uploads
206
- resumable by using the [AWS S3 Multipart][uppy aws-s3-multipart] Uppy plugin
207
- instead, with the [uppy-s3_multipart] gem on the backend. Your back-end
216
+ resumable by using Uppy's [AWS S3][uppy aws-s3-multipart] plugin in multipart
217
+ mode instead, with the [uppy-s3_multipart] gem on the backend. Your back-end
208
218
  implementation is similar, just using `Shrine.uppy_s3_multipart` in place of
209
219
  `Shrine.presign_endpoint`. Instructions can be found in the [gem
210
220
  docs][uppy-s3_multipart shrine].
@@ -396,8 +406,8 @@ setup] guide.
396
406
  [rails demo]: https://github.com/erikdahlstrand/shrine-rails-example
397
407
  [Uppy]: https://uppy.io
398
408
  [uppy aws-s3]: https://uppy.io/docs/aws-s3/
399
- [uppy aws-s3 cors]: https://uppy.io/docs/aws-s3/#S3-Bucket-configuration
400
- [uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3/
409
+ [uppy aws-s3 cors]: https://uppy.io/docs/aws-s3/#bucket-and-cors-setup
410
+ [uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3/#shouldusemultipartfile
401
411
  [uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart
402
412
  [uppy-s3_multipart shrine]: https://github.com/janko/uppy-s3_multipart#shrine
403
413
  [Amazon S3 Data Consistency Model]: http://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyMode
@@ -50,4 +50,4 @@ title: Extensions
50
50
  | [faster_s3_url](https://github.com/jrochkind/faster_s3_url) | Optimized generation of public and presigned AWS S3 GET URLs |
51
51
  | [imgproxy](https://github.com/imgproxy/imgproxy.rb) | Integration for [imgproxy](https://github.com/imgproxy/imgproxy) |
52
52
  | [rails_admin](https://github.com/sferik/rails_admin) | Integration for [RailsAdmin](https://github.com/sferik/rails_admin) |
53
- | [uppy-s3_multipart](https://github.com/janko/uppy-s3_multipart) | Integration for [Uppy AWS S3 Multipart](https://uppy.io/docs/aws-s3-multipart/) |
53
+ | [uppy-s3_multipart](https://github.com/janko/uppy-s3_multipart) | Integration for [Uppy AWS S3](https://uppy.io/docs/aws-s3/) multipart uploads |
@@ -896,13 +896,13 @@ on slow and flaky internet connections.
896
896
 
897
897
  #### Uppy S3 Multipart
898
898
 
899
- You can achieve resumable uploads directly to S3 with the [AWS S3
900
- Multipart][uppy aws-s3-multipart] Uppy plugin, accompanied with
899
+ You can achieve resumable uploads directly to S3 with Uppy's [AWS S3][uppy
900
+ aws-s3-multipart] plugin in multipart mode, accompanied with the
901
901
  `uppy_s3_multipart` Shrine plugin provided by the [uppy-s3_multipart] gem.
902
902
 
903
903
  ```rb
904
904
  # Gemfile
905
- gem "uppy-s3_multipart", "~> 0.3"
905
+ gem "uppy-s3_multipart", "~> 1.3"
906
906
  ```
907
907
  ```rb
908
908
  Shrine.plugin :uppy_s3_multipart
@@ -1108,7 +1108,7 @@ Shrine.logger.level = Logger::WARN
1108
1108
  [Uppy]: https://uppy.io
1109
1109
  [shrine-tus]: https://github.com/shrinerb/shrine-tus
1110
1110
  [tus]: https://tus.io
1111
- [uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3-multipart/
1111
+ [uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3/#shouldusemultipartfile
1112
1112
  [uppy aws-s3]: https://uppy.io/docs/aws-s3/
1113
1113
  [uppy tus]: https://uppy.io/docs/tus/
1114
1114
  [uppy xhr-upload]: https://uppy.io/docs/xhr-upload/
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: Shrine 3.10.1
3
+ ---
4
+
5
+ ## Bug fixes
6
+
7
+ * Since Shrine 3.9.0, the `remove_invalid` plugin would raise `NoMethodError` when validation failed on an attacher without a record and the `model` plugin was loaded (directly or through the `activerecord`/`sequel` plugins).
8
+
9
+ ```rb
10
+ attacher = Shrine::Attacher.new
11
+ attacher.assign(invalid_file)
12
+ # NoMethodError: undefined method '=' for nil
13
+ ```
14
+
15
+ `Attacher#write` from the `model` plugin is now a no-op when the attacher isn't backed by a model, so the invalid file is removed and validation errors are kept as before.
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: Shrine 3.10.2
3
+ ---
4
+
5
+ ## Security
6
+
7
+ * The `derivation_endpoint` plugin now verifies the URL signature against the
8
+ URL exactly as it was received. Previously, query parameters were decoded and
9
+ re-encoded before verification, so the same signed URL could be requested
10
+ through an unlimited number of variants that all passed verification. Since
11
+ each variant is a separate cache key on a CDN, this could be used to bypass
12
+ CDN caching and repeatedly trigger derivations on the server:
13
+
14
+ ```
15
+ /derivations/thumbnail/...?type=image%2Fwebp&signature=abc123 # original
16
+ /derivations/thumbnail/...?%74ype=image%2Fwebp&signature=abc123 # encoded parameter name
17
+ /derivations/thumbnail/...?&&type=image%2Fwebp&signature=abc123 # empty parameters
18
+ /derivations/thumbnail/...?type=image%2Fwebp&signature=abc123?foo # appended query
19
+ ```
20
+
21
+ These variants now return a `403 Forbidden` response. The `signature` query
22
+ parameter is also required to be the last parameter, which is where Shrine
23
+ always puts it.
24
+
25
+ ## Backwards compatibility
26
+
27
+ * URLs generated by the `derivation_endpoint` plugin are unchanged, so
28
+ existing URLs remain valid. However, if a proxy between the client and your
29
+ application rewrites the query string (e.g. re-encodes characters, reorders
30
+ parameters, or appends tracking parameters), signature verification will now
31
+ fail for those requests.
@@ -717,29 +717,27 @@ class Shrine
717
717
 
718
718
  # Returns a URL with the `signature` query parameter
719
719
  def sign_url(url)
720
- path, query = url.split("?")
720
+ path, query = url.split("?", 2)
721
721
 
722
- params = Rack::Utils.parse_query(query.to_s)
723
- params.merge!("signature" => generate_signature(url))
724
-
725
- query = Rack::Utils.build_query(params)
722
+ signature = generate_signature("#{path}?#{query}")
723
+ query = [query, "signature=#{signature}"].reject { |part| part.to_s.empty? }.join("&")
726
724
 
727
725
  "#{path}?#{query}"
728
726
  end
729
727
 
730
- # Calculcates the signature from the URL and checks whether it matches the
731
- # value in the `signature` query parameter. Raises `InvalidSignature` if
732
- # the `signature` parameter is missing or its value doesn't match the
733
- # calculated signature.
728
+ # Calculates the signature from the URL and checks whether it matches the
729
+ # value in the trailing `signature` query parameter. Raises
730
+ # `InvalidSignature` if the `signature` parameter is missing or its value
731
+ # doesn't match the calculated signature. The URL is verified as is,
732
+ # without any normalization, so that each signed URL has only one valid
733
+ # form (otherwise variants of it could be used to bypass CDN caching).
734
734
  def verify_url(url)
735
- path, query = url.split("?")
736
-
737
- params = Rack::Utils.parse_query(query.to_s)
738
- signature = params.delete("signature")
735
+ path, query = url.split("?", 2)
739
736
 
740
- query = Rack::Utils.build_query(params)
737
+ params = query.to_s.split("&", -1)
738
+ signature = params.pop.delete_prefix("signature=") if params.last&.start_with?("signature=")
741
739
 
742
- verify_signature("#{path}?#{query}", signature)
740
+ verify_signature("#{path}?#{params.join("&")}", signature)
743
741
  end
744
742
 
745
743
  def verify_signature(string, signature)
@@ -122,12 +122,15 @@ class Shrine
122
122
  # Writes uploaded file data into the model.
123
123
  def set(*)
124
124
  result = super
125
- write if model?
125
+ write
126
126
  result
127
127
  end
128
128
 
129
- # Writes the attachment data into the model attribute.
129
+ # Writes the attachment data into the model attribute. Does nothing
130
+ # when the attacher isn't backed by a model instance.
130
131
  def write
132
+ return unless model?
133
+
131
134
  column_values.each do |name, value|
132
135
  write_attribute(name, value)
133
136
  end
@@ -8,7 +8,7 @@ class Shrine
8
8
  module VERSION
9
9
  MAJOR = 3
10
10
  MINOR = 10
11
- TINY = 0
11
+ TINY = 2
12
12
  PRE = nil
13
13
 
14
14
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shrine
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.10.0
4
+ version: 3.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
@@ -490,6 +490,8 @@ files:
490
490
  - doc/release_notes/3.0.1.md
491
491
  - doc/release_notes/3.1.0.md
492
492
  - doc/release_notes/3.10.0.md
493
+ - doc/release_notes/3.10.1.md
494
+ - doc/release_notes/3.10.2.md
493
495
  - doc/release_notes/3.2.0.md
494
496
  - doc/release_notes/3.2.1.md
495
497
  - doc/release_notes/3.2.2.md