@automattic/newspack-blocks 4.28.0 → 4.28.1-hotfix-blocks-input-validation.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## @automattic/newspack-blocks [4.28.1-hotfix-blocks-input-validation.2](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.28.1-hotfix-blocks-input-validation.1...newspack-blocks@4.28.1-hotfix-blocks-input-validation.2) (2026-07-31)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **blocks:** close after-success dead ends and tighten write checks ([12e6bd8](https://github.com/Automattic/newspack-workspace/commit/12e6bd898d28eaf4fb6a59633f99b297febc308f))
7
+
1
8
  # @automattic/newspack-blocks [4.28.0](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.27.1...newspack-blocks@4.28.0) (2026-07-20)
2
9
 
3
10
 
@@ -1188,6 +1188,80 @@ final class Modal_Checkout {
1188
1188
  ob_end_flush();
1189
1189
  }
1190
1190
 
1191
+ /**
1192
+ * Behaviors that have somewhere to go once checkout finishes.
1193
+ *
1194
+ * Anything else leaves the reader in a modal that won't move and won't close, so it is
1195
+ * treated as "close the modal" instead.
1196
+ *
1197
+ * @var string[]
1198
+ */
1199
+ const AFTER_SUCCESS_BEHAVIORS = [ 'custom', 'referrer' ];
1200
+
1201
+ /**
1202
+ * Reduce a post-checkout destination to one this site is willing to send readers to.
1203
+ *
1204
+ * The destination reaches the thank-you page through the request, whether it came from
1205
+ * the block's own settings or from the link the reader followed, and by that point the
1206
+ * two are indistinguishable. Core's redirect validation decides, so the destinations
1207
+ * this site accepts are the ones `allowed_redirect_hosts` reports. That's this site's
1208
+ * own host, plus whatever a publisher adds through that filter, plus anything another
1209
+ * plugin has added (`Newspack\NRH` registers one).
1210
+ *
1211
+ * @param string $url The requested destination.
1212
+ *
1213
+ * @return string The destination, or an empty string if it is not allowed.
1214
+ */
1215
+ public static function sanitize_after_success_url( $url ) {
1216
+ $url = sanitize_url( (string) $url );
1217
+
1218
+ if ( empty( $url ) ) {
1219
+ return '';
1220
+ }
1221
+
1222
+ // Core compares hosts case-sensitively; host names aren't. Normalise first so this
1223
+ // site's own host typed in capitals isn't read as somewhere else.
1224
+ $host = wp_parse_url( $url, PHP_URL_HOST );
1225
+ if ( $host && strtolower( $host ) !== $host ) {
1226
+ $url = str_replace( '://' . $host, '://' . strtolower( $host ), $url );
1227
+ }
1228
+
1229
+ return (string) wp_validate_redirect( $url, '' );
1230
+ }
1231
+
1232
+ /**
1233
+ * Drop an after-success behavior the reader can't act on.
1234
+ *
1235
+ * A behavior with nowhere to go renders a modal that neither navigates nor closes, so
1236
+ * the destination, the behavior and its button label all fall away together and the
1237
+ * reader gets the ordinary "close" button instead of one labelled for a page they
1238
+ * won't be taken to.
1239
+ *
1240
+ * @param array $params After-success params.
1241
+ *
1242
+ * @return array The params, less any the reader can't act on.
1243
+ */
1244
+ public static function filter_after_success_params( $params ) {
1245
+ $behavior = $params['after_success_behavior'] ?? '';
1246
+
1247
+ if ( '' === $behavior ) {
1248
+ return $params;
1249
+ }
1250
+
1251
+ $is_unknown = ! in_array( $behavior, self::AFTER_SUCCESS_BEHAVIORS, true );
1252
+ $has_nowhere = 'custom' === $behavior && empty( $params['after_success_url'] );
1253
+
1254
+ if ( $is_unknown || $has_nowhere ) {
1255
+ unset(
1256
+ $params['after_success_behavior'],
1257
+ $params['after_success_url'],
1258
+ $params['after_success_button_label']
1259
+ );
1260
+ }
1261
+
1262
+ return $params;
1263
+ }
1264
+
1191
1265
  /**
1192
1266
  * Get after success button params.
1193
1267
  */
