end_point_blank 0.13.0 → 0.13.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: a18f4976c060497e06a7fd5af0ee63d14cc080f7541b951d474baefe75c605ae
4
- data.tar.gz: 9d04564e23b5d62e6a6b848c2c7f6f0e8c4ac08639c41072392dfa95ee4b9664
3
+ metadata.gz: 14c18a9f81130c9ec607b05a25aa5a4a67d9726c6692eb313a395734ce79bcf3
4
+ data.tar.gz: 4081ad91a0c10503521993a7d2d7887cd0810faab8b8f22229a57f9ed7685749
5
5
  SHA512:
6
- metadata.gz: 679591ad5e6f06194f5b93f42e30f0c9455deaeaf6b29293a4429beea5cc3e2444d06c7c28389e3432ff8504fbeaa6496ab5e0a08d08f5420888db6d7e0a709e
7
- data.tar.gz: d52d7a2bf74d7cff868e96bc338cdd8c77e0b4608c61cabd6014efdd5cf90c50b5ba765e990bce9cf53bc7089969a2deacb26aae3edbce285d8cfd0dd5aaffcc
6
+ metadata.gz: 181770dadb24b746868274196d0600ff58d92ad7e430f2165b8ff7f59e376a0790ed027b9a8fb000b607b20698190755f5de5525ca78d540c198addab72c5fdd
7
+ data.tar.gz: 88593bf5bb77615e05d9277de7a4b167d5885a155cb9b30cfac83dbe0335003e84801abe7b826fe55ab81793b8990e0d39578bb05b2033bef2512a4cc5c11d6f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.1
4
+
5
+ ### Security
6
+
7
+ - **Request and response records no longer carry credentials or cookies
8
+ (sc-1470).** Both records carried every `HTTP_*` header of the request (the
9
+ response record sends the request's headers, not the response's), so unless
10
+ you had written a masking rule for it, a caller's `Authorization` header
11
+ (Basic `client_id:secret` or a bearer token) landed in your request and
12
+ response logs in EndPointBlank. `RequestWriter` and `ResponseWriter` now
13
+ leave out `Authorization`, `Proxy-Authorization`, `Cookie` and `Set-Cookie`,
14
+ in any letter case, before masking runs, so they are not in the payload the
15
+ rules and hook receive, and they are not sent at all. The list is
16
+ `EndPointBlank::Rack::Headers::SENSITIVE_HEADERS`; the SDK's own reads of
17
+ the request (`Headers.extract`, used to find the endpoint version) are
18
+ unchanged. A masking rule that targeted one of these headers now has nothing
19
+ to match and can be removed. Records sent by earlier versions may hold these
20
+ values; rotate any client secret a caller sent while it was in use.
21
+
22
+ ### Upgrading
23
+
24
+ - A `mask_hook` that reads `Authorization`, `Proxy-Authorization`, `Cookie` or
25
+ `Set-Cookie` from `headers` now finds it absent; guard the lookup.
26
+
27
+ ### Added
28
+
29
+ - **`claim_invite` takes an optional `return_to` (sc-1515).**
30
+ `clients.claim_invite(client_id, email:, return_to:)` and a managed
31
+ client's `claim_invite(email:, return_to:)` send `return_to`, where
32
+ EndPointBlank sends the customer's browser once they have claimed the
33
+ managed client. It is sent only when given, and must equal, byte for byte,
34
+ a claim return URL your organization registered in EndPointBlank; anything
35
+ else is refused with `return_to_not_registered` (422), now in
36
+ `ErrorCodes` as `RETURN_TO_NOT_REGISTERED`.
37
+
3
38
  ## 0.13.0
4
39
 
5
40
  ### Added
data/README.md CHANGED
@@ -485,6 +485,11 @@ regex is applied only within the path-selected node(s). When a `regex` is presen
485
485
  match; `$$` for a literal `$`). Stacktraces and log messages/data are never masked (there is no
486
486
  `log` entry in the masking field map).
487
487
 
