@preprio/toolkit 0.2.0-beta.5 → 0.2.0-beta.6

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.
@@ -30,7 +30,7 @@ __export(astro_exports, {
30
30
  module.exports = __toCommonJS(astro_exports);
31
31
 
32
32
  // src/version.ts
33
- var VERSION = "0.2.0-beta.5";
33
+ var VERSION = "0.2.0-beta.6";
34
34
 
35
35
  // src/core/features.ts
36
36
  function isEnabled(config) {
@@ -193,6 +193,11 @@ var UTM_PARAMS = [
193
193
  "utm_content",
194
194
  "utm_campaign"
195
195
  ];
196
+ var MAX_HEADER_VALUE_LENGTH = 2048;
197
+ function sanitizeHeaderValue(value) {
198
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
199
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
200
+ }
196
201
  function serializeCookie(cookie) {
197
202
  const parts = [
198
203
  `${cookie.name}=${encodeURIComponent(cookie.value)}`,
@@ -224,6 +229,15 @@ function parseCookies(request) {
224
229
  function processPreprRequest(request, opts) {
225
230
  const requestHeaders = new Headers(request.headers);
226
231
  const responseCookies = [];
232
+ const inboundPreprHeaders = [];
233
+ requestHeaders.forEach((_value, key) => {
234
+ if (key.toLowerCase().startsWith("prepr-")) {
235
+ inboundPreprHeaders.push(key);
236
+ }
237
+ });
238
+ for (const key of inboundPreprHeaders) {
239
+ requestHeaders.delete(key);
240
+ }
227
241
  const requestCookies = parseCookies(request);
228
242
  const url = new URL(request.url);
229
243
  const searchParams = url.searchParams;
@@ -233,25 +247,28 @@ function processPreprRequest(request, opts) {
233
247
  for (const key of UTM_PARAMS) {
234
248
  const value = searchParams.get(key);
235
249
  if (value !== null) {
236
- requestHeaders.set(`Prepr-Context-${key}`, value);
250
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
237
251
  }
238
252
  }
239
253
  const referer = request.headers.get("referer");
240
254
  if (referer) {
241
- requestHeaders.set("Prepr-Context-initial_referral", referer);
255
+ requestHeaders.set(
256
+ "Prepr-Context-initial_referral",
257
+ sanitizeHeaderValue(referer)
258
+ );
242
259
  }
243
260
  const userAgent = request.headers.get("user-agent");
244
261
  if (userAgent) {
245
- requestHeaders.set("Prepr-User-Agent", userAgent);
262
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
246
263
  }
247
264
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
248
265
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
249
266
  if (ip) {
250
- requestHeaders.set("Prepr-Visitor-IP", ip);
267
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
251
268
  }
252
269
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
253
270
  if (hutkCookie) {
254
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
271
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
255
272
  }
256
273
  let uid = requestCookies.get(COOKIE_UID);
257
274
  if (!uid) {
@@ -291,16 +308,16 @@ function processPreprRequest(request, opts) {
291
308
  if (!isLivePreview) {
292
309
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
293
310
  if (segmentCookie) {
294
- requestHeaders.set("Prepr-Segments", segmentCookie);
311
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
295
312
  }
296
313
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
297
314
  if (abCookie) {
298
- requestHeaders.set("Prepr-ABtesting", abCookie);
315
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
299
316
  }
300
317
  }
301
318
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
302
319
  if (previewAb !== null) {
303
- requestHeaders.set("Prepr-ABtesting", previewAb);
320
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
304
321
  if (!isLivePreview) {
305
322
  responseCookies.push({
306
323
  name: COOKIE_VARIANT,
@@ -313,7 +330,7 @@ function processPreprRequest(request, opts) {
313
330
  }
314
331
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
315
332
  if (previewSegment !== null) {
316
- requestHeaders.set("Prepr-Segments", previewSegment);
333
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
317
334
  if (!isLivePreview) {
318
335
  responseCookies.push({
319
336
  name: COOKIE_SEGMENT,
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.2.0-beta.5";
2
+ var VERSION = "0.2.0-beta.6";
3
3
 
4
4
  // src/core/features.ts
5
5
  function isEnabled(config) {
@@ -162,6 +162,11 @@ var UTM_PARAMS = [
162
162
  "utm_content",
163
163
  "utm_campaign"
164
164
  ];
165
+ var MAX_HEADER_VALUE_LENGTH = 2048;
166
+ function sanitizeHeaderValue(value) {
167
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
168
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
169
+ }
165
170
  function serializeCookie(cookie) {
166
171
  const parts = [
167
172
  `${cookie.name}=${encodeURIComponent(cookie.value)}`,
@@ -193,6 +198,15 @@ function parseCookies(request) {
193
198
  function processPreprRequest(request, opts) {
194
199
  const requestHeaders = new Headers(request.headers);
195
200
  const responseCookies = [];
201
+ const inboundPreprHeaders = [];
202
+ requestHeaders.forEach((_value, key) => {
203
+ if (key.toLowerCase().startsWith("prepr-")) {
204
+ inboundPreprHeaders.push(key);
205
+ }
206
+ });
207
+ for (const key of inboundPreprHeaders) {
208
+ requestHeaders.delete(key);
209
+ }
196
210
  const requestCookies = parseCookies(request);
197
211
  const url = new URL(request.url);
198
212
  const searchParams = url.searchParams;
@@ -202,25 +216,28 @@ function processPreprRequest(request, opts) {
202
216
  for (const key of UTM_PARAMS) {
203
217
  const value = searchParams.get(key);
204
218
  if (value !== null) {
205
- requestHeaders.set(`Prepr-Context-${key}`, value);
219
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
206
220
  }
207
221
  }
208
222
  const referer = request.headers.get("referer");
209
223
  if (referer) {
210
- requestHeaders.set("Prepr-Context-initial_referral", referer);
224
+ requestHeaders.set(
225
+ "Prepr-Context-initial_referral",
226
+ sanitizeHeaderValue(referer)
227
+ );
211
228
  }
212
229
  const userAgent = request.headers.get("user-agent");
213
230
  if (userAgent) {
214
- requestHeaders.set("Prepr-User-Agent", userAgent);
231
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
215
232
  }
216
233
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
217
234
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
218
235
  if (ip) {
219
- requestHeaders.set("Prepr-Visitor-IP", ip);
236
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
220
237
  }
221
238
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
222
239
  if (hutkCookie) {
223
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
240
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
224
241
  }
225
242
  let uid = requestCookies.get(COOKIE_UID);
226
243
  if (!uid) {
@@ -260,16 +277,16 @@ function processPreprRequest(request, opts) {
260
277
  if (!isLivePreview) {
261
278
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
262
279
  if (segmentCookie) {
263
- requestHeaders.set("Prepr-Segments", segmentCookie);
280
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
264
281
  }
265
282
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
266
283
  if (abCookie) {
267
- requestHeaders.set("Prepr-ABtesting", abCookie);
284
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
268
285
  }
269
286
  }
270
287
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
271
288
  if (previewAb !== null) {
272
- requestHeaders.set("Prepr-ABtesting", previewAb);
289
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
273
290
  if (!isLivePreview) {
274
291
  responseCookies.push({
275
292
  name: COOKIE_VARIANT,
@@ -282,7 +299,7 @@ function processPreprRequest(request, opts) {
282
299
  }
283
300
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
284
301
  if (previewSegment !== null) {
285
- requestHeaders.set("Prepr-Segments", previewSegment);
302
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
286
303
  if (!isLivePreview) {
287
304
  responseCookies.push({
288
305
  name: COOKIE_SEGMENT,
@@ -1909,7 +1909,10 @@ function createIframeBridge(store, options = {}) {
1909
1909
  };
1910
1910
  return {
1911
1911
  start() {
1912
- sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
1912
+ const features = store?.get().features;
1913
+ sendPreprEvent("loaded", features ? { features } : void 0, {
1914
+ allowUntrustedTarget: true
1915
+ });
1913
1916
  window.addEventListener("keydown", onKeyDown);
1914
1917
  window.addEventListener("message", onMessage);
1915
1918
  },
@@ -2026,6 +2029,14 @@ function defaultNavigation() {
2026
2029
  currentPath: () => window.location.pathname + window.location.search
2027
2030
  };
2028
2031
  }
2032
+ function safeEditUrl(href) {
2033
+ try {
2034
+ const url = new URL(href, window.location.href);
2035
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
2036
+ } catch {
2037
+ return null;
2038
+ }
2039
+ }
2029
2040
  function buildSegments(data) {
2030
2041
  return [{ _id: "all_other_users", name: "All other users" }, ...data];
2031
2042
  }
@@ -2089,10 +2100,20 @@ function createPreprPreview(opts = {}) {
2089
2100
  // In the editor, ask the parent to focus the field instead of opening a new
2090
2101
  // tab. Standalone previews keep the window.open behaviour.
2091
2102
  onEdit: ({ href, origin, id, field }) => {
2103
+ const safeHref = href ? safeEditUrl(href) : null;
2104
+ if (href && !safeHref) {
2105
+ debug7.warn("ignored edit request with unsupported href scheme");
2106
+ return;
2107
+ }
2092
2108
  if (isIframe) {
2093
- sendPreprEvent("field_edit_requested", { href, origin, id, field });
2094
- } else if (href) {
2095
- window.open(href);
2109
+ sendPreprEvent("field_edit_requested", {
2110
+ href: safeHref ?? void 0,
2111
+ origin,
2112
+ id,
2113
+ field
2114
+ });
2115
+ } else if (safeHref) {
2116
+ window.open(safeHref, "_blank", "noopener,noreferrer");
2096
2117
  }
2097
2118
  }
2098
2119
  });
package/dist/index.cjs CHANGED
@@ -44,7 +44,7 @@ __export(src_exports, {
44
44
  module.exports = __toCommonJS(src_exports);
45
45
 
46
46
  // src/version.ts
47
- var VERSION = "0.2.0-beta.5";
47
+ var VERSION = "0.2.0-beta.6";
48
48
 
49
49
  // src/core/features.ts
50
50
  function isEnabled(config) {
@@ -355,7 +355,10 @@ function createIframeBridge(store, options = {}) {
355
355
  };
356
356
  return {
357
357
  start() {
358
- sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
358
+ const features = store?.get().features;
359
+ sendPreprEvent("loaded", features ? { features } : void 0, {
360
+ allowUntrustedTarget: true
361
+ });
359
362
  window.addEventListener("keydown", onKeyDown);
360
363
  window.addEventListener("message", onMessage);
361
364
  },
@@ -2074,6 +2077,14 @@ function defaultNavigation() {
2074
2077
  currentPath: () => window.location.pathname + window.location.search
2075
2078
  };
2076
2079
  }
2080
+ function safeEditUrl(href) {
2081
+ try {
2082
+ const url = new URL(href, window.location.href);
2083
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
2084
+ } catch {
2085
+ return null;
2086
+ }
2087
+ }
2077
2088
  function buildSegments(data) {
2078
2089
  return [{ _id: "all_other_users", name: "All other users" }, ...data];
2079
2090
  }
@@ -2137,10 +2148,20 @@ function createPreprPreview(opts = {}) {
2137
2148
  // In the editor, ask the parent to focus the field instead of opening a new
2138
2149
  // tab. Standalone previews keep the window.open behaviour.
2139
2150
  onEdit: ({ href, origin, id, field }) => {
2151
+ const safeHref = href ? safeEditUrl(href) : null;
2152
+ if (href && !safeHref) {
2153
+ debug7.warn("ignored edit request with unsupported href scheme");
2154
+ return;
2155
+ }
2140
2156
  if (isIframe) {
2141
- sendPreprEvent("field_edit_requested", { href, origin, id, field });
2142
- } else if (href) {
2143
- window.open(href);
2157
+ sendPreprEvent("field_edit_requested", {
2158
+ href: safeHref ?? void 0,
2159
+ origin,
2160
+ id,
2161
+ field
2162
+ });
2163
+ } else if (safeHref) {
2164
+ window.open(safeHref, "_blank", "noopener,noreferrer");
2144
2165
  }
2145
2166
  }
2146
2167
  });
@@ -2358,6 +2379,11 @@ var UTM_PARAMS = [
2358
2379
  "utm_content",
2359
2380
  "utm_campaign"
2360
2381
  ];
2382
+ var MAX_HEADER_VALUE_LENGTH = 2048;
2383
+ function sanitizeHeaderValue(value) {
2384
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
2385
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
2386
+ }
2361
2387
  function parseCookies(request) {
2362
2388
  const cookies = /* @__PURE__ */ new Map();
2363
2389
  const header = request.headers.get("cookie");
@@ -2375,6 +2401,15 @@ function parseCookies(request) {
2375
2401
  function processPreprRequest(request, opts) {
2376
2402
  const requestHeaders = new Headers(request.headers);
2377
2403
  const responseCookies = [];
2404
+ const inboundPreprHeaders = [];
2405
+ requestHeaders.forEach((_value, key) => {
2406
+ if (key.toLowerCase().startsWith("prepr-")) {
2407
+ inboundPreprHeaders.push(key);
2408
+ }
2409
+ });
2410
+ for (const key of inboundPreprHeaders) {
2411
+ requestHeaders.delete(key);
2412
+ }
2378
2413
  const requestCookies = parseCookies(request);
2379
2414
  const url = new URL(request.url);
2380
2415
  const searchParams = url.searchParams;
@@ -2384,25 +2419,28 @@ function processPreprRequest(request, opts) {
2384
2419
  for (const key of UTM_PARAMS) {
2385
2420
  const value = searchParams.get(key);
2386
2421
  if (value !== null) {
2387
- requestHeaders.set(`Prepr-Context-${key}`, value);
2422
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
2388
2423
  }
2389
2424
  }
2390
2425
  const referer = request.headers.get("referer");
2391
2426
  if (referer) {
2392
- requestHeaders.set("Prepr-Context-initial_referral", referer);
2427
+ requestHeaders.set(
2428
+ "Prepr-Context-initial_referral",
2429
+ sanitizeHeaderValue(referer)
2430
+ );
2393
2431
  }
2394
2432
  const userAgent = request.headers.get("user-agent");
2395
2433
  if (userAgent) {
2396
- requestHeaders.set("Prepr-User-Agent", userAgent);
2434
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
2397
2435
  }
2398
2436
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
2399
2437
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
2400
2438
  if (ip) {
2401
- requestHeaders.set("Prepr-Visitor-IP", ip);
2439
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
2402
2440
  }
2403
2441
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
2404
2442
  if (hutkCookie) {
2405
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
2443
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
2406
2444
  }
2407
2445
  let uid = requestCookies.get(COOKIE_UID);
2408
2446
  if (!uid) {
@@ -2442,16 +2480,16 @@ function processPreprRequest(request, opts) {
2442
2480
  if (!isLivePreview) {
2443
2481
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
2444
2482
  if (segmentCookie) {
2445
- requestHeaders.set("Prepr-Segments", segmentCookie);
2483
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
2446
2484
  }
2447
2485
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
2448
2486
  if (abCookie) {
2449
- requestHeaders.set("Prepr-ABtesting", abCookie);
2487
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
2450
2488
  }
2451
2489
  }
2452
2490
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
2453
2491
  if (previewAb !== null) {
2454
- requestHeaders.set("Prepr-ABtesting", previewAb);
2492
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
2455
2493
  if (!isLivePreview) {
2456
2494
  responseCookies.push({
2457
2495
  name: COOKIE_VARIANT,
@@ -2464,7 +2502,7 @@ function processPreprRequest(request, opts) {
2464
2502
  }
2465
2503
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
2466
2504
  if (previewSegment !== null) {
2467
- requestHeaders.set("Prepr-Segments", previewSegment);
2505
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
2468
2506
  if (!isLivePreview) {
2469
2507
  responseCookies.push({
2470
2508
  name: COOKIE_SEGMENT,
package/dist/index.d.cts CHANGED
@@ -5,7 +5,7 @@ export { g as PreprEnvironment, h as PreprEventType, i as PreprFeatureConfig, j
5
5
  * Package version; must match package.json. Lives in its own leaf module so
6
6
  * core can import it without cycling through the barrel.
7
7
  */
8
- declare const VERSION = "0.2.0-beta.5";
8
+ declare const VERSION = "0.2.0-beta.6";
9
9
 
10
10
  /**
11
11
  * Resolve a `PreprFeatures` bag to plain booleans, defaulting every omitted key
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { g as PreprEnvironment, h as PreprEventType, i as PreprFeatureConfig, j
5
5
  * Package version; must match package.json. Lives in its own leaf module so
6
6
  * core can import it without cycling through the barrel.
7
7
  */
8
- declare const VERSION = "0.2.0-beta.5";
8
+ declare const VERSION = "0.2.0-beta.6";
9
9
 
10
10
  /**
11
11
  * Resolve a `PreprFeatures` bag to plain booleans, defaulting every omitted key
package/dist/index.js CHANGED
@@ -17,10 +17,10 @@ import {
17
17
  setTrackingParam,
18
18
  stegaClean,
19
19
  trackEvent
20
- } from "./chunk-BYJ2JBGX.js";
20
+ } from "./chunk-C6YEXA5G.js";
21
21
 
22
22
  // src/version.ts
23
- var VERSION = "0.2.0-beta.5";
23
+ var VERSION = "0.2.0-beta.6";
24
24
 
25
25
  // src/core/server.ts
26
26
  var PreprError = class extends Error {
@@ -184,6 +184,11 @@ var UTM_PARAMS = [
184
184
  "utm_content",
185
185
  "utm_campaign"
186
186
  ];
187
+ var MAX_HEADER_VALUE_LENGTH = 2048;
188
+ function sanitizeHeaderValue(value) {
189
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
190
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
191
+ }
187
192
  function parseCookies(request) {
188
193
  const cookies = /* @__PURE__ */ new Map();
189
194
  const header = request.headers.get("cookie");
@@ -201,6 +206,15 @@ function parseCookies(request) {
201
206
  function processPreprRequest(request, opts) {
202
207
  const requestHeaders = new Headers(request.headers);
203
208
  const responseCookies = [];
209
+ const inboundPreprHeaders = [];
210
+ requestHeaders.forEach((_value, key) => {
211
+ if (key.toLowerCase().startsWith("prepr-")) {
212
+ inboundPreprHeaders.push(key);
213
+ }
214
+ });
215
+ for (const key of inboundPreprHeaders) {
216
+ requestHeaders.delete(key);
217
+ }
204
218
  const requestCookies = parseCookies(request);
205
219
  const url = new URL(request.url);
206
220
  const searchParams = url.searchParams;
@@ -210,25 +224,28 @@ function processPreprRequest(request, opts) {
210
224
  for (const key of UTM_PARAMS) {
211
225
  const value = searchParams.get(key);
212
226
  if (value !== null) {
213
- requestHeaders.set(`Prepr-Context-${key}`, value);
227
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
214
228
  }
215
229
  }
216
230
  const referer = request.headers.get("referer");
217
231
  if (referer) {
218
- requestHeaders.set("Prepr-Context-initial_referral", referer);
232
+ requestHeaders.set(
233
+ "Prepr-Context-initial_referral",
234
+ sanitizeHeaderValue(referer)
235
+ );
219
236
  }
220
237
  const userAgent = request.headers.get("user-agent");
221
238
  if (userAgent) {
222
- requestHeaders.set("Prepr-User-Agent", userAgent);
239
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
223
240
  }
224
241
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
225
242
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
226
243
  if (ip) {
227
- requestHeaders.set("Prepr-Visitor-IP", ip);
244
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
228
245
  }
229
246
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
230
247
  if (hutkCookie) {
231
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
248
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
232
249
  }
233
250
  let uid = requestCookies.get(COOKIE_UID);
234
251
  if (!uid) {
@@ -268,16 +285,16 @@ function processPreprRequest(request, opts) {
268
285
  if (!isLivePreview) {
269
286
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
270
287
  if (segmentCookie) {
271
- requestHeaders.set("Prepr-Segments", segmentCookie);
288
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
272
289
  }
273
290
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
274
291
  if (abCookie) {
275
- requestHeaders.set("Prepr-ABtesting", abCookie);
292
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
276
293
  }
277
294
  }
278
295
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
279
296
  if (previewAb !== null) {
280
- requestHeaders.set("Prepr-ABtesting", previewAb);
297
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
281
298
  if (!isLivePreview) {
282
299
  responseCookies.push({
283
300
  name: COOKIE_VARIANT,
@@ -290,7 +307,7 @@ function processPreprRequest(request, opts) {
290
307
  }
291
308
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
292
309
  if (previewSegment !== null) {
293
- requestHeaders.set("Prepr-Segments", previewSegment);
310
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
294
311
  if (!isLivePreview) {
295
312
  responseCookies.push({
296
313
  name: COOKIE_SEGMENT,
@@ -335,7 +335,10 @@ function createIframeBridge(store, options = {}) {
335
335
  };
336
336
  return {
337
337
  start() {
338
- sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
338
+ const features = store?.get().features;
339
+ sendPreprEvent("loaded", features ? { features } : void 0, {
340
+ allowUntrustedTarget: true
341
+ });
339
342
  window.addEventListener("keydown", onKeyDown);
340
343
  window.addEventListener("message", onMessage);
341
344
  },
@@ -2050,6 +2053,14 @@ function defaultNavigation() {
2050
2053
  currentPath: () => window.location.pathname + window.location.search
2051
2054
  };
2052
2055
  }
2056
+ function safeEditUrl(href) {
2057
+ try {
2058
+ const url = new URL(href, window.location.href);
2059
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
2060
+ } catch {
2061
+ return null;
2062
+ }
2063
+ }
2053
2064
  function buildSegments(data) {
2054
2065
  return [{ _id: "all_other_users", name: "All other users" }, ...data];
2055
2066
  }
@@ -2113,10 +2124,20 @@ function createPreprPreview(opts = {}) {
2113
2124
  // In the editor, ask the parent to focus the field instead of opening a new
2114
2125
  // tab. Standalone previews keep the window.open behaviour.
2115
2126
  onEdit: ({ href, origin, id, field }) => {
2127
+ const safeHref = href ? safeEditUrl(href) : null;
2128
+ if (href && !safeHref) {
2129
+ debug7.warn("ignored edit request with unsupported href scheme");
2130
+ return;
2131
+ }
2116
2132
  if (isIframe) {
2117
- sendPreprEvent("field_edit_requested", { href, origin, id, field });
2118
- } else if (href) {
2119
- window.open(href);
2133
+ sendPreprEvent("field_edit_requested", {
2134
+ href: safeHref ?? void 0,
2135
+ origin,
2136
+ id,
2137
+ field
2138
+ });
2139
+ } else if (safeHref) {
2140
+ window.open(safeHref, "_blank", "noopener,noreferrer");
2120
2141
  }
2121
2142
  }
2122
2143
  });
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  createPreprPreview,
4
4
  loadTrackingPixel
5
- } from "../chunk-BYJ2JBGX.js";
5
+ } from "../chunk-C6YEXA5G.js";
6
6
 
7
7
  // src/nextjs/components.ts
8
8
  import { usePathname, useRouter } from "next/navigation";
@@ -38,7 +38,7 @@ module.exports = __toCommonJS(nextjs_exports);
38
38
  var import_server = require("next/server");
39
39
 
40
40
  // src/version.ts
41
- var VERSION = "0.2.0-beta.5";
41
+ var VERSION = "0.2.0-beta.6";
42
42
 
43
43
  // src/core/constants.ts
44
44
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -77,6 +77,11 @@ var UTM_PARAMS = [
77
77
  "utm_content",
78
78
  "utm_campaign"
79
79
  ];
80
+ var MAX_HEADER_VALUE_LENGTH = 2048;
81
+ function sanitizeHeaderValue(value) {
82
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
83
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
84
+ }
80
85
  function parseCookies(request) {
81
86
  const cookies = /* @__PURE__ */ new Map();
82
87
  const header = request.headers.get("cookie");
@@ -94,6 +99,15 @@ function parseCookies(request) {
94
99
  function processPreprRequest(request, opts) {
95
100
  const requestHeaders = new Headers(request.headers);
96
101
  const responseCookies = [];
102
+ const inboundPreprHeaders = [];
103
+ requestHeaders.forEach((_value, key) => {
104
+ if (key.toLowerCase().startsWith("prepr-")) {
105
+ inboundPreprHeaders.push(key);
106
+ }
107
+ });
108
+ for (const key of inboundPreprHeaders) {
109
+ requestHeaders.delete(key);
110
+ }
97
111
  const requestCookies = parseCookies(request);
98
112
  const url = new URL(request.url);
99
113
  const searchParams = url.searchParams;
@@ -103,25 +117,28 @@ function processPreprRequest(request, opts) {
103
117
  for (const key of UTM_PARAMS) {
104
118
  const value = searchParams.get(key);
105
119
  if (value !== null) {
106
- requestHeaders.set(`Prepr-Context-${key}`, value);
120
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
107
121
  }
108
122
  }
109
123
  const referer = request.headers.get("referer");
110
124
  if (referer) {
111
- requestHeaders.set("Prepr-Context-initial_referral", referer);
125
+ requestHeaders.set(
126
+ "Prepr-Context-initial_referral",
127
+ sanitizeHeaderValue(referer)
128
+ );
112
129
  }
113
130
  const userAgent = request.headers.get("user-agent");
114
131
  if (userAgent) {
115
- requestHeaders.set("Prepr-User-Agent", userAgent);
132
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
116
133
  }
117
134
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
118
135
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
119
136
  if (ip) {
120
- requestHeaders.set("Prepr-Visitor-IP", ip);
137
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
121
138
  }
122
139
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
123
140
  if (hutkCookie) {
124
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
141
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
125
142
  }
126
143
  let uid = requestCookies.get(COOKIE_UID);
127
144
  if (!uid) {
@@ -161,16 +178,16 @@ function processPreprRequest(request, opts) {
161
178
  if (!isLivePreview) {
162
179
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
163
180
  if (segmentCookie) {
164
- requestHeaders.set("Prepr-Segments", segmentCookie);
181
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
165
182
  }
166
183
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
167
184
  if (abCookie) {
168
- requestHeaders.set("Prepr-ABtesting", abCookie);
185
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
169
186
  }
170
187
  }
171
188
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
172
189
  if (previewAb !== null) {
173
- requestHeaders.set("Prepr-ABtesting", previewAb);
190
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
174
191
  if (!isLivePreview) {
175
192
  responseCookies.push({
176
193
  name: COOKIE_VARIANT,
@@ -183,7 +200,7 @@ function processPreprRequest(request, opts) {
183
200
  }
184
201
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
185
202
  if (previewSegment !== null) {
186
- requestHeaders.set("Prepr-Segments", previewSegment);
203
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
187
204
  if (!isLivePreview) {
188
205
  responseCookies.push({
189
206
  name: COOKIE_SEGMENT,
@@ -2,7 +2,7 @@
2
2
  import { NextResponse } from "next/server";
3
3
 
4
4
  // src/version.ts
5
- var VERSION = "0.2.0-beta.5";
5
+ var VERSION = "0.2.0-beta.6";
6
6
 
7
7
  // src/core/constants.ts
8
8
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -41,6 +41,11 @@ var UTM_PARAMS = [
41
41
  "utm_content",
42
42
  "utm_campaign"
43
43
  ];
44
+ var MAX_HEADER_VALUE_LENGTH = 2048;
45
+ function sanitizeHeaderValue(value) {
46
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
47
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
48
+ }
44
49
  function parseCookies(request) {
45
50
  const cookies = /* @__PURE__ */ new Map();
46
51
  const header = request.headers.get("cookie");
@@ -58,6 +63,15 @@ function parseCookies(request) {
58
63
  function processPreprRequest(request, opts) {
59
64
  const requestHeaders = new Headers(request.headers);
60
65
  const responseCookies = [];
66
+ const inboundPreprHeaders = [];
67
+ requestHeaders.forEach((_value, key) => {
68
+ if (key.toLowerCase().startsWith("prepr-")) {
69
+ inboundPreprHeaders.push(key);
70
+ }
71
+ });
72
+ for (const key of inboundPreprHeaders) {
73
+ requestHeaders.delete(key);
74
+ }
61
75
  const requestCookies = parseCookies(request);
62
76
  const url = new URL(request.url);
63
77
  const searchParams = url.searchParams;
@@ -67,25 +81,28 @@ function processPreprRequest(request, opts) {
67
81
  for (const key of UTM_PARAMS) {
68
82
  const value = searchParams.get(key);
69
83
  if (value !== null) {
70
- requestHeaders.set(`Prepr-Context-${key}`, value);
84
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
71
85
  }
72
86
  }
73
87
  const referer = request.headers.get("referer");
74
88
  if (referer) {
75
- requestHeaders.set("Prepr-Context-initial_referral", referer);
89
+ requestHeaders.set(
90
+ "Prepr-Context-initial_referral",
91
+ sanitizeHeaderValue(referer)
92
+ );
76
93
  }
77
94
  const userAgent = request.headers.get("user-agent");
78
95
  if (userAgent) {
79
- requestHeaders.set("Prepr-User-Agent", userAgent);
96
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
80
97
  }
81
98
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
82
99
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
83
100
  if (ip) {
84
- requestHeaders.set("Prepr-Visitor-IP", ip);
101
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
85
102
  }
86
103
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
87
104
  if (hutkCookie) {
88
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
105
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
89
106
  }
90
107
  let uid = requestCookies.get(COOKIE_UID);
91
108
  if (!uid) {
@@ -125,16 +142,16 @@ function processPreprRequest(request, opts) {
125
142
  if (!isLivePreview) {
126
143
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
127
144
  if (segmentCookie) {
128
- requestHeaders.set("Prepr-Segments", segmentCookie);
145
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
129
146
  }
130
147
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
131
148
  if (abCookie) {
132
- requestHeaders.set("Prepr-ABtesting", abCookie);
149
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
133
150
  }
134
151
  }
135
152
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
136
153
  if (previewAb !== null) {
137
- requestHeaders.set("Prepr-ABtesting", previewAb);
154
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
138
155
  if (!isLivePreview) {
139
156
  responseCookies.push({
140
157
  name: COOKIE_VARIANT,
@@ -147,7 +164,7 @@ function processPreprRequest(request, opts) {
147
164
  }
148
165
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
149
166
  if (previewSegment !== null) {
150
- requestHeaders.set("Prepr-Segments", previewSegment);
167
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
151
168
  if (!isLivePreview) {
152
169
  responseCookies.push({
153
170
  name: COOKIE_SEGMENT,
@@ -35,7 +35,7 @@ __export(nuxt_exports, {
35
35
  module.exports = __toCommonJS(nuxt_exports);
36
36
 
37
37
  // src/version.ts
38
- var VERSION = "0.2.0-beta.5";
38
+ var VERSION = "0.2.0-beta.6";
39
39
 
40
40
  // src/core/constants.ts
41
41
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -74,6 +74,11 @@ var UTM_PARAMS = [
74
74
  "utm_content",
75
75
  "utm_campaign"
76
76
  ];
77
+ var MAX_HEADER_VALUE_LENGTH = 2048;
78
+ function sanitizeHeaderValue(value) {
79
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
80
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
81
+ }
77
82
  function serializeCookie(cookie) {
78
83
  const parts = [
79
84
  `${cookie.name}=${encodeURIComponent(cookie.value)}`,
@@ -105,6 +110,15 @@ function parseCookies(request) {
105
110
  function processPreprRequest(request, opts) {
106
111
  const requestHeaders = new Headers(request.headers);
107
112
  const responseCookies = [];
113
+ const inboundPreprHeaders = [];
114
+ requestHeaders.forEach((_value, key) => {
115
+ if (key.toLowerCase().startsWith("prepr-")) {
116
+ inboundPreprHeaders.push(key);
117
+ }
118
+ });
119
+ for (const key of inboundPreprHeaders) {
120
+ requestHeaders.delete(key);
121
+ }
108
122
  const requestCookies = parseCookies(request);
109
123
  const url = new URL(request.url);
110
124
  const searchParams = url.searchParams;
@@ -114,25 +128,28 @@ function processPreprRequest(request, opts) {
114
128
  for (const key of UTM_PARAMS) {
115
129
  const value = searchParams.get(key);
116
130
  if (value !== null) {
117
- requestHeaders.set(`Prepr-Context-${key}`, value);
131
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
118
132
  }
119
133
  }
120
134
  const referer = request.headers.get("referer");
121
135
  if (referer) {
122
- requestHeaders.set("Prepr-Context-initial_referral", referer);
136
+ requestHeaders.set(
137
+ "Prepr-Context-initial_referral",
138
+ sanitizeHeaderValue(referer)
139
+ );
123
140
  }
124
141
  const userAgent = request.headers.get("user-agent");
125
142
  if (userAgent) {
126
- requestHeaders.set("Prepr-User-Agent", userAgent);
143
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
127
144
  }
128
145
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
129
146
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
130
147
  if (ip) {
131
- requestHeaders.set("Prepr-Visitor-IP", ip);
148
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
132
149
  }
133
150
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
134
151
  if (hutkCookie) {
135
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
152
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
136
153
  }
137
154
  let uid = requestCookies.get(COOKIE_UID);
138
155
  if (!uid) {
@@ -172,16 +189,16 @@ function processPreprRequest(request, opts) {
172
189
  if (!isLivePreview) {
173
190
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
174
191
  if (segmentCookie) {
175
- requestHeaders.set("Prepr-Segments", segmentCookie);
192
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
176
193
  }
177
194
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
178
195
  if (abCookie) {
179
- requestHeaders.set("Prepr-ABtesting", abCookie);
196
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
180
197
  }
181
198
  }
182
199
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
183
200
  if (previewAb !== null) {
184
- requestHeaders.set("Prepr-ABtesting", previewAb);
201
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
185
202
  if (!isLivePreview) {
186
203
  responseCookies.push({
187
204
  name: COOKIE_VARIANT,
@@ -194,7 +211,7 @@ function processPreprRequest(request, opts) {
194
211
  }
195
212
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
196
213
  if (previewSegment !== null) {
197
- requestHeaders.set("Prepr-Segments", previewSegment);
214
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
198
215
  if (!isLivePreview) {
199
216
  responseCookies.push({
200
217
  name: COOKIE_SEGMENT,
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.2.0-beta.5";
2
+ var VERSION = "0.2.0-beta.6";
3
3
 
4
4
  // src/core/constants.ts
5
5
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -38,6 +38,11 @@ var UTM_PARAMS = [
38
38
  "utm_content",
39
39
  "utm_campaign"
40
40
  ];
41
+ var MAX_HEADER_VALUE_LENGTH = 2048;
42
+ function sanitizeHeaderValue(value) {
43
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
44
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
45
+ }
41
46
  function serializeCookie(cookie) {
42
47
  const parts = [
43
48
  `${cookie.name}=${encodeURIComponent(cookie.value)}`,
@@ -69,6 +74,15 @@ function parseCookies(request) {
69
74
  function processPreprRequest(request, opts) {
70
75
  const requestHeaders = new Headers(request.headers);
71
76
  const responseCookies = [];
77
+ const inboundPreprHeaders = [];
78
+ requestHeaders.forEach((_value, key) => {
79
+ if (key.toLowerCase().startsWith("prepr-")) {
80
+ inboundPreprHeaders.push(key);
81
+ }
82
+ });
83
+ for (const key of inboundPreprHeaders) {
84
+ requestHeaders.delete(key);
85
+ }
72
86
  const requestCookies = parseCookies(request);
73
87
  const url = new URL(request.url);
74
88
  const searchParams = url.searchParams;
@@ -78,25 +92,28 @@ function processPreprRequest(request, opts) {
78
92
  for (const key of UTM_PARAMS) {
79
93
  const value = searchParams.get(key);
80
94
  if (value !== null) {
81
- requestHeaders.set(`Prepr-Context-${key}`, value);
95
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
82
96
  }
83
97
  }
84
98
  const referer = request.headers.get("referer");
85
99
  if (referer) {
86
- requestHeaders.set("Prepr-Context-initial_referral", referer);
100
+ requestHeaders.set(
101
+ "Prepr-Context-initial_referral",
102
+ sanitizeHeaderValue(referer)
103
+ );
87
104
  }
88
105
  const userAgent = request.headers.get("user-agent");
89
106
  if (userAgent) {
90
- requestHeaders.set("Prepr-User-Agent", userAgent);
107
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
91
108
  }
92
109
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
93
110
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
94
111
  if (ip) {
95
- requestHeaders.set("Prepr-Visitor-IP", ip);
112
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
96
113
  }
97
114
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
98
115
  if (hutkCookie) {
99
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
116
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
100
117
  }
101
118
  let uid = requestCookies.get(COOKIE_UID);
102
119
  if (!uid) {
@@ -136,16 +153,16 @@ function processPreprRequest(request, opts) {
136
153
  if (!isLivePreview) {
137
154
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
138
155
  if (segmentCookie) {
139
- requestHeaders.set("Prepr-Segments", segmentCookie);
156
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
140
157
  }
141
158
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
142
159
  if (abCookie) {
143
- requestHeaders.set("Prepr-ABtesting", abCookie);
160
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
144
161
  }
145
162
  }
146
163
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
147
164
  if (previewAb !== null) {
148
- requestHeaders.set("Prepr-ABtesting", previewAb);
165
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
149
166
  if (!isLivePreview) {
150
167
  responseCookies.push({
151
168
  name: COOKIE_VARIANT,
@@ -158,7 +175,7 @@ function processPreprRequest(request, opts) {
158
175
  }
159
176
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
160
177
  if (previewSegment !== null) {
161
- requestHeaders.set("Prepr-Segments", previewSegment);
178
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
162
179
  if (!isLivePreview) {
163
180
  responseCookies.push({
164
181
  name: COOKIE_SEGMENT,
@@ -334,7 +334,10 @@ function createIframeBridge(store, options = {}) {
334
334
  };
335
335
  return {
336
336
  start() {
337
- sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
337
+ const features = store?.get().features;
338
+ sendPreprEvent("loaded", features ? { features } : void 0, {
339
+ allowUntrustedTarget: true
340
+ });
338
341
  window.addEventListener("keydown", onKeyDown);
339
342
  window.addEventListener("message", onMessage);
340
343
  },
@@ -2049,6 +2052,14 @@ function defaultNavigation() {
2049
2052
  currentPath: () => window.location.pathname + window.location.search
2050
2053
  };
2051
2054
  }
2055
+ function safeEditUrl(href) {
2056
+ try {
2057
+ const url = new URL(href, window.location.href);
2058
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
2059
+ } catch {
2060
+ return null;
2061
+ }
2062
+ }
2052
2063
  function buildSegments(data) {
2053
2064
  return [{ _id: "all_other_users", name: "All other users" }, ...data];
2054
2065
  }
@@ -2112,10 +2123,20 @@ function createPreprPreview(opts = {}) {
2112
2123
  // In the editor, ask the parent to focus the field instead of opening a new
2113
2124
  // tab. Standalone previews keep the window.open behaviour.
2114
2125
  onEdit: ({ href, origin, id, field }) => {
2126
+ const safeHref = href ? safeEditUrl(href) : null;
2127
+ if (href && !safeHref) {
2128
+ debug7.warn("ignored edit request with unsupported href scheme");
2129
+ return;
2130
+ }
2115
2131
  if (isIframe) {
2116
- sendPreprEvent("field_edit_requested", { href, origin, id, field });
2117
- } else if (href) {
2118
- window.open(href);
2132
+ sendPreprEvent("field_edit_requested", {
2133
+ href: safeHref ?? void 0,
2134
+ origin,
2135
+ id,
2136
+ field
2137
+ });
2138
+ } else if (safeHref) {
2139
+ window.open(safeHref, "_blank", "noopener,noreferrer");
2119
2140
  }
2120
2141
  }
2121
2142
  });
@@ -310,7 +310,10 @@ function createIframeBridge(store, options = {}) {
310
310
  };
311
311
  return {
312
312
  start() {
313
- sendPreprEvent("loaded", void 0, { allowUntrustedTarget: true });
313
+ const features = store?.get().features;
314
+ sendPreprEvent("loaded", features ? { features } : void 0, {
315
+ allowUntrustedTarget: true
316
+ });
314
317
  window.addEventListener("keydown", onKeyDown);
315
318
  window.addEventListener("message", onMessage);
316
319
  },
@@ -2025,6 +2028,14 @@ function defaultNavigation() {
2025
2028
  currentPath: () => window.location.pathname + window.location.search
2026
2029
  };
2027
2030
  }
2031
+ function safeEditUrl(href) {
2032
+ try {
2033
+ const url = new URL(href, window.location.href);
2034
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
2035
+ } catch {
2036
+ return null;
2037
+ }
2038
+ }
2028
2039
  function buildSegments(data) {
2029
2040
  return [{ _id: "all_other_users", name: "All other users" }, ...data];
2030
2041
  }
@@ -2088,10 +2099,20 @@ function createPreprPreview(opts = {}) {
2088
2099
  // In the editor, ask the parent to focus the field instead of opening a new
2089
2100
  // tab. Standalone previews keep the window.open behaviour.
2090
2101
  onEdit: ({ href, origin, id, field }) => {
2102
+ const safeHref = href ? safeEditUrl(href) : null;
2103
+ if (href && !safeHref) {
2104
+ debug7.warn("ignored edit request with unsupported href scheme");
2105
+ return;
2106
+ }
2091
2107
  if (isIframe) {
2092
- sendPreprEvent("field_edit_requested", { href, origin, id, field });
2093
- } else if (href) {
2094
- window.open(href);
2108
+ sendPreprEvent("field_edit_requested", {
2109
+ href: safeHref ?? void 0,
2110
+ origin,
2111
+ id,
2112
+ field
2113
+ });
2114
+ } else if (safeHref) {
2115
+ window.open(safeHref, "_blank", "noopener,noreferrer");
2095
2116
  }
2096
2117
  }
2097
2118
  });
@@ -35,7 +35,7 @@ __export(sveltekit_exports, {
35
35
  module.exports = __toCommonJS(sveltekit_exports);
36
36
 
37
37
  // src/version.ts
38
- var VERSION = "0.2.0-beta.5";
38
+ var VERSION = "0.2.0-beta.6";
39
39
 
40
40
  // src/core/constants.ts
41
41
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -74,6 +74,11 @@ var UTM_PARAMS = [
74
74
  "utm_content",
75
75
  "utm_campaign"
76
76
  ];
77
+ var MAX_HEADER_VALUE_LENGTH = 2048;
78
+ function sanitizeHeaderValue(value) {
79
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
80
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
81
+ }
77
82
  function serializeCookie(cookie) {
78
83
  const parts = [
79
84
  `${cookie.name}=${encodeURIComponent(cookie.value)}`,
@@ -105,6 +110,15 @@ function parseCookies(request) {
105
110
  function processPreprRequest(request, opts) {
106
111
  const requestHeaders = new Headers(request.headers);
107
112
  const responseCookies = [];
113
+ const inboundPreprHeaders = [];
114
+ requestHeaders.forEach((_value, key) => {
115
+ if (key.toLowerCase().startsWith("prepr-")) {
116
+ inboundPreprHeaders.push(key);
117
+ }
118
+ });
119
+ for (const key of inboundPreprHeaders) {
120
+ requestHeaders.delete(key);
121
+ }
108
122
  const requestCookies = parseCookies(request);
109
123
  const url = new URL(request.url);
110
124
  const searchParams = url.searchParams;
@@ -114,25 +128,28 @@ function processPreprRequest(request, opts) {
114
128
  for (const key of UTM_PARAMS) {
115
129
  const value = searchParams.get(key);
116
130
  if (value !== null) {
117
- requestHeaders.set(`Prepr-Context-${key}`, value);
131
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
118
132
  }
119
133
  }
120
134
  const referer = request.headers.get("referer");
121
135
  if (referer) {
122
- requestHeaders.set("Prepr-Context-initial_referral", referer);
136
+ requestHeaders.set(
137
+ "Prepr-Context-initial_referral",
138
+ sanitizeHeaderValue(referer)
139
+ );
123
140
  }
124
141
  const userAgent = request.headers.get("user-agent");
125
142
  if (userAgent) {
126
- requestHeaders.set("Prepr-User-Agent", userAgent);
143
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
127
144
  }
128
145
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
129
146
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
130
147
  if (ip) {
131
- requestHeaders.set("Prepr-Visitor-IP", ip);
148
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
132
149
  }
133
150
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
134
151
  if (hutkCookie) {
135
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
152
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
136
153
  }
137
154
  let uid = requestCookies.get(COOKIE_UID);
138
155
  if (!uid) {
@@ -172,16 +189,16 @@ function processPreprRequest(request, opts) {
172
189
  if (!isLivePreview) {
173
190
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
174
191
  if (segmentCookie) {
175
- requestHeaders.set("Prepr-Segments", segmentCookie);
192
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
176
193
  }
177
194
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
178
195
  if (abCookie) {
179
- requestHeaders.set("Prepr-ABtesting", abCookie);
196
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
180
197
  }
181
198
  }
182
199
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
183
200
  if (previewAb !== null) {
184
- requestHeaders.set("Prepr-ABtesting", previewAb);
201
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
185
202
  if (!isLivePreview) {
186
203
  responseCookies.push({
187
204
  name: COOKIE_VARIANT,
@@ -194,7 +211,7 @@ function processPreprRequest(request, opts) {
194
211
  }
195
212
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
196
213
  if (previewSegment !== null) {
197
- requestHeaders.set("Prepr-Segments", previewSegment);
214
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
198
215
  if (!isLivePreview) {
199
216
  responseCookies.push({
200
217
  name: COOKIE_SEGMENT,
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.2.0-beta.5";
2
+ var VERSION = "0.2.0-beta.6";
3
3
 
4
4
  // src/core/constants.ts
5
5
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -38,6 +38,11 @@ var UTM_PARAMS = [
38
38
  "utm_content",
39
39
  "utm_campaign"
40
40
  ];
41
+ var MAX_HEADER_VALUE_LENGTH = 2048;
42
+ function sanitizeHeaderValue(value) {
43
+ const stripped = value.replace(/[\u0000-\u001f\u007f]/g, "");
44
+ return stripped.length > MAX_HEADER_VALUE_LENGTH ? stripped.slice(0, MAX_HEADER_VALUE_LENGTH) : stripped;
45
+ }
41
46
  function serializeCookie(cookie) {
42
47
  const parts = [
43
48
  `${cookie.name}=${encodeURIComponent(cookie.value)}`,
@@ -69,6 +74,15 @@ function parseCookies(request) {
69
74
  function processPreprRequest(request, opts) {
70
75
  const requestHeaders = new Headers(request.headers);
71
76
  const responseCookies = [];
77
+ const inboundPreprHeaders = [];
78
+ requestHeaders.forEach((_value, key) => {
79
+ if (key.toLowerCase().startsWith("prepr-")) {
80
+ inboundPreprHeaders.push(key);
81
+ }
82
+ });
83
+ for (const key of inboundPreprHeaders) {
84
+ requestHeaders.delete(key);
85
+ }
72
86
  const requestCookies = parseCookies(request);
73
87
  const url = new URL(request.url);
74
88
  const searchParams = url.searchParams;
@@ -78,25 +92,28 @@ function processPreprRequest(request, opts) {
78
92
  for (const key of UTM_PARAMS) {
79
93
  const value = searchParams.get(key);
80
94
  if (value !== null) {
81
- requestHeaders.set(`Prepr-Context-${key}`, value);
95
+ requestHeaders.set(`Prepr-Context-${key}`, sanitizeHeaderValue(value));
82
96
  }
83
97
  }
84
98
  const referer = request.headers.get("referer");
85
99
  if (referer) {
86
- requestHeaders.set("Prepr-Context-initial_referral", referer);
100
+ requestHeaders.set(
101
+ "Prepr-Context-initial_referral",
102
+ sanitizeHeaderValue(referer)
103
+ );
87
104
  }
88
105
  const userAgent = request.headers.get("user-agent");
89
106
  if (userAgent) {
90
- requestHeaders.set("Prepr-User-Agent", userAgent);
107
+ requestHeaders.set("Prepr-User-Agent", sanitizeHeaderValue(userAgent));
91
108
  }
92
109
  requestHeaders.set("Prepr-Package", `@preprio/toolkit@${opts?.version ?? VERSION}`);
93
110
  const ip = request.headers.get("Cf-Connecting-Ip") ?? request.headers.get("x-real-ip");
94
111
  if (ip) {
95
- requestHeaders.set("Prepr-Visitor-IP", ip);
112
+ requestHeaders.set("Prepr-Visitor-IP", sanitizeHeaderValue(ip));
96
113
  }
97
114
  const hutkCookie = requestCookies.get(COOKIE_HUBSPOT);
98
115
  if (hutkCookie) {
99
- requestHeaders.set("Prepr-Hubspot-Id", hutkCookie);
116
+ requestHeaders.set("Prepr-Hubspot-Id", sanitizeHeaderValue(hutkCookie));
100
117
  }
101
118
  let uid = requestCookies.get(COOKIE_UID);
102
119
  if (!uid) {
@@ -136,16 +153,16 @@ function processPreprRequest(request, opts) {
136
153
  if (!isLivePreview) {
137
154
  const segmentCookie = features.segments ? requestCookies.get(COOKIE_SEGMENT) : void 0;
138
155
  if (segmentCookie) {
139
- requestHeaders.set("Prepr-Segments", segmentCookie);
156
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(segmentCookie));
140
157
  }
141
158
  const abCookie = features.abTesting ? requestCookies.get(COOKIE_VARIANT) : void 0;
142
159
  if (abCookie) {
143
- requestHeaders.set("Prepr-ABtesting", abCookie);
160
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(abCookie));
144
161
  }
145
162
  }
146
163
  const previewAb = features.abTesting ? searchParams.get(PARAM_VARIANT) : null;
147
164
  if (previewAb !== null) {
148
- requestHeaders.set("Prepr-ABtesting", previewAb);
165
+ requestHeaders.set("Prepr-ABtesting", sanitizeHeaderValue(previewAb));
149
166
  if (!isLivePreview) {
150
167
  responseCookies.push({
151
168
  name: COOKIE_VARIANT,
@@ -158,7 +175,7 @@ function processPreprRequest(request, opts) {
158
175
  }
159
176
  const previewSegment = features.segments ? searchParams.get(PARAM_SEGMENT) : null;
160
177
  if (previewSegment !== null) {
161
- requestHeaders.set("Prepr-Segments", previewSegment);
178
+ requestHeaders.set("Prepr-Segments", sanitizeHeaderValue(previewSegment));
162
179
  if (!isLivePreview) {
163
180
  responseCookies.push({
164
181
  name: COOKIE_SEGMENT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preprio/toolkit",
3
- "version": "0.2.0-beta.5",
3
+ "version": "0.2.0-beta.6",
4
4
  "description": "Prepr personalization, A/B testing and preview toolkit for React, Next.js, Nuxt, Astro, SvelteKit and Express",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Prepr B.V. (https://prepr.io)",