@@ -1201,14 +1275,16 @@ final class Modal_Checkout {
1201
1275
  \wp_parse_str( $referrer_query, $request_params );
1202
1276
  }
1203
1277
  }
1204
- return array_filter(
1278
+ $params = array_filter(
1205
1279
  [
1206
1280
  'after_success_behavior' => isset( $request_params['after_success_behavior'] ) ? sanitize_text_field( wp_unslash( $request_params['after_success_behavior'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1207
- 'after_success_url' => isset( $request_params['after_success_url'] ) ? sanitize_url( wp_unslash( $request_params['after_success_url'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1281
+ 'after_success_url' => isset( $request_params['after_success_url'] ) ? self::sanitize_after_success_url( wp_unslash( $request_params['after_success_url'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1208
1282
  'after_success_button_label' => isset( $request_params['after_success_button_label'] ) ? sanitize_text_field( wp_unslash( $request_params['after_success_button_label'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1209
1283
  'action_type' => isset( $request_params['action_type'] ) ? sanitize_text_field( wp_unslash( $request_params['action_type'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1210
1284
  ]
1211
1285
  );
1286
+
1287
+ return self::filter_after_success_params( $params );
1212
1288
  }
1213
1289
 
1214
1290
  /**
@@ -7,7 +7,7 @@
7
7
  * Author URI: https://newspack.com/
8
8
  * Text Domain: newspack-blocks
9
9
  * Domain Path: /languages
10
- * Version: 4.28.0
10
+ * Version: 4.28.1-hotfix-blocks-input-validation.2
11
11
  *
12
12
  * @package Newspack_Blocks
13
13
  */
@@ -15,7 +15,7 @@
15
15
  define( 'NEWSPACK_BLOCKS__PLUGIN_FILE', __FILE__ );
16
16
  define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', 'dist/' );
17
17
  define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( NEWSPACK_BLOCKS__PLUGIN_FILE ) );
18
- define( 'NEWSPACK_BLOCKS__VERSION', '4.28.0' );
18
+ define( 'NEWSPACK_BLOCKS__VERSION', '4.28.1-hotfix-blocks-input-validation.2' );
19
19
 
20
20
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks.php';
21
21
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks-api.php';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/newspack-blocks",
3
- "version": "4.28.0",
3
+ "version": "4.28.1-hotfix-blocks-input-validation.2",
4
4
  "author": "Automattic",
5
5
  "description": "=== Newspack Blocks === Contributors: (this should be a list of wordpress.org userid's) Donate link: https://example.com/ Tags: comments, spam Requires at least: 4.5 Tested up to: 5.1.1 Stable tag: 0.1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html",
6
6
  "repository": {
@@ -12,6 +12,13 @@
12
12
  class WP_REST_Newspack_Author_List_Controller extends WP_REST_Newspack_Authors_Controller {
13
13
  // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound
14
14
 
15
+ /**
16
+ * Fields returned when a caller doesn't ask for a specific set.
17
+ *
18
+ * @var string[]
19
+ */
20
+ const DEFAULT_FIELDS = [ 'id', 'name', 'bio', 'email', 'social', 'avatar', 'url' ];
21
+
15
22
  /**
16
23
  * Constructs the controller.
17
24
  *
@@ -136,6 +143,10 @@ class WP_REST_Newspack_Author_List_Controller extends WP_REST_Newspack_Authors_C
136
143
  $options['fields'] = explode( ',', $request->get_param( 'fields' ) );
137
144
  }
138
145
 
146
+ // Restricted here rather than in get_all_authors(), which also renders the block on
147
+ // the front end for visitors and must keep returning whatever a publisher publishes.
148
+ $options['fields'] = self::restrict_fields( $options['fields'] ?? self::DEFAULT_FIELDS );
149
+
139
150
  $combined_authors = $this->get_all_authors( $options );
140
151
  $response = new \WP_REST_Response( $combined_authors );
141
152
  $response->header( 'x-wp-total', count( $combined_authors ) );
@@ -157,7 +168,7 @@ class WP_REST_Newspack_Author_List_Controller extends WP_REST_Newspack_Authors_C
157
168
  'avatar_hide_default' => false,
158
169
  'exclude' => [], // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
159
170
  'exclude_empty' => false,
160
- 'fields' => [ 'id', 'name', 'bio', 'email', 'social', 'avatar', 'url' ],
171
+ 'fields' => self::DEFAULT_FIELDS,
161
172
  'per_page' => 10,
162
173
  ];
163
174
  $options = wp_parse_args( $options, $default_options );
@@ -57,7 +57,7 @@ function newspack_blocks_render_block_author_list( $attributes ) {
57
57
  'author_type' => $author_type,
58
58
  'author_roles' => $author_roles,
59
59
  'exclude' => $attributes['exclude'], // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
60
- 'fields' => [ 'id', 'name', 'bio', 'email', 'social', 'avatar', 'url' ],
60
+ 'fields' => WP_REST_Newspack_Author_List_Controller::DEFAULT_FIELDS,
61
61
  ];
62
62
 
63
63
  if ( $exclude_empty ) {
@@ -12,6 +12,14 @@
12
12
  class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
13
13
  // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound
14
14
 
15
+ /**
16
+ * Fields carrying contact details that WordPress reserves for people who manage users.
17
+ *
18
+ * These endpoints are open to anyone who can edit posts, which is a much wider audience
19
+ * than core allows to read an address.
20
+ */
21
+ const USER_MANAGEMENT_FIELDS = [ 'email' ];
22
+
15
23
  /**
16
24
  * Constructs the controller.
17
25
  *
@@ -22,6 +30,41 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
22
30
  $this->rest_base = 'authors';
23
31
  }
24
32
 
33
+ /**
34
+ * Whether the current user may read author contact details.
35
+ *
36
+ * Matches the capabilities core requires before exposing a user's address.
37
+ *
38
+ * @return boolean
39
+ */
40
+ public static function can_read_user_management_fields() {
41
+ // Keep `list_users` first: on multisite, core maps `edit_users` through
42
+ // `manage_network_users`, so a site administrator passes only on `list_users`.
43
+ return current_user_can( 'list_users' ) || current_user_can( 'edit_users' );
44
+ }
45
+
46
+ /**
47
+ * Drop any requested fields the current user is not allowed to read.
48
+ *
49
+ * Applied per request rather than inside the formatting helpers, which also render the
50
+ * front-end author blocks for visitors and must keep returning whatever a publisher has
51
+ * chosen to publish there.
52
+ *
53
+ * `false` is the helpers' "every field" convention, used by the front-end blocks; it is
54
+ * passed through untouched so those renders are unaffected.
55
+ *
56
+ * @param array|false $fields Requested fields, or false for every field.
57
+ *
58
+ * @return array|false Fields the current user may receive.
59
+ */
60
+ public static function restrict_fields( $fields ) {
61
+ if ( ! is_array( $fields ) || self::can_read_user_management_fields() ) {
62
+ return $fields;
63
+ }
64
+
65
+ return array_values( array_diff( $fields, self::USER_MANAGEMENT_FIELDS ) );
66
+ }
67
+
25
68
  /**
26
69
  * Registers the necessary REST API routes.
27
70
  *
@@ -84,6 +127,7 @@ class WP_REST_Newspack_Authors_Controller extends WP_REST_Controller {
84
127
  $per_page = ! empty( $request->get_param( 'perPage' ) ) ? $request->get_param( 'perPage' ) : 10; // Number of results to return per page. This is applied to each query, so the actual number of results returned may be up to 2x this number.
85
128
  $avatar_hide_default = ! empty( $request->get_param( 'avatar_hide_default' ) ) ? true : false; // Hide the default avatar if the user has no custom avatar.
86
129
  $fields = ! empty( $request->get_param( 'fields' ) ) ? explode( ',', $request->get_param( 'fields' ) ) : [ 'id' ]; // Fields to get. Will return at least id.
130
+ $fields = self::restrict_fields( $fields );
87
131
  $include = ! empty( $request->get_param( 'include' ) ) ? explode( ',', $request->get_param( 'include' ) ) : null; // Fetch authors by multiple IDs.
88
132
  $post_id = ! empty( $request->get_param( 'post_id' ) ) ? $request->get_param( 'post_id' ) : 0; // Fetch authors for a specific post (contextual mode).
89
133
 
@@ -246,6 +246,100 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
246
246
  return in_array( $mime_type, array_keys( self::iframe_archive_accepted_file_mimes() ), true );
247
247
  }
248
248
 
249
+ /**
250
+ * Reduce a caller-supplied name to a single safe path segment.
251
+ *
252
+ * Used for names this plugin generates but builds out of caller-supplied parts, such as
253
+ * the destination folder derived from an attachment title.
254
+ *
255
+ * @param string $segment The name to reduce.
256
+ *
257
+ * @return string|false The safe segment, or false if nothing usable is left.
258
+ */
259
+ private function sanitize_path_segment( $segment ) {
260
+ $segment = sanitize_file_name( basename( str_replace( '\\', '/', (string) $segment ) ) );
261
+
262
+ if ( '' === $segment || '.' === $segment || '..' === $segment ) {
263
+ return false;
264
+ }
265
+
266
+ return $segment;
267
+ }
268
+
269
+ /**
270
+ * Reduce a caller-supplied relative path to safe segments, keeping subdirectories.
271
+ *
272
+ * Archive entries may legitimately sit in subdirectories and are referenced by name from
273
+ * the archive's own markup, so the segments are checked rather than rewritten: renaming
274
+ * them would break those references. Anything that could point outside the destination —
275
+ * an upward segment, an absolute path, a drive letter or a null byte — is refused
276
+ * outright rather than stripped, since stripping is what makes layered payloads such as
277
+ * `....//` work.
278
+ *
279
+ * @param string $path The relative path to reduce.
280
+ *
281
+ * @return string|false The safe relative path, or false if it is not usable.
282
+ */
283
+ private function sanitize_relative_path( $path ) {
284
+ $path = str_replace( '\\', '/', (string) $path );
285
+
286
+ if ( '' === $path || false !== strpos( $path, "\0" ) ) {
287
+ return false;
288
+ }
289
+
290
+ // Absolute paths, including Windows drive letters.
291
+ if ( '/' === $path[0] || preg_match( '#^[a-zA-Z]:#', $path ) ) {
292
+ return false;
293
+ }
294
+
295
+ $segments = [];
296
+ foreach ( explode( '/', $path ) as $segment ) {
297
+ if ( '' === $segment || '.' === $segment ) {
298
+ continue;
299
+ }
300
+ if ( '..' === $segment ) {
301
+ return false;
302
+ }
303
+ $segments[] = $segment;
304
+ }
305
+
306
+ return empty( $segments ) ? false : implode( '/', $segments );
307
+ }
308
+
309
+ /**
310
+ * Confirm that a destination resolves inside a directory.
311
+ *
312
+ * A last check on the resolved paths, so that a symlink or a gap in the checks above
313
+ * still cannot place a file outside the directory. The destination itself need not
314
+ * exist yet: the deepest part of it that does exist is what gets resolved, which is
315
+ * what lets this run before anything is created.
316
+ *
317
+ * @param string $directory Absolute path to the directory the write must stay inside.
318
+ * @param string $destination Absolute path to the intended destination.
319
+ *
320
+ * @return boolean
321
+ */
322
+ private function is_inside_directory( $directory, $destination ) {
323
+ $directory = realpath( $directory );
324
+
325
+ if ( ! $directory ) {
326
+ return false;
327
+ }
328
+
329
+ $existing = dirname( $destination );
330
+ while ( ! file_exists( $existing ) && dirname( $existing ) !== $existing ) {
331
+ $existing = dirname( $existing );
332
+ }
333
+
334
+ $resolved = realpath( $existing );
335
+
336
+ if ( ! $resolved ) {
337
+ return false;
338
+ }
339
+
340
+ return 0 === strpos( trailingslashit( $resolved ), trailingslashit( $directory ) );
341
+ }
342
+
249
343
  /**
250
344
  * Receive the iframe content, handle it, and returns the URL and the folder name.
251
345
  *
@@ -363,7 +457,11 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
363
457
  * @return WP_REST_Response
364
458
  */
365
459
  private function process_iframe_document( $request, $document_filename, $media_source_path ) {
366
- if ( ! $this->validate_file_extension( $document_filename, false ) ) {
460
+ // The filename is built from the source attachment's title, so it is only a name once
461
+ // it has been reduced to a single segment.
462
+ $document_filename = $this->sanitize_path_segment( $document_filename );
463
+
464
+ if ( ! $document_filename || ! $this->validate_file_extension( $document_filename, false ) ) {
367
465
  return new WP_Error(
368
466
  'newspack_blocks',
369
467
  sprintf(
@@ -393,6 +491,14 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
393
491
  }
394
492
  }
395
493
 
494
+ if ( ! $this->is_inside_directory( $iframe_upload_dir, $iframe_path ) ) {
495
+ return new WP_Error(
496
+ 'newspack_blocks',
497
+ __( 'Could not upload your document.', 'newspack-blocks' ),
498
+ [ 'status' => '400' ]
499
+ );
500
+ }
501
+
396
502
  // Copy uploaded document file to destination.
397
503
  if ( copy( $media_source_path, $iframe_path ) ) {
398
504
  return [
@@ -434,14 +540,25 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
434
540
  }
435
541
  global $wp_filesystem;
436
542
 
543
+ // The folder is built from the source attachment's title, so it is only a name once it
544
+ // has been reduced to a single segment.
545
+ $iframe_folder = $this->sanitize_path_segment( $iframe_folder );
546
+ if ( ! $iframe_folder ) {
547
+ return $invalid_file_error;
548
+ }
549
+
437
550
  $wp_upload_dir = wp_upload_dir();
438
551
  $iframe_upload_dir = $wp_upload_dir['path'] . self::IFRAME_UPLOAD_DIR;
439
552
  $iframe_path = trailingslashit( $iframe_upload_dir . $iframe_folder );
440
553
  $data = $request->get_body_params();
441
554
 
442
- // create iframe directory if not existing.
443
- if ( ! file_exists( $iframe_upload_dir ) ) {
444
- wp_mkdir_p( $iframe_upload_dir );
555
+ // Create this archive's own directory up front, so each entry's destination can be
556
+ // resolved against it before anything is written.
557
+ if ( ! file_exists( $iframe_path ) ) {
558
+ wp_mkdir_p( $iframe_path );
559
+ }
560
+ if ( ! $this->is_inside_directory( $iframe_upload_dir, $iframe_path ) ) {
561
+ return $invalid_file_error;
445
562
  }
446
563
 
447
564
  // Extract files from archive.
@@ -464,10 +581,24 @@ class WP_REST_Newspack_Iframe_Controller extends WP_REST_Controller {
464
581
  }
465
582
  $stat = $zip->statIndex( $file_index );
466
583
  if ( $stat && isset( $stat['name'] ) ) {
467
- if ( ! file_exists( dirname( $iframe_path . $stat['name'] ) ) ) {
468
- wp_mkdir_p( dirname( $iframe_path . $stat['name'] ) );
584
+ // An entry names its own destination, so it decides where the write lands.
585
+ $entry_relative_path = $this->sanitize_relative_path( $stat['name'] );
586
+ if ( ! $entry_relative_path ) {
587
+ return $invalid_file_error;
588
+ }
589
+
590
+ $destination = $iframe_path . $entry_relative_path;
591
+
592
+ // Checked before anything is created, so a destination outside this archive's
593
+ // own folder never gets a directory made for it.
594
+ if ( ! $this->is_inside_directory( $iframe_path, $destination ) ) {
595
+ return $invalid_file_error;
469
596
  }
470
- $put = $wp_filesystem->put_contents( $iframe_path . $stat['name'], $contents );
597
+ if ( ! file_exists( dirname( $destination ) ) ) {
598
+ wp_mkdir_p( dirname( $destination ) );
599
+ }
600
+
601
+ $put = $wp_filesystem->put_contents( $destination, $contents );
471
602
  if ( ! $put ) {
472
603
  return $invalid_file_error;
473
604
  }
@@ -47,9 +47,22 @@ if ( ! $is_valid ) {
47
47
 
48
48
  $is_success = ! $order->has_status( 'failed' );
49
49
  $after_success_behavior = isset( $_GET['after_success_behavior'] ) ? \sanitize_text_field( \wp_unslash( $_GET['after_success_behavior'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
50
- $after_success_url = isset( $_GET['after_success_url'] ) ? esc_url( \sanitize_url( \wp_unslash( $_GET['after_success_url'] ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
50
+ $after_success_url = isset( $_GET['after_success_url'] ) ? esc_url( \Newspack_Blocks\Modal_Checkout::sanitize_after_success_url( \sanitize_url( \wp_unslash( $_GET['after_success_url'] ) ) ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
51
51
  $after_success_label = isset( $_GET['after_success_button_label'] ) ? \sanitize_text_field( \wp_unslash( $_GET['after_success_button_label'] ) ) : \Newspack_Blocks\Modal_Checkout::get_modal_checkout_labels( 'after_success' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
52
52
  $checkout_data = Checkout_Data::get_checkout_data( $order );
53
+
54
+ // A behavior the reader can't act on falls back to closing the modal, so they get a
55
+ // finished checkout rather than a button that goes nowhere.
56
+ $after_success_params = \Newspack_Blocks\Modal_Checkout::filter_after_success_params(
57
+ [
58
+ 'after_success_behavior' => $after_success_behavior,
59
+ 'after_success_url' => $after_success_url,
60
+ 'after_success_button_label' => $after_success_label,
61
+ ]
62
+ );
63
+ $after_success_behavior = $after_success_params['after_success_behavior'] ?? '';
64
+ $after_success_url = $after_success_params['after_success_url'] ?? '';
65
+ $after_success_label = $after_success_params['after_success_button_label'] ?? \Newspack_Blocks\Modal_Checkout::get_modal_checkout_labels( 'after_success' );
53
66
  ?>
54
67
  <div class="woocommerce-order">
55
68
  <?php if ( $is_success ) : ?>
@@ -3,7 +3,7 @@
3
3
  'name' => 'automattic/newspack-blocks',
4
4
  'pretty_version' => 'dev-main',
5
5
  'version' => 'dev-main',
6
- 'reference' => 'f937e9f691dbdb938f7478a05bc22d51b771311f',
6
+ 'reference' => '12e6bd898d28eaf4fb6a59633f99b297febc308f',
7
7
  'type' => 'wordpress-plugin',
8
8
  'install_path' => __DIR__ . '/../../',
9
9
  'aliases' => array(),
@@ -13,7 +13,7 @@
13
13
  'automattic/newspack-blocks' => array(
14
14
  'pretty_version' => 'dev-main',
15
15
  'version' => 'dev-main',
16
- 'reference' => 'f937e9f691dbdb938f7478a05bc22d51b771311f',
16
+ 'reference' => '12e6bd898d28eaf4fb6a59633f99b297febc308f',
17
17
  'type' => 'wordpress-plugin',
18
18
  'install_path' => __DIR__ . '/../../',
19
19
  'aliases' => array(),