shopify_api 16.3.0 → 16.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ef28917f2c106b9f37d8bbe2d84a870da064c05dc9a1c962adabc3e1c554990
4
- data.tar.gz: 3f09f7a81bd0fb124e449c2025a228588a0d2e23f9ccff7db26ef321e33685f8
3
+ metadata.gz: 15b9053636bf36a285462a2843dae8e7603dfcac4429e5e98810bfd0538679b0
4
+ data.tar.gz: d5488b89b1fa4135536142cc729656515ca338c5fd61e30df15d9026d2dd52a9
5
5
  SHA512:
6
- metadata.gz: 617d78c0c104967dfda7f676807563d1c41e01c76ffa063d66728c77f2463fc13cc8e7b1316743c09997c793c98a4c679b1c2848098864131ddabf09653ba509
7
- data.tar.gz: 0c1ec1644cca56fcb2df2f5d55fefa5f692cbe7f8b2800e398a5cb1e7be462ff84e9d14065090e2441f0c725565c9916919b2fa1fd7c965e63e00fbbe8ef344a
6
+ metadata.gz: 1d3a1818a57ffe3be7dc30ca153e63c0e37e2cb2198db19d0d711db2f256857f1fff60307a3c8896b26252964679b9b6170c111debefc45be655eb40cbec3569
7
+ data.tar.gz: ee2bedb0b6fa998ac04fdcba3a4b5927a4c0c626ab2d686cc6aba6c3005944cdf1288a3622a186cc457a49e25715c0afeae9f8f7dd0f195a9d38d8d21011b47e
@@ -1,6 +1,6 @@
1
1
  name: Gardener - Notify Slack
2
2
  # Runs after `Gardener - Notify Event` completes and does the real work:
3
- # applies the devtools-gardener label and posts a summary to Slack.
3
+ # applies the devtools-gardener label and posts a repo-prefixed linked title to Slack.
4
4
  #
5
5
  # The workflow_run trigger runs this job in the default-branch context with
6
6
  # full GITHUB_TOKEN permissions and Actions secret access — this is what
@@ -59,57 +59,16 @@ jobs:
59
59
  SLACK_BOT_TOKEN: ${{ secrets.SLACK_GARDENER_BOT_TOKEN }}
60
60
  SLACK_CHANNEL_ID: ${{ vars.GARDENER_SLACK_CHANNEL_ID }}
61
61
  run: |
