diff --git a/node_modules/.gitignore b/node_modules/.gitignore index 9ae4ee04339a1..13bfabaec7ad2 100644 --- a/node_modules/.gitignore +++ b/node_modules/.gitignore @@ -194,6 +194,11 @@ !/npm-pick-manifest !/npm-profile !/npm-registry-fetch +!/npm-registry-fetch/node_modules/ +/npm-registry-fetch/node_modules/* +!/npm-registry-fetch/node_modules/@npmcli/ +/npm-registry-fetch/node_modules/@npmcli/* +!/npm-registry-fetch/node_modules/@npmcli/redact !/npm-user-validate !/p-map !/package-json-from-dist diff --git a/node_modules/@npmcli/redact/package.json b/node_modules/@npmcli/redact/package.json index 831387ca54106..649f82ef5ca89 100644 --- a/node_modules/@npmcli/redact/package.json +++ b/node_modules/@npmcli/redact/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/redact", - "version": "2.0.1", + "version": "3.0.0", "description": "Redact sensitive npm information from output", "main": "lib/index.js", "exports": { @@ -10,12 +10,13 @@ }, "scripts": { "test": "tap", - "lint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"", + "lint": "npm run eslint", "postlint": "template-oss-check", "template-oss-apply": "template-oss-apply --force", - "lintfix": "npm run lint -- --fix", + "lintfix": "npm run eslint -- --fix", "snap": "tap", - "posttest": "npm run lint" + "posttest": "npm run lint", + "eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"" }, "keywords": [], "author": "GitHub Inc.", @@ -26,11 +27,11 @@ ], "repository": { "type": "git", - "url": "https://github.com/npm/redact.git" + "url": "git+https://github.com/npm/redact.git" }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.21.3", + "version": "4.23.3", "publish": true }, "tap": { @@ -41,11 +42,11 @@ "timeout": 120 }, "devDependencies": { - "@npmcli/eslint-config": "^4.0.2", - "@npmcli/template-oss": "4.21.3", + "@npmcli/eslint-config": "^5.0.0", + "@npmcli/template-oss": "4.23.3", "tap": "^16.3.10" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } } diff --git a/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/LICENSE b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/LICENSE new file mode 100644 index 0000000000000..c21644115c85d --- /dev/null +++ b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 npm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/deep-map.js b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/deep-map.js new file mode 100644 index 0000000000000..b555cf9fc4c8b --- /dev/null +++ b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/deep-map.js @@ -0,0 +1,78 @@ +function filterError (input) { + return { + errorType: input.name, + message: input.message, + stack: input.stack, + ...(input.code ? { code: input.code } : {}), + ...(input.statusCode ? { statusCode: input.statusCode } : {}), + } +} + +const deepMap = (input, handler = v => v, path = ['$'], seen = new Set([input])) => { + // this is in an effort to maintain bole's error logging behavior + if (path.join('.') === '$' && input instanceof Error) { + return deepMap({ err: filterError(input) }, handler, path, seen) + } + if (input instanceof Error) { + return deepMap(filterError(input), handler, path, seen) + } + if (input instanceof Buffer) { + return `[unable to log instanceof buffer]` + } + if (input instanceof Uint8Array) { + return `[unable to log instanceof Uint8Array]` + } + + if (Array.isArray(input)) { + const result = [] + for (let i = 0; i < input.length; i++) { + const element = input[i] + const elementPath = [...path, i] + if (element instanceof Object) { + if (!seen.has(element)) { // avoid getting stuck in circular reference + seen.add(element) + result.push(deepMap(handler(element, elementPath), handler, elementPath, seen)) + } + } else { + result.push(handler(element, elementPath)) + } + } + return result + } + + if (input === null) { + return null + } else if (typeof input === 'object' || typeof input === 'function') { + const result = {} + + for (const propertyName of Object.getOwnPropertyNames(input)) { + // skip logging internal properties + if (propertyName.startsWith('_')) { + continue + } + + try { + const property = input[propertyName] + const propertyPath = [...path, propertyName] + if (property instanceof Object) { + if (!seen.has(property)) { // avoid getting stuck in circular reference + seen.add(property) + result[propertyName] = deepMap( + handler(property, propertyPath), handler, propertyPath, seen + ) + } + } else { + result[propertyName] = handler(property, propertyPath) + } + } catch (err) { + // a getter may throw an error + result[propertyName] = `[error getting value: ${err.message}]` + } + } + return result + } + + return handler(input, path) +} + +module.exports = { deepMap } diff --git a/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/index.js b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/index.js new file mode 100644 index 0000000000000..9b10c7f6a0081 --- /dev/null +++ b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/index.js @@ -0,0 +1,44 @@ +const matchers = require('./matchers') +const { redactUrlPassword } = require('./utils') + +const REPLACE = '***' + +const redact = (value) => { + if (typeof value !== 'string' || !value) { + return value + } + return redactUrlPassword(value, REPLACE) + .replace(matchers.NPM_SECRET.pattern, `npm_${REPLACE}`) + .replace(matchers.UUID.pattern, REPLACE) +} + +// split on \s|= similar to how nopt parses options +const splitAndRedact = (str) => { + // stateful regex, don't move out of this scope + const splitChars = /[\s=]/g + + let match = null + let result = '' + let index = 0 + while (match = splitChars.exec(str)) { + result += redact(str.slice(index, match.index)) + match[0] + index = splitChars.lastIndex + } + + return result + redact(str.slice(index)) +} + +// replaces auth info in an array of arguments or in a strings +const redactLog = (arg) => { + if (typeof arg === 'string') { + return splitAndRedact(arg) + } else if (Array.isArray(arg)) { + return arg.map((a) => typeof a === 'string' ? splitAndRedact(a) : a) + } + return arg +} + +module.exports = { + redact, + redactLog, +} diff --git a/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/matchers.js b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/matchers.js new file mode 100644 index 0000000000000..fe9b9071de8a1 --- /dev/null +++ b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/matchers.js @@ -0,0 +1,81 @@ +const TYPE_REGEX = 'regex' +const TYPE_URL = 'url' +const TYPE_PATH = 'path' + +const NPM_SECRET = { + type: TYPE_REGEX, + pattern: /\b(npms?_)[a-zA-Z0-9]{36,48}\b/gi, + replacement: `[REDACTED_NPM_SECRET]`, +} + +const AUTH_HEADER = { + type: TYPE_REGEX, + pattern: /\b(Basic\s+|Bearer\s+)[\w+=\-.]+\b/gi, + replacement: `[REDACTED_AUTH_HEADER]`, +} + +const JSON_WEB_TOKEN = { + type: TYPE_REGEX, + pattern: /\b[A-Za-z0-9-_]{10,}(?!\.\d+\.)\.[A-Za-z0-9-_]{3,}\.[A-Za-z0-9-_]{20,}\b/gi, + replacement: `[REDACTED_JSON_WEB_TOKEN]`, +} + +const UUID = { + type: TYPE_REGEX, + pattern: /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, + replacement: `[REDACTED_UUID]`, +} + +const URL_MATCHER = { + type: TYPE_REGEX, + pattern: /(?:https?|ftp):\/\/[^\s/"$.?#].[^\s"]*/gi, + replacement: '[REDACTED_URL]', +} + +const DEEP_HEADER_AUTHORIZATION = { + type: TYPE_PATH, + predicate: ({ path }) => path.endsWith('.headers.authorization'), + replacement: '[REDACTED_HEADER_AUTHORIZATION]', +} + +const DEEP_HEADER_SET_COOKIE = { + type: TYPE_PATH, + predicate: ({ path }) => path.endsWith('.headers.set-cookie'), + replacement: '[REDACTED_HEADER_SET_COOKIE]', +} + +const REWRITE_REQUEST = { + type: TYPE_PATH, + predicate: ({ path }) => path.endsWith('.request'), + replacement: (input) => ({ + method: input?.method, + path: input?.path, + headers: input?.headers, + url: input?.url, + }), +} + +const REWRITE_RESPONSE = { + type: TYPE_PATH, + predicate: ({ path }) => path.endsWith('.response'), + replacement: (input) => ({ + data: input?.data, + status: input?.status, + headers: input?.headers, + }), +} + +module.exports = { + TYPE_REGEX, + TYPE_URL, + TYPE_PATH, + NPM_SECRET, + AUTH_HEADER, + JSON_WEB_TOKEN, + UUID, + URL_MATCHER, + DEEP_HEADER_AUTHORIZATION, + DEEP_HEADER_SET_COOKIE, + REWRITE_REQUEST, + REWRITE_RESPONSE, +} diff --git a/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/server.js b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/server.js new file mode 100644 index 0000000000000..669e834da6131 --- /dev/null +++ b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/server.js @@ -0,0 +1,34 @@ +const { + AUTH_HEADER, + JSON_WEB_TOKEN, + NPM_SECRET, + DEEP_HEADER_AUTHORIZATION, + DEEP_HEADER_SET_COOKIE, + REWRITE_REQUEST, + REWRITE_RESPONSE, +} = require('./matchers') + +const { + redactUrlMatcher, + redactUrlPasswordMatcher, + redactMatchers, +} = require('./utils') + +const { deepMap } = require('./deep-map') + +const _redact = redactMatchers( + NPM_SECRET, + AUTH_HEADER, + JSON_WEB_TOKEN, + DEEP_HEADER_AUTHORIZATION, + DEEP_HEADER_SET_COOKIE, + REWRITE_REQUEST, + REWRITE_RESPONSE, + redactUrlMatcher( + redactUrlPasswordMatcher() + ) +) + +const redact = (input) => deepMap(input, (value, path) => _redact(value, { path })) + +module.exports = { redact } diff --git a/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/utils.js b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/utils.js new file mode 100644 index 0000000000000..8395ab25fc373 --- /dev/null +++ b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/lib/utils.js @@ -0,0 +1,202 @@ +const { + URL_MATCHER, + TYPE_URL, + TYPE_REGEX, + TYPE_PATH, +} = require('./matchers') + +/** + * creates a string of asterisks, + * this forces a minimum asterisk for security purposes + */ +const asterisk = (length = 0) => { + length = typeof length === 'string' ? length.length : length + if (length < 8) { + return '*'.repeat(8) + } + return '*'.repeat(length) +} + +/** + * escapes all special regex chars + * @see https://stackoverflow.com/a/9310752 + * @see https://github.com/tc39/proposal-regex-escaping + */ +const escapeRegExp = (text) => { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, `\\$&`) +} + +/** + * provieds a regex "or" of the url versions of a string + */ +const urlEncodeRegexGroup = (value) => { + const decoded = decodeURIComponent(value) + const encoded = encodeURIComponent(value) + const union = [...new Set([encoded, decoded, value])].map(escapeRegExp).join('|') + return union +} + +/** + * a tagged template literal that returns a regex ensures all variables are excaped + */ +const urlEncodeRegexTag = (strings, ...values) => { + let pattern = '' + for (let i = 0; i < values.length; i++) { + pattern += strings[i] + `(${urlEncodeRegexGroup(values[i])})` + } + pattern += strings[strings.length - 1] + return new RegExp(pattern) +} + +/** + * creates a matcher for redacting url hostname + */ +const redactUrlHostnameMatcher = ({ hostname, replacement } = {}) => ({ + type: TYPE_URL, + predicate: ({ url }) => url.hostname === hostname, + pattern: ({ url }) => { + return urlEncodeRegexTag`(^${url.protocol}//${url.username}:.+@)?${url.hostname}` + }, + replacement: `$1${replacement || asterisk()}`, +}) + +/** + * creates a matcher for redacting url search / query parameter values + */ +const redactUrlSearchParamsMatcher = ({ param, replacement } = {}) => ({ + type: TYPE_URL, + predicate: ({ url }) => url.searchParams.has(param), + pattern: ({ url }) => urlEncodeRegexTag`(${param}=)${url.searchParams.get(param)}`, + replacement: `$1${replacement || asterisk()}`, +}) + +/** creates a matcher for redacting the url password */ +const redactUrlPasswordMatcher = ({ replacement } = {}) => ({ + type: TYPE_URL, + predicate: ({ url }) => url.password, + pattern: ({ url }) => urlEncodeRegexTag`(^${url.protocol}//${url.username}:)${url.password}`, + replacement: `$1${replacement || asterisk()}`, +}) + +const redactUrlReplacement = (...matchers) => (subValue) => { + try { + const url = new URL(subValue) + return redactMatchers(...matchers)(subValue, { url }) + } catch (err) { + return subValue + } +} + +/** + * creates a matcher / submatcher for urls, this function allows you to first + * collect all urls within a larger string and then pass those urls to a + * submatcher + * + * @example + * console.log("this will first match all urls, then pass those urls to the password patcher") + * redactMatchers(redactUrlMatcher(redactUrlPasswordMatcher())) + * + * @example + * console.log( + * "this will assume you are passing in a string that is a url, and will redact the password" + * ) + * redactMatchers(redactUrlPasswordMatcher()) + * + */ +const redactUrlMatcher = (...matchers) => { + return { + ...URL_MATCHER, + replacement: redactUrlReplacement(...matchers), + } +} + +const matcherFunctions = { + [TYPE_REGEX]: (matcher) => (value) => { + if (typeof value === 'string') { + value = value.replace(matcher.pattern, matcher.replacement) + } + return value + }, + [TYPE_URL]: (matcher) => (value, ctx) => { + if (typeof value === 'string') { + try { + const url = ctx?.url || new URL(value) + const { predicate, pattern } = matcher + const predicateValue = predicate({ url }) + if (predicateValue) { + value = value.replace(pattern({ url }), matcher.replacement) + } + } catch (_e) { + return value + } + } + return value + }, + [TYPE_PATH]: (matcher) => (value, ctx) => { + const rawPath = ctx?.path + const path = rawPath.join('.').toLowerCase() + const { predicate, replacement } = matcher + const replace = typeof replacement === 'function' ? replacement : () => replacement + const shouldRun = predicate({ rawPath, path }) + if (shouldRun) { + value = replace(value, { rawPath, path }) + } + return value + }, +} + +/** converts a matcher to a function */ +const redactMatcher = (matcher) => { + return matcherFunctions[matcher.type](matcher) +} + +/** converts a series of matchers to a function */ +const redactMatchers = (...matchers) => (value, ctx) => { + const flatMatchers = matchers.flat() + return flatMatchers.reduce((result, matcher) => { + const fn = (typeof matcher === 'function') ? matcher : redactMatcher(matcher) + return fn(result, ctx) + }, value) +} + +/** + * replacement handler, keeping $1 (if it exists) and replacing the + * rest of the string with asterisks, maintaining string length + */ +const redactDynamicReplacement = () => (value, start) => { + if (typeof start === 'number') { + return asterisk(value) + } + return start + asterisk(value.substring(start.length).length) +} + +/** + * replacement handler, keeping $1 (if it exists) and replacing the + * rest of the string with a fixed number of asterisks + */ +const redactFixedReplacement = (length) => (_value, start) => { + if (typeof start === 'number') { + return asterisk(length) + } + return start + asterisk(length) +} + +const redactUrlPassword = (value, replacement) => { + return redactMatchers(redactUrlPasswordMatcher({ replacement }))(value) +} + +module.exports = { + asterisk, + escapeRegExp, + urlEncodeRegexGroup, + urlEncodeRegexTag, + redactUrlHostnameMatcher, + redactUrlSearchParamsMatcher, + redactUrlPasswordMatcher, + redactUrlMatcher, + redactUrlReplacement, + redactDynamicReplacement, + redactFixedReplacement, + redactMatchers, + redactUrlPassword, +} diff --git a/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/package.json b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/package.json new file mode 100644 index 0000000000000..831387ca54106 --- /dev/null +++ b/node_modules/npm-registry-fetch/node_modules/@npmcli/redact/package.json @@ -0,0 +1,51 @@ +{ + "name": "@npmcli/redact", + "version": "2.0.1", + "description": "Redact sensitive npm information from output", + "main": "lib/index.js", + "exports": { + ".": "./lib/index.js", + "./server": "./lib/server.js", + "./package.json": "./package.json" + }, + "scripts": { + "test": "tap", + "lint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"", + "postlint": "template-oss-check", + "template-oss-apply": "template-oss-apply --force", + "lintfix": "npm run lint -- --fix", + "snap": "tap", + "posttest": "npm run lint" + }, + "keywords": [], + "author": "GitHub Inc.", + "license": "ISC", + "files": [ + "bin/", + "lib/" + ], + "repository": { + "type": "git", + "url": "https://github.com/npm/redact.git" + }, + "templateOSS": { + "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", + "version": "4.21.3", + "publish": true + }, + "tap": { + "nyc-arg": [ + "--exclude", + "tap-snapshots/**" + ], + "timeout": 120 + }, + "devDependencies": { + "@npmcli/eslint-config": "^4.0.2", + "@npmcli/template-oss": "4.21.3", + "tap": "^16.3.10" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } +} diff --git a/package-lock.json b/package-lock.json index 63256b17e7a8b..69c1489cd510d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ "@npmcli/map-workspaces": "^4.0.1", "@npmcli/package-json": "^6.0.1", "@npmcli/promise-spawn": "^8.0.1", - "@npmcli/redact": "^2.0.1", + "@npmcli/redact": "^3.0.0", "@npmcli/run-script": "^8.1.0", "@sigstore/tuf": "^2.3.4", "abbrev": "^2.0.0", @@ -1932,13 +1932,13 @@ } }, "node_modules/@npmcli/redact": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", - "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.0.0.tgz", + "integrity": "sha512-/1uFzjVcfzqrgCeGW7+SZ4hv0qLWmKXVzFahZGJ6QuJBj6Myt9s17+JL86i76NV9YSnJRcGXJYQbAU0rn1YTCQ==", "inBundle": true, "license": "ISC", "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/run-script": { @@ -9848,6 +9848,16 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/npm-registry-fetch/node_modules/@npmcli/redact": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", + "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/npm-run-path": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", @@ -16114,7 +16124,7 @@ "@npmcli/node-gyp": "^3.0.0", "@npmcli/package-json": "^6.0.1", "@npmcli/query": "^3.1.0", - "@npmcli/redact": "^2.0.0", + "@npmcli/redact": "^3.0.0", "@npmcli/run-script": "^8.1.0", "bin-links": "^4.0.4", "cacache": "^18.0.3", diff --git a/package.json b/package.json index 99423a86ade8f..7d85fb85f1918 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@npmcli/map-workspaces": "^4.0.1", "@npmcli/package-json": "^6.0.1", "@npmcli/promise-spawn": "^8.0.1", - "@npmcli/redact": "^2.0.1", + "@npmcli/redact": "^3.0.0", "@npmcli/run-script": "^8.1.0", "@sigstore/tuf": "^2.3.4", "abbrev": "^2.0.0", diff --git a/workspaces/arborist/package.json b/workspaces/arborist/package.json index f9ab90ad8fa4e..43d39699d4d45 100644 --- a/workspaces/arborist/package.json +++ b/workspaces/arborist/package.json @@ -12,7 +12,7 @@ "@npmcli/node-gyp": "^3.0.0", "@npmcli/package-json": "^6.0.1", "@npmcli/query": "^3.1.0", - "@npmcli/redact": "^2.0.0", + "@npmcli/redact": "^3.0.0", "@npmcli/run-script": "^8.1.0", "bin-links": "^4.0.4", "cacache": "^18.0.3",