Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions src/controller/cve.controller/cve.middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,9 @@ function purlValidateHelper (affected) {
for (const affObj of affected) {
const purlStr = affObj.packageURL

// If no PURL string provided, skip validation
// If no PURL string provided, skip validation and continue through loop
if (!purlStr) {
return true
continue
}
let purlObj
let parsedPurlArray
Expand All @@ -216,22 +216,48 @@ function purlValidateHelper (affected) {

// PURL's with versions are not allowed
if (purlObj.version !== undefined) {
throw new Error('The PURL version component is currently not supported by the CVE schema: "' + purlStr + '"')
throw new Error('The PURL version component is currently not supported by the CVE schema: ' + purlStr)
}

// Handle qualifier cases
if (purlObj.qualifiers !== undefined) {
// Check for versions within qualifiers
if (Object.keys(purlObj.qualifiers).includes('vers')) {
throw new Error('PURL versions are currently not supported by the CVE schema: ' + purlStr)
}
}

if (parsedPurlArray[4] !== undefined) {
// Check for qualifier with key but no value
if ((Array.from(parsedPurlArray[4].values()).includes(''))) {
throw new Error('Qualifier keys must have a value: ' + purlStr)
}
}

// PackageURL does not properly prevent encoded ':', so check for that here
const encColon = /%3a/i
if (encColon.test(purlStr)) {
throw new Error('Percent-encoded colons are not allowed in a PURL: ' + purlStr)
}

// PackageURL does not properly account for certain Subpath situations
// so adding additional validation to account for them
// Handles PURLs that include a # but no subpath
if (purlObj.subpath === undefined && purlStr.includes('#')) {
throw new Error('Subpaths cannot be empty or contain only a /: ' + purlStr)
}

if (purlObj.subpath !== undefined) {
// Checks if any subpaths contain invalid characters
// Subpaths cannot be '.' or '..'
const parsedSubpaths = subpathHelper(parsedPurlArray[5])

if (parsedSubpaths.includes('..') || parsedSubpaths.includes('.')) {
throw new Error('Subpaths cannot be "." or "..": "' + purlStr + '"')
throw new Error('Subpaths cannot be "." or "..": ' + purlStr)
}

if (parsedSubpaths.includes('')) {
throw new Error('Subpaths cannot be empty or contain only a "/": "' + purlStr + '"')
throw new Error('Subpaths cannot be empty or contain only a /: ' + purlStr)
}
}
}
Expand Down
51 changes: 45 additions & 6 deletions test/unit-tests/cve/validatePurlTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ const PurlRecordVersion = [
}
]

const RecordQualifierVersionPurl = [
{
packageURL: 'pkg:pypi/django?vers=vers:pypi%2F%3E%3D1.11.0%7C%21%3D1.11.1%7C%3C2.0.0'
}
]
const PurlRecordEmptySubpath = [
{
packageURL: 'pkg:npm/foo/bar?q=1&s=2#test//'
Expand All @@ -72,6 +77,12 @@ const PurlRecordOnlySinglePeriodSubpath = [
}
]

const PurlRecordPoundSymbolEmpty = [
{
packageURL: 'pkg:npm/foo/bar?q=1&s=2#'
}
]

const MultipleRecordsOneInvalid = [
{
packageURL: 'pkg:npm/foo/bar?q=1&s=2#./'
Expand All @@ -93,12 +104,24 @@ const MultipleRecordsOneInvalid = [
}
]

const PurlEncodedColonRecord = [
{
packageURL: 'pkg:pypi/django#%3A'
}
]

const RecordNoPurl = [
{
test: 'testing String'
}
]

const PurlQualifierKeyNoValueRecord = [
{
packageURL: 'pkg:npm/package-name?qualifier&test=value'
}
]

describe('Testing validatePURL middleware', () => {
context('Positive Tests', () => {
it('Should validate a correctly formatted PURL ', () => {
Expand All @@ -123,27 +146,43 @@ describe('Testing validatePURL middleware', () => {
})

it('Should fail to validate a PURL object with a version component ', () => {
expect(() => purlValidateHelper(PurlRecordVersion)).to.throw('The PURL version component is currently not supported by the CVE schema: "' + PurlRecordVersion[0].packageURL + '"')
expect(() => purlValidateHelper(PurlRecordVersion)).to.throw('The PURL version component is currently not supported by the CVE schema: ' + PurlRecordVersion[0].packageURL)
})

it('Should fail to validate a PURL object with one non-empty subpath and at least one empty subpath ', () => {
expect(() => purlValidateHelper(PurlRecordEmptySubpath)).to.throw('Subpaths cannot be empty or contain only a "/": "' + PurlRecordEmptySubpath[0].packageURL + '"')
expect(() => purlValidateHelper(PurlRecordEmptySubpath)).to.throw('Subpaths cannot be empty or contain only a /: ' + PurlRecordEmptySubpath[0].packageURL)
})

it('Should fail to validate a PURL object with a subpath containing only a "/" ', () => {
expect(() => purlValidateHelper(PurlRecordOnlySlashSubpath)).to.throw('Subpaths cannot be empty or contain only a "/": "' + PurlRecordOnlySlashSubpath[0].packageURL + '"')
expect(() => purlValidateHelper(PurlRecordOnlySlashSubpath)).to.throw('Subpaths cannot be empty or contain only a /: ' + PurlRecordOnlySlashSubpath[0].packageURL)
})

it('Should fail to validate a PURL object with a subpath equal to "." ', () => {
expect(() => purlValidateHelper(PurlRecordOnlySinglePeriodSubpath)).to.throw('Subpaths cannot be "." or "..": "' + PurlRecordOnlySinglePeriodSubpath[0].packageURL + '"')
expect(() => purlValidateHelper(PurlRecordOnlySinglePeriodSubpath)).to.throw('Subpaths cannot be "." or "..": ' + PurlRecordOnlySinglePeriodSubpath[0].packageURL)
})

it('Should fail to validate a PURL object with a subpath equal to ".." ', () => {
expect(() => purlValidateHelper(PurlRecordOnlyDoublePeriodSubpath)).to.throw('Subpaths cannot be "." or "..": "' + PurlRecordOnlyDoublePeriodSubpath[0].packageURL + '"')
expect(() => purlValidateHelper(PurlRecordOnlyDoublePeriodSubpath)).to.throw('Subpaths cannot be "." or "..": ' + PurlRecordOnlyDoublePeriodSubpath[0].packageURL)
})

it('Should fail to validate a PURL object with a # symbol but no subpath ', () => {
expect(() => purlValidateHelper(PurlRecordPoundSymbolEmpty)).to.throw('Subpaths cannot be empty or contain only a /: ' + PurlRecordPoundSymbolEmpty[0].packageURL)
})

it('Should fail to validate when at least one PURL object in an array is invalid ', () => {
expect(() => purlValidateHelper(MultipleRecordsOneInvalid)).to.throw('Subpaths cannot be "." or "..": "' + PurlRecordOnlySinglePeriodSubpath[0].packageURL + '"')
expect(() => purlValidateHelper(MultipleRecordsOneInvalid)).to.throw('Subpaths cannot be "." or "..": ' + PurlRecordOnlySinglePeriodSubpath[0].packageURL)
})

it('Should fail to validate when a version is passed in the qualifier component ', () => {
expect(() => purlValidateHelper(RecordQualifierVersionPurl)).to.throw('PURL versions are currently not supported by the CVE schema: ' + RecordQualifierVersionPurl[0].packageURL)
})

it('Should fail to validate when a qualifier has a key and no value ', () => {
expect(() => purlValidateHelper(PurlQualifierKeyNoValueRecord)).to.throw('Qualifier keys must have a value: ' + PurlQualifierKeyNoValueRecord[0].packageURL)
})

it('Should fail to validate when a PURL contain an encoded colon ', () => {
expect(() => purlValidateHelper(PurlEncodedColonRecord)).to.throw('Percent-encoded colons are not allowed in a PURL: ' + PurlEncodedColonRecord[0].packageURL)
})
})
})
Loading