Skip to content

Commit b0669de

Browse files
Merge upstream and update generated code for v2479 and 927bdc999cd89e81dfd3b8303986df21a573cf10
2 parents 0073cd5 + 0d8b075 commit b0669de

14 files changed

Lines changed: 278 additions & 3 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
branches:
88
- master
99
- beta
10+
- private-preview
1011
- sdk-release/**
1112
- feature/**
1213
tags:

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ This release changes the pinned API version to 2026-08-26.preview.
2626
* Add support for error codes `authentication_failure`, `capability_not_active`, `expired_payment_method`, `incorrect_postal_code`, `invalid_canceled_subscription_fields`, and `payment_method_restricted` on `QuotePreviewInvoice.last_finalization_error`
2727
* [#2121](https://github.com/stripe/stripe-php/pull/2121) Add non-verified methods to managed handlers
2828

29+
## 21.3.2 - 2026-09-09
30+
* [#2142](https://github.com/stripe/stripe-php/pull/2142) Validate that webhook secrets are non-empty
31+
32+
## 21.3.1 - 2026-09-01
33+
* [#2138](https://github.com/stripe/stripe-php/pull/2138) Harden API requestor code against malicious URLs
34+
2935
## 21.3.0 - 2026-08-26
3036
This release changes the pinned API version to 2026-08-26.dahlia.
3137

CODEGEN_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
baff58c9d515cdd5f5c3231d101989d588788c6f
1+
927bdc999cd89e81dfd3b8303986df21a573cf10

OPENAPI_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v2442
1+
v2479

examples/EventNotificationHandlerEndpoint.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
$api_key = getenv('STRIPE_API_KEY');
1616
$webhook_secret = getenv('WEBHOOK_SECRET');
1717

18+
if (empty($webhook_secret)) {
19+
fwrite(STDERR, "WEBHOOK_SECRET environment variable is not set. It should start with `whsec_`\n");
20+
21+
exit(1);
22+
}
23+
1824
$app = new Slim\App();
1925
$client = new Stripe\StripeClient($api_key);
2026

examples/EventNotificationWebhookHandler.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
$api_key = getenv('STRIPE_API_KEY');
1717
$webhook_secret = getenv('WEBHOOK_SECRET');
1818

19+
if (empty($webhook_secret)) {
20+
fwrite(STDERR, "WEBHOOK_SECRET environment variable is not set. It should start with `whsec_`\n");
21+
22+
exit(1);
23+
}
24+
1925
$app = new Slim\App();
2026
$client = new Stripe\StripeClient($api_key);
2127

lib/ApiRequestor.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,71 @@ private static function _defaultHeaders($apiKey, $clientInfo = null, $appInfo =
630630
];
631631
}
632632

633+
/**
634+
* Asserts that a request path is origin-relative: that it begins with a
635+
* single "/" and carries no scheme, authority or userinfo.
636+
*
637+
* The absolute URL is built by concatenating the API base onto this path,
638+
* and no base URL ends in a slash. A path like "@evil.example/v1/x" or
639+
* ".evil.example/v1/x" would modify the resulting host and direct the
640+
* request (including the API key) to a non-Stripe host.
641+
*
642+
* Because some relative urls arrive from potentially untrusted sources (like
643+
* webhook bodies), we have to be a little defensive.
644+
*
645+
* So, we require that a path starts with a leading slash and that
646+
* parse_url() finds no scheme or authority in it.
647+
*
648+
* @param string $url
649+
*
650+
* @throws Exception\InvalidArgumentException
651+
*/
652+
private static function validatePath($url)
653+
{
654+
if (!\is_string($url) || '/' !== \substr($url, 0, 1) || '//' === \substr($url, 0, 2)) {
655+
throw new Exception\InvalidArgumentException(
656+
'Request path must be a string beginning with a single "/".'
657+
);
658+
}
659+
660+
$parts = \parse_url($url);
661+
if (false === $parts || isset($parts['scheme']) || isset($parts['host']) || isset($parts['user'])) {
662+
throw new Exception\InvalidArgumentException(
663+
'Request path may not contain a scheme or authority.'
664+
);
665+
}
666+
}
667+
668+
/**
669+
* Rejects CR, LF and NUL in a header name or value.
670+
*
671+
* Header lines are assembled by concatenation, so a newline in either half
672+
* would let the remainder be parsed as additional headers. Some of these
673+
* values originate in remote data - fetchRelatedObject() sets Stripe-Context
674+
* and Stripe-Request-Trigger from the webhook body - and a per-request
675+
* api_key reaches the Authorization value without the whitespace check that
676+
* Stripe::setApiKey() applies.
677+
*
678+
* @param string $header
679+
* @param mixed $value
680+
*
681+
* @throws Exception\InvalidArgumentException
682+
*/
683+
private static function assertNoHeaderInjection($header, $value)
684+
{
685+
foreach ([$header, $value] as $part) {
686+
if (!\is_string($part)) {
687+
continue;
688+
}
689+
690+
if (false !== \strpbrk($part, "\r\n") || false !== \strpos($part, "\0")) {
691+
throw new Exception\InvalidArgumentException(
692+
'Header names and values may not contain CR, LF or NUL characters.'
693+
);
694+
}
695+
}
696+
}
697+
633698
/**
634699
* @param 'delete'|'get'|'post' $method
635700
* @param string $url
@@ -678,6 +743,7 @@ static function ($key) use ($params) {
678743
}
679744
}
680745

746+
self::validatePath($url);
681747
$absUrl = $this->_apiBase . $url;
682748
if ('v1' === $apiMode) {
683749
$params = self::_encodeObjects($params);
@@ -716,6 +782,7 @@ static function ($key) use ($params) {
716782
$rawHeaders = [];
717783

718784
foreach ($combinedHeaders as $header => $value) {
785+
self::assertNoHeaderInjection($header, $value);
719786
$rawHeaders[] = $header . ': ' . $value;
720787
}
721788

lib/Treasury/OutboundPayment.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class OutboundPayment extends \Stripe\ApiResource
3939
{
4040
const OBJECT_NAME = 'treasury.outbound_payment';
4141

42+
const PURPOSE_PAYROLL = 'payroll';
43+
4244
const STATUS_CANCELED = 'canceled';
4345
const STATUS_FAILED = 'failed';
4446
const STATUS_POSTED = 'posted';

lib/V2/Billing/MeterEventAdjustment.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ class MeterEventAdjustment extends \Stripe\ApiResource
2222

2323
const STATUS_COMPLETE = 'complete';
2424
const STATUS_PENDING = 'pending';
25+
26+
const TYPE_CANCEL = 'cancel';
2527
}

lib/V2/Core/EventNotification.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ public function fetchEvent()
118118
{
119119
$response = $this->client->rawRequest(
120120
'get',
121-
"/v2/core/events/{$this->id}",
121+
// `id` comes from the notification body, so encode it the way
122+
// buildPath() does for generated services -- otherwise it can inject
123+
// extra path or query segments.
124+
'/v2/core/events/' . \urlencode($this->id),
122125
null,
123126
[
124127
'stripe_context' => $this->context,

0 commit comments

Comments
 (0)