ensure workspace pkgs aren't fetched from registry
refs SEC-4437 flag = none [pin-commit-respondus_lockdown_browser=8746ff2ccfd6e9ac1d7280687c88e9fa67c0f693] [pin-commit-multiple_root_accounts=7b11533084764b3a34f390e9a75df26e9dd871f9] rationale in the script file and more context in the accompanying ticket ~ test plan ~ edit package.json and modify one of the explicit workspace dependencies such as @instructure/ready to have a specifier other than "*", run the script and verify it exits with 1 yarn --silent workspaces info --json | \ node script/yarn-validate-workspace-deps.js 2>/dev/null Change-Id: I6624ada67a21d433477a7ad4d36acf5801853b7a Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/282948 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Ben Rinaca <brinaca@instructure.com> QA-Review: Ahmad Amireh <ahmad@instructure.com> Product-Review: Ahmad Amireh <ahmad@instructure.com>
This commit is contained in:
parent
f85c04643f
commit
4d0de671d3
|
@ -30,6 +30,7 @@ ruby script/stylelint
|
|||
ruby script/rlint --no-fail-on-offense
|
||||
[ "${SKIP_ESLINT-}" != "true" ] && ruby script/eslint
|
||||
ruby script/lint_commit_message
|
||||
node script/yarn-validate-workspace-deps.js 2>/dev/null < <(yarn --silent workspaces info --json)
|
||||
|
||||
gergich status
|
||||
echo "LINTER OK!"
|
||||
|
|
|
@ -24,12 +24,12 @@
|
|||
"@instructure/brandable_css": "^3",
|
||||
"@instructure/canvas-theme": "^7",
|
||||
"@instructure/debounce": "^7",
|
||||
"@instructure/js-utils": ">=1",
|
||||
"@instructure/js-utils": "*",
|
||||
"@instructure/media-capture": "~8.0.1-rc.11",
|
||||
"@instructure/outcomes-ui": "^2.0.0",
|
||||
"@instructure/react-crop": "^5.0.1",
|
||||
"@instructure/reactour": "https://github.com/instructure/reactour#b908434fe544703e26bc67c67c4111252c401f92",
|
||||
"@instructure/ready": ">=1",
|
||||
"@instructure/ready": "*",
|
||||
"@instructure/ui-a11y-content": "^7",
|
||||
"@instructure/ui-a11y-utils": "^7",
|
||||
"@instructure/ui-alerts": "^7",
|
||||
|
@ -123,7 +123,7 @@
|
|||
"immer": "^3",
|
||||
"immutability-helper": "^3",
|
||||
"immutable": "^3.8.2",
|
||||
"intl-polyfills": "^1",
|
||||
"intl-polyfills": "*",
|
||||
"is-valid-domain": "^0.0.11",
|
||||
"jquery": "https://github.com/instructure/jquery.git#1.7.2-with-AMD-and-CommonJS",
|
||||
"jquery-getscrollbarwidth": "^1.0.0",
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"homepage": "https://github.com/instructure/canvas-lms#readme",
|
||||
"dependencies": {
|
||||
"@instructure/canvas-theme": "7",
|
||||
"@instructure/k5uploader": "^1.0.0",
|
||||
"@instructure/k5uploader": "*",
|
||||
"@instructure/media-capture": "~8.0.1-rc.11",
|
||||
"@instructure/ui-a11y-content": "^7",
|
||||
"@instructure/ui-alerts": "^7",
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
"dependencies": {
|
||||
"@instructure/babel-plugin-themeable-styles": "7",
|
||||
"@instructure/canvas-theme": "7",
|
||||
"@instructure/js-utils": ">=1",
|
||||
"@instructure/js-utils": "*",
|
||||
"@instructure/ui-a11y-content": "7",
|
||||
"@instructure/ui-a11y-utils": "7",
|
||||
"@instructure/ui-alerts": "7",
|
||||
|
|
|
@ -58,9 +58,9 @@
|
|||
"instrument": false
|
||||
},
|
||||
"dependencies": {
|
||||
"@instructure/canvas-media": ">=1.0.0",
|
||||
"@instructure/canvas-media": "*",
|
||||
"@instructure/canvas-theme": "7",
|
||||
"@instructure/k5uploader": ">=1",
|
||||
"@instructure/k5uploader": "*",
|
||||
"@instructure/media-capture": "~8.0.1-rc.11",
|
||||
"@instructure/ui-a11y-content": "7",
|
||||
"@instructure/ui-alerts": "7",
|
||||
|
@ -113,7 +113,7 @@
|
|||
"classnames": "^2.2.5",
|
||||
"format-message": "^6",
|
||||
"format-message-generate-id": "^6",
|
||||
"get-cookie": "1",
|
||||
"get-cookie": "*",
|
||||
"i18n-js": "^3",
|
||||
"i18nliner": "~0.2.0",
|
||||
"isomorphic-fetch": "2.2.1",
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"datetime-moment-parser": "1.0.0",
|
||||
"datetime-moment-parser": "*",
|
||||
"moment": "^2.10.6",
|
||||
"moment-timezone": "^0.5.13",
|
||||
"timezone": "https://registry.npmjs.org/@brentburgoyne/timezone/-/timezone-1.0.24.tgz"
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// yarn-validate-workspace-deps.js: ensure that any explicit dependency on a
|
||||
// local workspace package is specified using the free-range specifier "*" so
|
||||
// that yarn cannot be tricked into retrieving the package from a remote
|
||||
// registry in case the package's local version can no longer satisfy the
|
||||
// requested one
|
||||
//
|
||||
// USAGE:
|
||||
//
|
||||
// yarn --silent workspaces info --json |
|
||||
// node script/yarn-validate-workspace-deps.js
|
||||
//
|
||||
// consider the following case:
|
||||
//
|
||||
// // file: package.json
|
||||
// {
|
||||
// "name": "canvas-lms",
|
||||
// "workspaces": {
|
||||
// "packages": [ "packages/*" ]
|
||||
// },
|
||||
// "dependencies": {
|
||||
// "get-cookie": "^1"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // file: packages/get-cookie/package.json
|
||||
// {
|
||||
// "name": "get-cookie",
|
||||
// "version": "2.0"
|
||||
// }
|
||||
//
|
||||
// since "get-cookie" got bumped up to 2 but consumer package.json is still
|
||||
// requesting ^1, yarn will attempt to retrieve get-cookie@^1 from the remote
|
||||
// registry, and we don't necessarily own that, which is a potential attack
|
||||
// vector
|
||||
//
|
||||
// instead, we can guarantee that yarn will use whatever version the workspace
|
||||
// package is providing by using a free specifier like "*"
|
||||
//
|
||||
// see SEC-4437
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const glob = require('glob')
|
||||
const root = path.resolve(__dirname, '..')
|
||||
|
||||
async function main() {
|
||||
const workspaces = await parseWorkspacesFromStdin()
|
||||
|
||||
let errors = []
|
||||
|
||||
for (const pkgfile of scanPkgfiles(require.resolve('../package.json'))) {
|
||||
errors = errors.concat( validate({ pkgfile, workspaces, errors }) )
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
process.exitCode = 1
|
||||
|
||||
console.log('dependencies listed below must have a specifier of "*"')
|
||||
console.log('---')
|
||||
|
||||
for (const error of errors) {
|
||||
console.log("%s:%s", path.relative(root, error.pkgfile), error.dep)
|
||||
}
|
||||
|
||||
console.log('---')
|
||||
}
|
||||
}
|
||||
|
||||
function scanPkgfiles(rootpkgfile) {
|
||||
let pkgfiles = [ rootpkgfile ]
|
||||
|
||||
for (const pattern of require(rootpkgfile).workspaces.packages || []) {
|
||||
pkgfiles = pkgfiles.concat(
|
||||
glob.sync(`${pattern}/package.json`, {
|
||||
cwd: path.dirname(rootpkgfile),
|
||||
absolute: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return pkgfiles
|
||||
}
|
||||
|
||||
function validate({ pkgfile, workspaces }) {
|
||||
const { dependencies = {} } = require(pkgfile)
|
||||
const errors = []
|
||||
|
||||
let depcount = 0
|
||||
|
||||
for (const [dep, version] of Object.entries(dependencies)) {
|
||||
if (dep in workspaces) {
|
||||
depcount += 1
|
||||
|
||||
if (version !== '*') {
|
||||
errors.push({ pkgfile, dep })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (depcount > 0) {
|
||||
console.error('found %d workspace package dependencies and %d errors -- "%s"',
|
||||
depcount,
|
||||
errors.length,
|
||||
path.relative(root, pkgfile),
|
||||
)
|
||||
}
|
||||
else {
|
||||
console.error('found no workspace package dependencies -- "%s"',
|
||||
path.relative(root, pkgfile)
|
||||
)
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
async function read(stream) {
|
||||
const chunks = []
|
||||
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
return Buffer.concat(chunks).toString('utf8')
|
||||
}
|
||||
|
||||
async function parseWorkspacesFromStdin() {
|
||||
const buffer = await read(process.stdin)
|
||||
const parsed = JSON.parse(buffer)
|
||||
|
||||
// yarn 1.19.1, which is what jenkins is on, has a different output structure
|
||||
// that looks like this:
|
||||
//
|
||||
// {
|
||||
// "type": "log",
|
||||
// "data": "{\"json\":\"blob\"}"
|
||||
// }
|
||||
//
|
||||
// while yarn 1.22.1 directly emits the data with no metadata:
|
||||
//
|
||||
// {
|
||||
// "json": "blob"
|
||||
// }
|
||||
//
|
||||
if (parsed.type === 'log' && parsed.hasOwnProperty('data')) {
|
||||
return JSON.parse(parsed.data)
|
||||
}
|
||||
else {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
Loading…
Reference in New Issue