basecradle 0.10.1 → 0.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 +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +19 -0
- data/lib/basecradle/version.rb +1 -1
- data/lib/basecradle/webhooks.rb +45 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e662740bb7ecbde3769c3430a269cbe32a0c507a4244626688af1ca13ce3ce4
|
|
4
|
+
data.tar.gz: 64820f7409159b2cfd1b063a53c96858302bfc939e4ff2df17b6ffcaf3b494e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6c7a007a0c7eef4fe1bb8d4d19fc108c361fc5e5d4a618d5d61600a0cdd55fbb6b97ecc5ea5c07ed8ac6488840b7cc33133c59a6160feab2a04222da73683eeb
|
|
7
|
+
data.tar.gz: 7059369ae648787b45a97afe3b12ba721474629078031ac036461732cc7dfc9d13e764b916f48a7e72fa96b248bac4c53c1ee3ed686f2abc98d13ed1c54a6c28
|
data/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,40 @@ section**. The newest heading is always the version `lib/basecradle/version.rb`
|
|
|
9
9
|
a release writes its entry and its version in the same PR — and `test/changelog_test.rb`
|
|
10
10
|
fails CI if the two ever disagree.
|
|
11
11
|
|
|
12
|
+
## [0.10.2] - 2026-09-30
|
|
13
|
+
|
|
14
|
+
### Security
|
|
15
|
+
|
|
16
|
+
- **`WebhookEventHeaders` no longer prints header values when inspected**
|
|
17
|
+
([#206](https://github.com/basecradle/basecradle-ruby/issues/206)). An inbound
|
|
18
|
+
delivery's headers are the *sender's*, stored verbatim by the platform, so one of them
|
|
19
|
+
may be the sender's own credential — a POST authenticated to an ingest URL carries its
|
|
20
|
+
`Authorization` or `X-Api-Key` right there. `WebhookEventHeaders` is a `Hash`
|
|
21
|
+
descendant and inherited `Hash`'s render, which prints every pair, so
|
|
22
|
+
`logger.debug(event.content.headers)` wrote **another party's secret** into your logs.
|
|
23
|
+
`inspect`, `to_s` and `pp` now render the header names alone, sorted, matching the rule
|
|
24
|
+
every other object in this SDK already followed (`ApiObject#inspect` prints field
|
|
25
|
+
names, never values):
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
#<BaseCradle::WebhookEventHeaders Authorization, Content-Type, X-Api-Key>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Only the human-facing render changed.** Every read is untouched and still wire-exact:
|
|
32
|
+
the case-folded lookup, `fetch`, `each`, `keys`, `to_h`, `to_json`, `==`. If you were
|
|
33
|
+
relying on `inspect` to see values, use `to_h` — which is what it was always for. No
|
|
34
|
+
credential of yours was exposed; the exposure was of whoever sent you the webhook, so
|
|
35
|
+
if you have logged inbound deliveries at debug level, treat those senders' headers as
|
|
36
|
+
disclosed.
|
|
37
|
+
- **Two ways a header value still reaches a log, both by design.** `to_json`/`as_json`
|
|
38
|
+
emit the delivery verbatim — that is what a webhook record *is* — so
|
|
39
|
+
`render json: event` and `logger.info(event.to_json)` still write the sender's headers
|
|
40
|
+
in full. And converting away from the type gives up the redaction exactly as it gives up
|
|
41
|
+
the case-folding: `slice`, `except`, `select`, `transform_values` and `to_h` each hand
|
|
42
|
+
back a plain `Hash`, whose `inspect` prints pairs. Only `merge` and `dup` keep the type,
|
|
43
|
+
and so the redaction. Changing what a webhook record *serializes* as is a separate
|
|
44
|
+
decision and is not made here.
|
|
45
|
+
|
|
12
46
|
## [0.10.1] - 2026-09-30
|
|
13
47
|
|
|
14
48
|
### Security
|
|
@@ -450,6 +484,7 @@ the Python SDK's behavior in idiomatic Ruby. Zero runtime dependencies.
|
|
|
450
484
|
- **Quality bars** — a README-as-tested-doc harness (every example runs against a mocked
|
|
451
485
|
API) and a spec drift-guard (CI fails if the live API grows beyond the SDK).
|
|
452
486
|
|
|
487
|
+
[0.10.2]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.10.2
|
|
453
488
|
[0.10.1]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.10.1
|
|
454
489
|
[0.10.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.10.0
|
|
455
490
|
[0.9.0]: https://github.com/basecradle/basecradle-ruby/releases/tag/v0.9.0
|
data/README.md
CHANGED
|
@@ -220,6 +220,19 @@ fallback-less `fetch` raise `KeyError` naming the headers that did arrive, while
|
|
|
220
220
|
and the methods that reshape it (`slice`, `except`, `select`) work on those stored names,
|
|
221
221
|
case-sensitively.
|
|
222
222
|
|
|
223
|
+
What it does **not** show you is the values. `inspect`, `to_s` and `pp` print the header
|
|
224
|
+
names alone — `#<BaseCradle::WebhookEventHeaders Authorization, Content-Type>` — because
|
|
225
|
+
these headers are the **sender's**, and a sender that authenticates its POST to your
|
|
226
|
+
ingest URL carries its credential in one of them. So `logger.debug(event.content.headers)`
|
|
227
|
+
cannot write another party's secret into your logs. Every read is unaffected; `to_h` is
|
|
228
|
+
how you look at values.
|
|
229
|
+
|
|
230
|
+
Two things do still show them, deliberately. `to_json` emits the delivery verbatim — that
|
|
231
|
+
is what the record is — so `render json: event` and `logger.info(event.to_json)` write the
|
|
232
|
+
headers in full. And converting away from the type gives up the redaction exactly as it
|
|
233
|
+
gives up the case-folding: `slice`, `except`, `select`, `transform_values` and `to_h` hand
|
|
234
|
+
back a plain `Hash`, which prints pairs. `merge` and `dup` keep the type, and keep both.
|
|
235
|
+
|
|
223
236
|
This holds however you reach the record. A timeline item's `content` is typed by the
|
|
224
237
|
item's own `type`, so a `webhook_event` row of `timeline.items` is a `WebhookEventContent`
|
|
225
238
|
just like one from `bc.webhook_events` — `item.content.headers["X-GitHub-Delivery"]` folds
|
|
@@ -426,6 +439,12 @@ CI additionally lints the workflow files with [actionlint](https://github.com/rh
|
|
|
426
439
|
both and run `actionlint` from the repo root — CI pins actionlint by version and SHA256 in
|
|
427
440
|
`.github/workflows/ci.yml`; match that version to see what CI sees.
|
|
428
441
|
|
|
442
|
+
Bare `actionlint` covers slightly more than CI blocks on. It lints everything under
|
|
443
|
+
`.github/workflows`, including `needs-human-alert.yml` and `dependabot-auto-merge.yml`,
|
|
444
|
+
which are shared verbatim from the BaseCradle core repo and are not this repo's to edit;
|
|
445
|
+
CI lints those two advisory-only, so a finding in them is reported upstream rather than
|
|
446
|
+
failing the build. A finding in any other workflow does fail it.
|
|
447
|
+
|
|
429
448
|
## Contributing
|
|
430
449
|
|
|
431
450
|
Human and AI contributors work under identical rules here: branch → PR → green CI → merge. See [`CLAUDE.md`](CLAUDE.md) for the project conventions and the issues for the roadmap.
|
data/lib/basecradle/version.rb
CHANGED
data/lib/basecradle/webhooks.rb
CHANGED
|
@@ -99,6 +99,14 @@ module BaseCradle
|
|
|
99
99
|
# that already happened. Converting away from this type gives up the folding too
|
|
100
100
|
# (+to_h+, <tt>{**headers}</tt>, and anything else that hands back a plain +Hash+);
|
|
101
101
|
# +merge+ and +dup+ keep it.
|
|
102
|
+
#
|
|
103
|
+
# What it does *not* print is the values: +inspect+, +to_s+ and +pp+ render the header
|
|
104
|
+
# names alone, because these are the sender's headers and one of them may be the
|
|
105
|
+
# sender's credential. Every read is unaffected — and so is +to_json+, which still
|
|
106
|
+
# emits the delivery verbatim. Converting away from this type gives the redaction up
|
|
107
|
+
# exactly as it gives up the folding, and for the same reason: +slice+, +except+,
|
|
108
|
+
# +select+, +transform_values+ and +to_h+ hand back a plain +Hash+, whose +inspect+
|
|
109
|
+
# prints pairs.
|
|
102
110
|
class WebhookEventHeaders < Hash
|
|
103
111
|
# Hash's own exact-match membership, kept under a private name before the case-folding
|
|
104
112
|
# +key?+ below takes the name: a caller who wrote the wire's own spelling is answered
|
|
@@ -167,6 +175,43 @@ module BaseCradle
|
|
|
167
175
|
->(name) { dig(name) }
|
|
168
176
|
end
|
|
169
177
|
|
|
178
|
+
# The header *names* this delivery carried, and none of their values.
|
|
179
|
+
#
|
|
180
|
+
# +Hash+'s own +inspect+ prints every pair, and these headers are the *sender's*,
|
|
181
|
+
# stored verbatim by the platform: a sender that authenticates its POST to an ingest
|
|
182
|
+
# URL puts its credential in one of them, so +logger.debug(event.content.headers)+
|
|
183
|
+
# would write another party's secret into our logs. Every other renderable object in
|
|
184
|
+
# this SDK is already safe by one rule — +ApiObject#inspect+ prints field names, never
|
|
185
|
+
# values — and this is the one class that sat outside it, because +Hash+ brought its
|
|
186
|
+
# own render.
|
|
187
|
+
#
|
|
188
|
+
# Only the human-facing render changes. Every *read* is untouched and still
|
|
189
|
+
# wire-exact: lookups, +fetch+, +each+, +keys+, +to_h+, +to_json+, +==+.
|
|
190
|
+
#
|
|
191
|
+
# Names are sorted, as +ApiObject#inspect+ sorts its fields and as the +KeyError+ a
|
|
192
|
+
# missing header raises already lists the ones that did arrive.
|
|
193
|
+
def inspect
|
|
194
|
+
return "#<#{self.class}>" if empty?
|
|
195
|
+
|
|
196
|
+
"#<#{self.class} #{keys.sort_by(&:to_s).join(', ')}>"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# +Hash+ aliases +to_s+ to its *own* +inspect+ — one shared method entry — so
|
|
200
|
+
# overriding +inspect+ alone would leave <tt>"#{headers}"</tt> and +puts headers+
|
|
201
|
+
# printing every value. Late-bound rather than +alias to_s inspect+, so a subclass
|
|
202
|
+
# that redacts more is not bypassed by interpolation (as +Client#to_s+ is).
|
|
203
|
+
def to_s
|
|
204
|
+
inspect
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# +pp+ reaches for +pretty_print+, and +Hash+ brings one that prints the pairs — the
|
|
208
|
+
# same leak through the one door +inspect+ does not cover. Delegating puts +pp+ back
|
|
209
|
+
# on the SDK-wide rule: +pp+ uses an object's own +inspect+ when it defines one, which
|
|
210
|
+
# is why every +ApiObject+ is already safe here and only a +Hash+ descendant was not.
|
|
211
|
+
def pretty_print(printer)
|
|
212
|
+
printer.text(inspect)
|
|
213
|
+
end
|
|
214
|
+
|
|
170
215
|
private
|
|
171
216
|
|
|
172
217
|
# The wire's own spelling of +name+, or nil if no casing of it was delivered. Anything
|