@justeattakeaway/pie-cookie-banner 1.8.2 → 1.9.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@justeattakeaway/pie-cookie-banner",
3
3
  "description": "PIE Design System Cookie Banner built using Web Components",
4
- "version": "1.8.2",
4
+ "version": "1.9.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/justeattakeaway/pie",
@@ -50,18 +50,19 @@
50
50
  "license": "Apache-2.0",
51
51
  "devDependencies": {
52
52
  "@justeattakeaway/pie-components-config": "0.21.3",
53
- "@justeattakeaway/pie-css": "1.1.2",
54
- "@justeattakeaway/pie-monorepo-utils": "0.9.4",
53
+ "@justeattakeaway/pie-css": "1.2.0",
54
+ "@justeattakeaway/pie-monorepo-utils": "0.9.5",
55
55
  "@justeattakeaway/pie-wrapper-react": "0.14.5"
56
56
  },
57
57
  "dependencies": {
58
- "@justeattakeaway/pie-button": "1.14.2",
59
- "@justeattakeaway/pie-divider": "1.5.24",
60
- "@justeattakeaway/pie-icon-button": "2.7.17",
61
- "@justeattakeaway/pie-link": "1.3.33",
62
- "@justeattakeaway/pie-modal": "1.26.9",
63
- "@justeattakeaway/pie-switch": "2.4.1",
64
- "@justeattakeaway/pie-webc-core": "14.0.2"
58
+ "@justeattakeaway/pie-button": "1.14.4",
59
+ "@justeattakeaway/pie-divider": "1.5.25",
60
+ "@justeattakeaway/pie-icon-button": "2.7.19",
61
+ "@justeattakeaway/pie-link": "1.3.35",
62
+ "@justeattakeaway/pie-modal": "1.26.11",
63
+ "@justeattakeaway/pie-switch": "2.4.2",
64
+ "@justeattakeaway/pie-webc-core": "15.0.0",
65
+ "dompurify": "3.4.8"
65
66
  },
66
67
  "customElements": "custom-elements.json",
67
68
  "sideEffects": [
package/src/index.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  property, state, queryAll,
10
10
  } from 'lit/decorators.js';
11
11
  import { repeat } from 'lit/directives/repeat.js';
12
+ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
12
13
 
13
14
  import '@justeattakeaway/pie-button';
14
15
  import '@justeattakeaway/pie-divider';
@@ -39,7 +40,7 @@ import {
39
40
  type LanguageCode,
40
41
  } from './defs';
41
42
 
42
- import { localiseText, localiseRichText } from './localisation-utils';
43
+ import { localiseText, localiseRichText, sanitiseDescriptionHtml } from './localisation-utils';
43
44
 
44
45
  // Valid values available to consumers
45
46
  export * from './defs';
@@ -262,7 +263,7 @@ export class PieCookieBanner extends PieElement implements CookieBannerProps {
262
263
  <div class="c-cookieBanner-preference">
263
264
  <div>
264
265
  <h3 class="c-cookieBanner-subheading">${title}</h3>
265
- ${description ? html`<p class="c-cookieBanner-description">${description}</p>` : nothing}
266
+ ${description ? html`<p class="c-cookieBanner-description">${unsafeHTML(sanitiseDescriptionHtml(description, this._linkTargetAttribute))}</p>` : nothing}
266
267
  </div>
267
268
  <pie-switch
268
269
  id="${id}"
@@ -1,4 +1,5 @@
1
1
  import { type TemplateResult } from 'lit';
2
+ import DOMPurify from 'dompurify';
2
3
 
3
4
  import { type CookieBannerLocale, type CustomTagEnhancers } from './defs';
4
5
 
@@ -134,6 +135,61 @@ function enhanceCustomTags (richText:string, customTagEnhancers:CustomTagEnhance
134
135
  });
135
136
  }
136
137
 
138
+ /**
139
+ * Sanitises an HTML string to allow only safe <a> tags, and normalises anchor
140
+ * attributes to respect the component's link-target behaviour.
141
+ *
142
+ * Uses DOMPurify for the core sanitisation:
143
+ * - Strips all non-<a> elements (keeping their text content).
144
+ * - Removes unsafe href protocols (javascript:, data:, vbscript:).
145
+ * - Removes non-allowlisted attributes (only href, rel, target survive).
146
+ *
147
+ * A post-sanitisation hook then enforces target/rel:
148
+ * - Sets target to linkTarget (overrides any existing target).
149
+ * - Ensures rel contains "noopener noreferrer" when target="_blank"
150
+ * (prevents reverse-tabnabbing).
151
+ *
152
+ * In SSR / non-browser environments (no `window`), all HTML is stripped and
153
+ * only the text content is returned — the client will re-render with the full
154
+ * sanitisation pass after hydration.
155
+ */
156
+ export function sanitiseDescriptionHtml (input: string, linkTarget = '_blank'): string {
157
+ if (typeof window === 'undefined') {
158
+ return input.replace(/<[^>]*>/g, '');
159
+ }
160
+
161
+ const normalisedLinkTarget = linkTarget === '_self' ? '_self' : '_blank';
162
+
163
+ const enforceAnchorAttributes = (node: Element) => {
164
+ if (node.tagName !== 'A') return;
165
+
166
+ node.setAttribute('target', normalisedLinkTarget);
167
+
168
+ if (normalisedLinkTarget !== '_blank') {
169
+ node.removeAttribute('rel');
170
+ return;
171
+ }
172
+
173
+ const relTokens = new Set((node.getAttribute('rel') ?? '')
174
+ .split(/\s+/)
175
+ .filter(Boolean));
176
+ relTokens.add('noopener');
177
+ relTokens.add('noreferrer');
178
+ node.setAttribute('rel', Array.from(relTokens).join(' '));
179
+ };
180
+
181
+ try {
182
+ DOMPurify.addHook('afterSanitizeAttributes', enforceAnchorAttributes);
183
+
184
+ return DOMPurify.sanitize(input, {
185
+ ALLOWED_TAGS: ['a'],
186
+ ALLOWED_ATTR: ['href', 'rel', 'target'],
187
+ }) as string;
188
+ } finally {
189
+ DOMPurify.removeHook('afterSanitizeAttributes');
190
+ }
191
+ }
192
+
137
193
  /**
138
194
  * Localises a rich text string by replacing custom tags with their respective enhancer functions content
139
195
  * If the key is not found, it will be used as fallback