488
+ **Credential and cookie headers are never sent.** Before any rule runs, the request and response
489
+ records leave out `Authorization`, `Proxy-Authorization`, `Cookie` and `Set-Cookie`, whatever
490
+ their letter case. They are left out of the record, not masked: they are not in the payload the
491
+ rules and hook receive. The list is `EndPointBlank::Rack::Headers::SENSITIVE_HEADERS`.
492
+
488
493
  ## Framework integration
489
494
 
490
495
  ### Rails
@@ -670,6 +675,11 @@ mgmt.package_assignments.assign(customer["id"], api_package_id: package["id"], e
670
675
 
671
676
  # ... and hand it over: the customer gets an email, and claiming rotates every credential you issued.
672
677
  initech.claim_invite(email: "it@initech.example")
678
+
679
+ # Optionally send the customer's browser somewhere once they have claimed it. `return_to` must
680
+ # equal, byte for byte, a claim return URL your organization registered in EndPointBlank;
681
+ # anything else is refused with `return_to_not_registered` (422).
682
+ initech.claim_invite(email: "it@initech.example", return_to: "https://app.example/welcome")
673
683
  ```
674
684
 
675
685
  Once claimed, the managed client's calls answer `not_found`. Remove an unclaimed one with
@@ -215,9 +215,11 @@ module EndPointBlank
215
215
  end
216
216
 
217
217
  # POST /clients/:client_id/claim_invites: emails your customer an
218
- # invite to claim the client. @return [Hash]
219
- def claim_invite(email:, idempotency_key: nil)
220
- @clients.claim_invite(client_id, email: email, idempotency_key: idempotency_key)
218
+ # invite to claim the client, and with +return_to+ (a claim return URL
219
+ # your organization registered) where to send them once claimed; see
220
+ # {Resources::Clients#claim_invite}. @return [Hash]
221
+ def claim_invite(email:, return_to: nil, idempotency_key: nil)
222
+ @clients.claim_invite(client_id, email: email, return_to: return_to, idempotency_key: idempotency_key)
221
223
  end
222
224
 
223
225
  def inspect
@@ -66,6 +66,9 @@ module EndPointBlank
66
66
  ENVIRONMENT_NOT_IN_APPLICATION = "environment_not_in_application"
67
67
  ALREADY_GRANTED = "already_granted"
68
68
  GRANT_REVOKED_CONCURRENTLY = "grant_revoked_concurrently"
69
+ # A claim invite's +return_to+ is not one of the claim return URLs your
70
+ # organization registered.
71
+ RETURN_TO_NOT_REGISTERED = "return_to_not_registered"
69
72
 
70
73
  # The warning code API package endpoint writes answer in +warnings+ (not
71
74
  # an error): a client assignment of the package now derives no grant.
@@ -97,7 +100,7 @@ module EndPointBlank
97
100
  ALREADY_A_MEMBER => 422, MANAGED_CLIENT_HAS_CREDENTIALS => 422, API_PACKAGE_NOT_FOUND => 422,
98
101
  ENVIRONMENT_NOT_FOUND => 422, ALREADY_ASSIGNED => 422, NOTHING_PUBLISHED_IN_ENVIRONMENT => 422,
99
102
  APPLICATION_NOT_FOUND => 422, ENDPOINT_NOT_FOUND => 422, ENVIRONMENT_NOT_IN_APPLICATION => 422,
100
- ALREADY_GRANTED => 422, GRANT_REVOKED_CONCURRENTLY => 409
103
+ ALREADY_GRANTED => 422, GRANT_REVOKED_CONCURRENTLY => 409, RETURN_TO_NOT_REGISTERED => 422
101
104
  }.freeze
102
105
 
103
106
  # Every code the API can send, as strings.
@@ -70,9 +70,17 @@ module EndPointBlank
70
70
 
71
71
  # POST /clients/:client_id/claim_invites: emails your customer an
72
72
  # invite to claim a managed client.
73
+ #
74
+ # +return_to+ (optional, sent only when given) is where EndPointBlank
75
+ # sends the customer's browser once they have claimed it. It must
76
+ # equal, byte for byte, a claim return URL your organization
77
+ # registered in EndPointBlank, or the call is refused with
78
+ # +return_to_not_registered+ (422).
73
79
  # @return [Hash] <tt>{"client_id", "email", "sent_at", "expires_at"}</tt>
74
- def claim_invite(client_id, email:, idempotency_key: nil)
75
- post_data(path("clients", client_id, "claim_invites"), { email: email }, idempotency_key)
80
+ def claim_invite(client_id, email:, return_to: nil, idempotency_key: nil)
81
+ body = { email: email }
82
+ body[:return_to] = return_to unless return_to.nil?
83
+ post_data(path("clients", client_id, "claim_invites"), body, idempotency_key)
76
84
  end
77
85
  end
78
86
 
@@ -1,6 +1,18 @@
1
1
  module EndPointBlank
2
2
  module Rack
3
3
  module Headers
4
+ # Headers the SDK never sends to intake, lower-cased and matched in any
5
+ # letter case (sc-1470).
6
+ #
7
+ # A request record used to carry every inbound header, so a caller's
8
+ # `Authorization: Basic client_id:secret` or bearer token, a proxy
9
+ # credential and its session cookie landed in the provider's request log
10
+ # unless the provider had written a masking rule for them. The writers
11
+ # drop these before masking runs, rather than mask them, so no rule and
12
+ # no mask_hook can bring them back. `Set-Cookie` never arrives on a
13
+ # request; it is listed so a response record can never carry it either.
14
+ SENSITIVE_HEADERS = %w[authorization proxy-authorization cookie set-cookie].freeze
15
+
4
16
  def self.extract
5
17
  env = ::EndPointBlank::Rack::EnvStore.get
6
18
  return {} if env.nil?
@@ -8,6 +20,20 @@ module EndPointBlank
8
20
  env.select { |k,v| k.start_with? 'HTTP_'}.
9
21
  transform_keys { |k| k.sub(/^HTTP_/, '').split('_').map(&:capitalize).join('-') }
10
22
  end
23
+
24
+ # The request's headers as a request or response record may send them:
25
+ # {extract} without any of {SENSITIVE_HEADERS}.
26
+ def self.reportable
27
+ without_sensitive(extract)
28
+ end
29
+
30
+ # A new hash of `headers` without any of {SENSITIVE_HEADERS}, whatever
31
+ # their letter case. Never changes its argument; nil is {}.
32
+ def self.without_sensitive(headers)
33
+ return {} if headers.nil?
34
+
35
+ headers.reject { |name, _value| SENSITIVE_HEADERS.include?(name.to_s.downcase) }
36
+ end
11
37
  end
12
38
  end
13
- end
39
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EndPointBlank
4
- VERSION = "0.13.0"
4
+ VERSION = "0.13.1"
5
5
  end
@@ -25,7 +25,9 @@ module EndPointBlank
25
25
  env = ::EndPointBlank::Rack::EnvStore.get
26
26
  request = ::Rack::Request.new(env)
27
27
  version = Commands::VersionFinder.new.find(request)
28
- headers = ::EndPointBlank::Rack::Headers.extract
28
+ # Authorization, Proxy-Authorization and Cookie are never sent,
29
+ # masking rule or not (Headers::SENSITIVE_HEADERS, sc-1470).
30
+ headers = ::EndPointBlank::Rack::Headers.reportable
29
31
 
30
32
  {
31
33
  app_name: EndPointBlank::Configuration.instance.app_name,
@@ -24,7 +24,10 @@ module EndPointBlank
24
24
  def payload(status:, headers:, body:, data: {})
25
25
  request = ::EndPointBlank::Rack::EnvStore.request
26
26
  env = ::EndPointBlank::Rack::EnvStore.get
27
- headers = ::EndPointBlank::Rack::Headers.extract
27
+ # The record carries the request's headers, not the `headers:`
28
+ # argument, so without the sc-1470 filter a caller's Authorization
29
+ # and Cookie reached the response record too.
30
+ headers = ::EndPointBlank::Rack::Headers.reportable
28
31
  version = request ? Commands::VersionFinder.new.find(request) : nil
29
32
  route = request ? Commands::RoutePatternFinder.find(request) : nil
30
33
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: end_point_blank
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert A. Lasch
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-02 00:00:00.000000000 Z
10
+ date: 2026-10-06 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: excon