55import ContentFeature from '../content-feature.js' ;
66import { getFaviconList } from './favicon.js' ;
77import { isDuckAi , isBeingFramed , getTabUrl } from '../utils.js' ;
8+ // eslint-disable-next-line no-redeclare
9+ import { JSONparse , Set , hasOwnProperty } from '../captured-globals.js' ;
810const MSG_PAGE_CONTEXT_RESPONSE = 'collectionResult' ;
911
12+ // Bail past this nesting depth when walking JSON-LD so adversarially deep blocks
13+ // can't blow the stack and fail the whole content collection.
14+ const MAX_JSON_LD_DEPTH = 32 ;
15+
1016export function checkNodeIsVisible ( node ) {
1117 try {
1218 const style = window . getComputedStyle ( node ) ;
@@ -250,14 +256,16 @@ function extractJsonLdTypes(document, maxTypes, maxBlockLength) {
250256 types . push ( type ) ;
251257 } ;
252258
253- const blocks = document . querySelectorAll ( 'script[type="application/ld+json"]' ) ;
259+ // Case-insensitive attribute match ("i") so non-canonical casing (e.g. APPLICATION/LD+JSON)
260+ // is still discovered.
261+ const blocks = document . querySelectorAll ( 'script[type="application/ld+json" i]' ) ;
254262 for ( const block of blocks ) {
255263 if ( types . length >= maxTypes ) break ;
256264 const text = block . textContent || '' ;
257265 if ( ! text || text . length > maxBlockLength ) continue ;
258266 let data ;
259267 try {
260- data = JSON . parse ( text ) ;
268+ data = JSONparse ( text ) ;
261269 } catch ( e ) {
262270 continue ;
263271 }
@@ -269,24 +277,30 @@ function extractJsonLdTypes(document, maxTypes, maxBlockLength) {
269277
270278/**
271279 * Walk a parsed JSON-LD value (object, array, or `@graph` container) and report each `@type`.
280+ * Own-property checks keep a polluted `Object.prototype` from surfacing spurious `@type` /
281+ * `@graph` keys, and recursion is bounded by {@link MAX_JSON_LD_DEPTH}.
272282 * @param {unknown } node
273283 * @param {(value: unknown) => void } add
284+ * @param {number } [depth]
274285 */
275- function collectJsonLdTypes ( node , add ) {
286+ function collectJsonLdTypes ( node , add , depth = 0 ) {
287+ if ( depth > MAX_JSON_LD_DEPTH ) return ;
276288 if ( Array . isArray ( node ) ) {
277- for ( const item of node ) collectJsonLdTypes ( item , add ) ;
289+ for ( const item of node ) collectJsonLdTypes ( item , add , depth + 1 ) ;
278290 return ;
279291 }
280292 if ( ! node || typeof node !== 'object' ) return ;
281293 const obj = /** @type {Record<string, unknown> } */ ( node ) ;
282- const type = obj [ '@type' ] ;
283- if ( Array . isArray ( type ) ) {
284- for ( const value of type ) add ( value ) ;
285- } else if ( type != null ) {
286- add ( type ) ;
294+ if ( hasOwnProperty . call ( obj , '@type' ) ) {
295+ const type = obj [ '@type' ] ;
296+ if ( Array . isArray ( type ) ) {
297+ for ( const value of type ) add ( value ) ;
298+ } else if ( type != null ) {
299+ add ( type ) ;
300+ }
287301 }
288- if ( obj [ '@graph' ] ) {
289- collectJsonLdTypes ( obj [ '@graph' ] , add ) ;
302+ if ( hasOwnProperty . call ( obj , '@graph' ) ) {
303+ collectJsonLdTypes ( obj [ '@graph' ] , add , depth + 1 ) ;
290304 }
291305}
292306
@@ -722,9 +736,11 @@ export default class PageContext extends ContentFeature {
722736 }
723737
724738 getPageTypeSignals ( ) {
739+ // `??` so an explicit 0 isn't silently replaced by the default; Math.min clamps remote
740+ // config to a safe ceiling to bound parse work.
725741 return extractPageTypeSignals ( document , {
726- maxTypes : this . getFeatureSetting ( 'maxPageTypeSignals' ) || 10 ,
727- maxBlockLength : this . getFeatureSetting ( 'maxJsonLdBlockLength' ) || 100000 ,
742+ maxTypes : Math . min ( this . getFeatureSetting ( 'maxPageTypeSignals' ) ?? 10 , 50 ) ,
743+ maxBlockLength : Math . min ( this . getFeatureSetting ( 'maxJsonLdBlockLength' ) ?? 100000 , 1000000 ) ,
728744 } ) ;
729745 }
730746
0 commit comments