@@ -31,7 +31,6 @@ SOFTWARE.
3131import { decodePurlComponent } from './decode.js'
3232import { PurlError } from './error.js'
3333import { isObject , recursiveFreeze } from './objects.js'
34- import { PackageURLBuilder } from './package-url-builder.js'
3534import { PurlComponent } from './purl-component.js'
3635import { PurlQualifierNames } from './purl-qualifier-names.js'
3736import { PurlType } from './purl-type.js'
@@ -68,10 +67,12 @@ export type PackageURLObject = {
6867}
6968
7069// Pattern to match URLs with schemes other than "pkg".
71- const OTHER_SCHEME_PATTERN = / ^ [ a - z A - Z ] [ a - z A - Z 0 - 9 + . - ] * : \/ \/ /
70+ // Limited to 256 chars for scheme to prevent ReDoS.
71+ const OTHER_SCHEME_PATTERN = / ^ [ a - z A - Z ] [ a - z A - Z 0 - 9 + . - ] { 0 , 255 } : \/ \/ /
7272
7373// Pattern to match purl-like strings with type/name format.
74- const PURL_LIKE_PATTERN = / ^ [ a - z A - Z 0 - 9 + . - ] + \/ /
74+ // Limited to 256 chars for type to prevent ReDoS.
75+ const PURL_LIKE_PATTERN = / ^ [ a - z A - Z 0 - 9 + . - ] { 1 , 256 } \/ /
7576
7677/**
7778 * Package URL parser and constructor implementing the PURL specification.
@@ -255,11 +256,39 @@ class PackageURL {
255256 if ( typeof json !== 'string' ) {
256257 throw new Error ( 'JSON string argument is required.' )
257258 }
259+
260+ // Size limit: 1MB to prevent memory exhaustion.
261+ const MAX_JSON_SIZE = 1024 * 1024
262+ if ( json . length > MAX_JSON_SIZE ) {
263+ throw new Error (
264+ `JSON string exceeds maximum size limit of ${ MAX_JSON_SIZE } bytes.` ,
265+ )
266+ }
267+
268+ let parsed
258269 try {
259- return PackageURL . fromObject ( JSON . parse ( json ) )
270+ parsed = JSON . parse ( json )
260271 } catch ( e ) {
261272 throw new Error ( 'Invalid JSON string.' , { cause : e } )
262273 }
274+
275+ // Validate parsed result is an object.
276+ if ( ! parsed || typeof parsed !== 'object' || Array . isArray ( parsed ) ) {
277+ throw new Error ( 'JSON must parse to an object.' )
278+ }
279+
280+ // Create a safe object without prototype chain to prevent prototype pollution.
281+ const safeObject : PackageURLObject = {
282+ __proto__ : null ,
283+ type : parsed . type ,
284+ namespace : parsed . namespace ,
285+ name : parsed . name ,
286+ version : parsed . version ,
287+ qualifiers : parsed . qualifiers ,
288+ subpath : parsed . subpath ,
289+ } as PackageURLObject
290+
291+ return PackageURL . fromObject ( safeObject )
263292 }
264293
265294 /**
@@ -302,6 +331,15 @@ class PackageURL {
302331 return [ undefined , undefined , undefined , undefined , undefined , undefined ]
303332 }
304333
334+ // Input length validation to prevent DoS.
335+ // Reasonable limit for a package URL.
336+ const MAX_PURL_LENGTH = 4096
337+ if ( purlStr . length > MAX_PURL_LENGTH ) {
338+ throw new Error (
339+ `Package URL exceeds maximum length of ${ MAX_PURL_LENGTH } characters.` ,
340+ )
341+ }
342+
305343 // If the string doesn't start with "pkg:" but looks like a purl format, prepend "pkg:" and try parsing.
306344 if ( ! purlStr . startsWith ( 'pkg:' ) ) {
307345 // Only auto-prepend "pkg:" if the string looks like a purl (contains a type/name pattern)
@@ -484,7 +522,6 @@ export {
484522 Err ,
485523 Ok ,
486524 PackageURL ,
487- PackageURLBuilder ,
488525 PurlComponent ,
489526 PurlQualifierNames ,
490527 PurlType ,
0 commit comments