Skip to content

Commit 9a03156

Browse files
committed
fix: escape </script> sequences in jsonld override path
Applies escapeForScriptElement() to both the string and object variants of the product.jsonld full-override path, so all JSON-LD output—whether auto-generated or customer-supplied—is safe for embedding in HTML <script> elements per the W3C JSON-LD 1.1 spec.
1 parent f87bdbb commit 9a03156

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

src/steps/render-jsonld.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,11 @@ function renderOffer(state, variant, simple = false) {
8080
export function convertToJsonLD(state, product) {
8181
// If the product has a jsonld property, use it directly instead of generating
8282
if (product.jsonld) {
83-
return typeof product.jsonld === 'string'
84-
? product.jsonld
85-
: JSON.stringify(product.jsonld, null, 2);
83+
return escapeForScriptElement(
84+
typeof product.jsonld === 'string'
85+
? product.jsonld
86+
: JSON.stringify(product.jsonld, null, 2),
87+
);
8688
}
8789

8890
const {

test/steps/render-jsonld.test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,50 @@ describe('convertToJsonLD', () => {
7171
assert.strictEqual(parsed.sku, 'STRING-SKU');
7272
});
7373

74+
it('escapes </script> sequences in jsonld string override', () => {
75+
const maliciousJsonLdString = JSON.stringify({
76+
'@context': 'https://schema.org',
77+
'@type': 'Product',
78+
name: 'Injected</script><script>alert(1)</script>',
79+
});
80+
81+
const product = {
82+
sku: 'ORIGINAL-SKU',
83+
name: 'Original Product Name',
84+
jsonld: maliciousJsonLdString,
85+
};
86+
87+
const result = convertToJsonLD(mockState, product);
88+
89+
assert.ok(!result.includes('</script>'), 'output must not contain </script>');
90+
assert.ok(result.includes('<\\/script>'), 'output must contain escaped sequence');
91+
92+
// Round-trip: JSON.parse must recover original value
93+
const parsed = JSON.parse(result);
94+
assert.strictEqual(parsed.name, 'Injected</script><script>alert(1)</script>');
95+
});
96+
97+
it('escapes </script> sequences in jsonld object override', () => {
98+
const product = {
99+
sku: 'ORIGINAL-SKU',
100+
name: 'Original Product Name',
101+
jsonld: {
102+
'@context': 'https://schema.org',
103+
'@type': 'Product',
104+
name: 'Injected</script><script>alert(1)</script>',
105+
},
106+
};
107+
108+
const result = convertToJsonLD(mockState, product);
109+
110+
assert.ok(!result.includes('</script>'), 'output must not contain </script>');
111+
assert.ok(result.includes('<\\/script>'), 'output must contain escaped sequence');
112+
113+
// Round-trip: JSON.parse must recover original value
114+
const parsed = JSON.parse(result);
115+
assert.strictEqual(parsed.name, 'Injected</script><script>alert(1)</script>');
116+
});
117+
74118
it('generates jsonld when jsonld property is not provided', () => {
75119
const product = {
76120
sku: 'GENERATED-SKU',

0 commit comments

Comments
 (0)