Skip to content

Commit 5eea728

Browse files
authored
Merge pull request #24 from closeio/fix-js-yaml-vuln
2 parents 52d3f90 + 99a0f2c commit 5eea728

3 files changed

Lines changed: 151 additions & 16 deletions

File tree

dist/index.js

Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6935,7 +6935,13 @@ function processHeader (request, key, val) {
69356935
} else if (typeof val[i] === 'object') {
69366936
throw new InvalidArgumentError(`invalid ${key} header`)
69376937
} else {
6938-
arr.push(`${val[i]}`)
6938+
// Coerce primitives (and reject unsafe coercions such as functions
6939+
// with a crafted toString/Symbol.toPrimitive).
6940+
const str = `${val[i]}`
6941+
if (!isValidHeaderValue(str)) {
6942+
throw new InvalidArgumentError(`invalid ${key} header`)
6943+
}
6944+
arr.push(str)
69396945
}
69406946
}
69416947
val = arr
@@ -6946,7 +6952,12 @@ function processHeader (request, key, val) {
69466952
} else if (val === null) {
69476953
val = ''
69486954
} else {
6955+
// Coerce primitives (and reject unsafe coercions such as functions
6956+
// with a crafted toString/Symbol.toPrimitive).
69496957
val = `${val}`
6958+
if (!isValidHeaderValue(val)) {
6959+
throw new InvalidArgumentError(`invalid ${key} header`)
6960+
}
69506961
}
69516962