62
- KIND=$(jq -r 'if has("pull_request") then "PR" else "Issue" end' event.json)
63
- # Pull the body out, truncate, then convert GitHub Markdown to
64
- # Slack mrkdwn. Links and fenced code blocks are stashed before
65
- # the HTML-escape pass so their contents survive verbatim (a `&`
66
- # inside a URL must stay raw, and code content shouldn't be
67
- # mangled). Blockquote `> ` markers are also stashed so the
68
- # `>` → `>` escape doesn't break them. Everything else is
69
- # HTML-escaped so user-supplied `<`, `>`, `&` can't collide
70
- # with Slack link syntax or injected mentions like <!channel>.
71
- BODY=$(jq -r '(.issue // .pull_request).body // ""' event.json)
72
- if [ ${#BODY} -gt 1000 ]; then
73
- BODY="${BODY:0:1000}…"
74
- fi
75
- BODY=$(printf '%s' "$BODY" | perl -0777 -pe '
76
- my @u;
77
- s{\[([^\]]+)\]\(([^)]+)\)}{push @u, $2; "\x01$#u\x02$1\x03"}ge;
78
- my @c;
79
- s{^```[^\n]*\n(.*?)\n```$}{push @c, $1; "\x04$#c\x05"}gems;
80
- s/^> /\x06/gm;
81
- s/^#{1,6}\s+(.+)$/*$1*/gm;
82
- s/\*\*(.+?)\*\*/*$1*/g;
83
- s/^(\s*)- \[x\]\s+/$1✓ /gm;
84
- s/^(\s*)[-*]\s+/$1• /gm;
85
- s/&/&amp;/g;
86
- s/</&lt;/g;
87
- s/>/&gt;/g;
88
- s/\x06/> /g;
89
- s{\x01(\d+)\x02(.*?)\x03}{"<$u[$1]|$2>"}ge;
90
- s{\x04(\d+)\x05}{"```\n$c[$1]\n```"}ge;
91
- ')
92
62
  jq \
93
63
  --arg channel "$SLACK_CHANNEL_ID" \
94
- --arg kind "$KIND" \
95
- --arg body "$BODY" \
96
64
  '
97
65
  def escape: gsub("&";"&amp;") | gsub("<";"&lt;") | gsub(">";"&gt;");
98
66
 
99
67
  (.issue // .pull_request) as $i
100
- | ([$i.labels[]?.name | select(. != "devtools-gardener")]
101
- | map("`\(.)`") | join(" ")) as $labels
102
- | (if $kind == "PR"
103
- then " · \($i.changed_files) files, +\($i.additions)/-\($i.deletions)"
104
- + (if $i.draft then " · draft" else "" end)
105
- else "" end) as $meta
106
- | [ "*<\($i.html_url)|\($kind) #\($i.number)>* — \(($i.title | escape))",
107
- "_opened by \($i.user.login)\($meta)_" ]
108
- + (if $body != "" then [$body] else [] end)
109
- + (if $labels != "" then [$labels] else [] end)
110
- | join("\n") as $msg
111
- | { channel: $channel, text: "\($kind) #\($i.number): \($i.title)",
112
- blocks: [{ type: "section", text: { type: "mrkdwn", text: $msg } }] }
68
+ | { channel: $channel,
69
+ text: "\(.repository.name) · <\($i.html_url)|\($i.title | escape)>",
70
+ unfurl_links: false,
71
+ unfurl_media: false }
113
72
  ' event.json | curl -sf -X POST \
114
73
  -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
115
74
  -H 'Content-type: application/json; charset=utf-8' \
data/CHANGELOG.md CHANGED
@@ -3,6 +3,11 @@
3
3
  Note: For changes to the API, see https://shopify.dev/changelog?filter=api
4
4
  ## Unreleased
5
5
 
6
+ ## 16.3.1 (2026-10-22)
7
+ - [#1472](https://github.com/Shopify/shopify-api-ruby/pull/1472) `ShopifyAPI::Auth.embedded_app_url` now raises `ShopifyAPI::Errors::InvalidShopError` unless the decoded `host` is a trusted Shopify Admin host (see `ShopifyAPI::Utils::ShopValidator`). URLs for supported Admin hosts are unchanged.
8
+ - [#1472](https://github.com/Shopify/shopify-api-ruby/pull/1472) `ShopifyAPI::Auth::Oauth.begin_auth` now validates and normalizes `shop` with `ShopifyAPI::Utils::ShopValidator.sanitize!`, and raises `ShopifyAPI::Errors::InvalidShopError` for untrusted domains.
9
+ - [#1472](https://github.com/Shopify/shopify-api-ruby/pull/1472) `ShopifyAPI::Utils::ShopValidator` now rejects hosts with non-hostname characters and reads the store handle from the segment after `/store/` in Admin URLs (`admin.shopify.com/store/<handle>`), rejecting a missing or invalid handle.
10
+
6
11
  ## 16.3.0 (2026-08-04)
7
12
  - [#1443](https://github.com/Shopify/shopify-api-ruby/pull/1443) Add `ShopifyAPI::Utils::ShopValidator` with `sanitize_shop_domain` and `sanitize!`.
8
13
  - [#1443](https://github.com/Shopify/shopify-api-ruby/pull/1443) Derive the target shop for `ShopifyAPI::Auth::TokenExchange.exchange_token` from the session token's `dest` claim. The `shop` argument is now deprecated and will be removed in the next major version.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify_api (16.3.0)
4
+ shopify_api (16.3.1)
5
5
  activesupport
6
6
  addressable (~> 2.7)
7
7
  concurrent-ruby
@@ -33,6 +33,8 @@ module ShopifyAPI
33
33
  end
34
34
  raise Errors::UnsupportedOauthError, "Cannot perform OAuth for private apps." if Context.private?
35
35
 
36
+ shop = Utils::ShopValidator.sanitize!(shop)
37
+
36
38
  state = SecureRandom.alphanumeric(NONCE_LENGTH)
37
39
 
38
40
  cookie = SessionCookie.new(value: state, expires: Time.now + 60)
@@ -1,10 +1,19 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "addressable/uri"
5
+
4
6
  module ShopifyAPI
5
7
  module Auth
6
8
  extend T::Sig
7
9
 
10
+ # Hostname characters, plus an optional port. Anything else before the first "/" is rejected.
11
+ EMBEDDED_HOST_AUTHORITY_REGEX = T.let(/\A[a-zA-Z0-9._-]+(:\d{1,5})?\z/, Regexp)
12
+
13
+ # Printable ASCII only: no whitespace, control bytes or non-ASCII bytes.
14
+ EMBEDDED_HOST_ALLOWED_BYTES_REGEX = T.let(/\A[\x21-\x7e]+\z/n, Regexp)
15
+ private_constant :EMBEDDED_HOST_AUTHORITY_REGEX, :EMBEDDED_HOST_ALLOWED_BYTES_REGEX
16
+
8
17
  class << self
9
18
  extend T::Sig
10
19
 
@@ -19,8 +28,29 @@ module ShopifyAPI
19
28
  end
20
29
 
21
30
  decoded_host = Base64.decode64(host)
31
+ unless trusted_embedded_host?(decoded_host)
32
+ raise Errors::InvalidShopError, "host must decode to a trusted Shopify Admin host"
33
+ end
34
+
22
35
  "https://#{decoded_host}/apps/#{Context.api_key}"
23
36
  end
37
+
38
+ private
39
+
40
+ sig { params(decoded_host: String).returns(T::Boolean) }
41
+ def trusted_embedded_host?(decoded_host)
42
+ return false unless decoded_host.b.match?(EMBEDDED_HOST_ALLOWED_BYTES_REGEX)
43
+
44
+ authority = T.must(decoded_host.split("/", 2).first)
45
+ return false unless authority.match?(EMBEDDED_HOST_AUTHORITY_REGEX)
46
+
47
+ uri = Addressable::URI.parse("https://#{decoded_host}")
48
+ return false unless uri.userinfo.nil? && uri.authority == authority
49
+
50
+ !Utils::ShopValidator.sanitize_shop_domain(uri.to_s).nil?
51
+ rescue Addressable::URI::InvalidURIError
52
+ false
53
+ end
24
54
  end
25
55
  end
26
56
  end
@@ -17,6 +17,10 @@ module ShopifyAPI
17
17
  T::Array[String],
18
18
  )
19
19
 
20
+ HOSTNAME_REGEX = T.let(/\A[a-z0-9_-]+(\.[a-z0-9_-]+)*\.?\z/, Regexp)
21
+ STORE_HANDLE_REGEX = T.let(/\A[a-z0-9][a-z0-9-]*\z/, Regexp)
22
+ private_constant :HOSTNAME_REGEX, :STORE_HANDLE_REGEX
23
+
20
24
  class << self
21
25
  extend T::Sig
22
26
 
@@ -29,6 +33,7 @@ module ShopifyAPI
29
33
  def sanitize_shop_domain(shop_domain, myshopify_domain: nil)
30
34
  uri = uri_from_shop_domain(shop_domain, myshopify_domain)
31
35
  return nil if uri.nil? || uri.host.nil? || uri.host.empty?
36
+ return nil unless T.cast(uri.host, String).match?(HOSTNAME_REGEX)
32
37
 
33
38
  trusted_domains(myshopify_domain).each do |trusted_domain|
34
39
  host = T.cast(uri.host, String)
@@ -107,9 +112,13 @@ module ShopifyAPI
107
112
  T.cast(uri.host, String).split(".").first == "admin"
108
113
  end
109
114
 
110
- sig { params(uri: Addressable::URI).returns(String) }
115
+ sig { params(uri: Addressable::URI).returns(T.nilable(String)) }
111
116
  def myshopify_domain_from_unified_admin(uri)
112
- shop = uri.path.to_s.split("/").last
117
+ segments = uri.path.to_s.split("/")
118
+ store_index = segments.index("store")
119
+ shop = store_index && segments[store_index + 1]
120
+ return unless shop&.match?(STORE_HANDLE_REGEX)
121
+
113
122
  "#{shop}.myshopify.com"
114
123
  end
115
124
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module ShopifyAPI
5
- VERSION = "16.3.0"
5
+ VERSION = "16.3.1"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.3.0
4
+ version: 16.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -1703,7 +1703,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
1703
1703
  - !ruby/object:Gem::Version
1704
1704
  version: '0'
1705
1705
  requirements: []
1706
- rubygems_version: 4.0.16
1706
+ rubygems_version: 4.0.22
1707
1707
  specification_version: 4
1708
1708
  summary: The gem for accessing the Shopify API
1709
1709
  test_files: []