69526963
if (headerName === 'host') {
@@ -8323,6 +8334,7 @@ const {
83238334
RequestContentLengthMismatchError,
83248335
ResponseContentLengthMismatchError,
83258336
RequestAbortedError,
8337+
InvalidArgumentError,
83268338
HeadersTimeoutError,
83278339
HeadersOverflowError,
83288340
SocketError,
@@ -9306,8 +9318,16 @@ function writeH1 (client, request) {
93069318
}
93079319
body = bodyStream.stream
93089320
contentLength = bodyStream.length
9309-
} else if (util.isBlobLike(body) && request.contentType == null && body.type) {
9310-
headers.push('content-type', body.type)
9321+
} else if (util.isBlobLike(body) && request.contentType == null) {
9322+
const contentType = body.type
9323+
if (contentType) {
9324+
const contentTypeValue = `${contentType}`
9325+
if (!util.isValidHeaderValue(contentTypeValue)) {
9326+
util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header'))
9327+
return false
9328+
}
9329+
headers.push('content-type', contentTypeValue)
9330+
}
93119331
}
93129332

93139333
if (body && typeof body.read === 'function') {
@@ -12794,6 +12814,28 @@ function calculateRetryAfterHeader (retryAfter) {
1279412814
return new Date(retryAfter).getTime() - current
1279512815
}
1279612816

12817+
function validatePartialResponseContentLength (headers, range, statusCode, retryCount) {
12818+
const contentLength = headers['content-length']
12819+
if (contentLength == null) {
12820+
return null
12821+
}
12822+
12823+
if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) {
12824+
return null
12825+
}
12826+
12827+
const length = Number(contentLength)
12828+
const expectedLength = range.end - range.start + 1
12829+
if (!Number.isFinite(length) || length !== expectedLength) {
12830+
return new RequestRetryError('Content-Length mismatch', statusCode, {
12831+
headers,
12832+
data: { count: retryCount }
12833+
})
12834+
}
12835+
12836+
return null
12837+
}
12838+
1279712839
class RetryHandler {
1279812840
constructor (opts, handlers) {
1279912841
const { retryOptions, ...dispatchOpts } = opts
@@ -13008,6 +13050,12 @@ class RetryHandler {
1300813050
return false
1300913051
}
1301013052

13053+
const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount)
13054+
if (contentLengthError != null) {
13055+
this.abort(contentLengthError)
13056+
return false
13057+
}
13058+
1301113059
const { start, size, end = size - 1 } = contentRange
1301213060

1301313061
assert(this.start === start, 'content-range mismatch')
@@ -13031,6 +13079,12 @@ class RetryHandler {
1303113079
)
1303213080
}
1303313081

13082+
const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount)
13083+
if (contentLengthError != null) {
13084+
this.abort(contentLengthError)
13085+
return false
13086+
}
13087+
1303413088
const { start, size, end = size - 1 } = range
1303513089
assert(
1303613090
start != null && Number.isFinite(start),
@@ -17302,7 +17356,7 @@ function validateCookiePath (path) {
1730217356

1730317357
if (
1730417358
code < 0x20 || // exclude CTLs (0-31)
17305-
code === 0x7F || // DEL
17359+
code > 0x7E || // exclude DEL and non-ascii
1730617360
code === 0x3B // ;
1730717361
) {
1730817362
throw new Error('Invalid cookie path')
@@ -17311,16 +17365,80 @@ function validateCookiePath (path) {
1731117365
}
1731217366

1731317367
/**
17314-
* I have no idea why these values aren't allowed to be honest,
17315-
* but Deno tests these. - Khafra
17368+
* <let-dig> ::= <letter> | <digit>
17369+
*
17370+
* <letter> ::= any one of the 52 alphabetic characters A through Z in
17371+
* upper case and a through z in lower case
17372+
*
17373+
* <digit> ::= any one of the ten digits 0 through 9r
17374+
*
17375+
* @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
17376+
* @param {number} code
17377+
*/
17378+
function isLetterOrDigit (code) {
17379+
return (
17380+
(code >= 0x30 && code <= 0x39) || // 0-9
17381+
(code >= 0x41 && code <= 0x5A) || // A-Z
17382+
(code >= 0x61 && code <= 0x7A) // a-z
17383+
)
17384+
}
17385+
17386+
/**
17387+
* Validates a cookie domain against the "preferred name syntax".
17388+
*
17389+
* <domain> ::= <subdomain> | " "
17390+
* <subdomain> ::= <label> | <subdomain> "." <label>
17391+
* <label> ::= <let-dig> [ [ <ldh-str> ] <let-dig> ]
17392+
* <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
17393+
* <let-dig-hyp> ::= <let-dig> | "-"
17394+
*
17395+
* @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5
17396+
* @see https://www.rfc-editor.org/rfc/rfc1123#section-2.1
17397+
* @see https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4
1731617398
* @param {string} domain
1731717399
*/
1731817400
function validateCookieDomain (domain) {
17319-
if (
17320-
domain.startsWith('-') ||
17321-
domain.endsWith('.') ||
17322-
domain.endsWith('-')
17323-
) {
17401+
// <domain> ::= <subdomain> | " "
17402+
if (domain === ' ') {
17403+
return
17404+
}
17405+
17406+
if (domain.length > 255) {
17407+
throw new Error('Invalid cookie domain')
17408+
}
17409+
17410+
let labelLength = 0
17411+
17412+
for (let i = 0; i < domain.length; ++i) {
17413+
const code = domain.charCodeAt(i)
17414+
17415+
if (code === 0x2E) {
17416+
if (labelLength === 0) {
17417+
throw new Error('Invalid cookie domain')
17418+
}
17419+
17420+
if (domain.charCodeAt(i - 1) === 0x2D) { // "-"
17421+
throw new Error('Invalid cookie domain')
17422+
}
17423+
17424+
labelLength = 0
17425+
continue
17426+
}
17427+
17428+
if (labelLength === 0 && !isLetterOrDigit(code)) {
17429+
throw new Error('Invalid cookie domain')
17430+
}
17431+
17432+
if (!isLetterOrDigit(code) && code !== 0x2D) { // "-"
17433+
throw new Error('Invalid cookie domain')
17434+
}
17435+
17436+
if (++labelLength > 63) {
17437+
throw new Error('Invalid cookie domain')
17438+
}
17439+
}
17440+
17441+
if (labelLength === 0 || domain.charCodeAt(domain.length - 1) === 0x2D) { // "-"
1732417442
throw new Error('Invalid cookie domain')
1732517443
}
1732617444
}
@@ -17463,7 +17581,13 @@ function stringify (cookie) {
1746317581

1746417582
const [key, ...value] = part.split('=')
1746517583

17466-
out.push(`${key.trim()}=${value.join('=')}`)
17584+
const trimmedKey = key.trim()
17585+
const joinedValue = value.join('=')
17586+
17587+
validateCookieName(trimmedKey)
17588+
validateCookieValue(joinedValue)
17589+
17590+
out.push(`${trimmedKey}=${joinedValue}`)
1746717591
}
1746817592

1746917593
return out.join('; ')

package-lock.json

Lines changed: 13 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
},
5454
"overrides": {
5555
"handlebars@>=4.0.0 <4.7.9": "^4.7.9",
56-
"brace-expansion@<1.1.16": "^1.1.18"
56+
"brace-expansion@<1.1.16": "^1.1.18",
57+
"js-yaml@>=4.0.0 <4.3.0": "^4.3.1"
5758
},
5859
"lint-staged": {
5960
"**/*.ts": [

0 commit comments

Comments
 (0)