mirror of
https://github.com/hanzoai/esbuild.git
synced 2026-09-24 16:58:50 +00:00
security: add integrity checks to the Deno API
This commit is contained in:
@@ -8,6 +8,14 @@
|
||||
|
||||
Thanks to [@dellalibera](https://github.com/dellalibera) for reporting this issue.
|
||||
|
||||
* Add integrity checks to the Deno API ([GHSA-gv7w-rqvm-qjhr](https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr))
|
||||
|
||||
The previous release of esbuild added integrity checks to esbuild's npm install script. This release also adds integrity checks to esbuild's Deno install script. Now esbuild's Deno API will also fail with an error if the downloaded esbuild binary contains something other than the expected content.
|
||||
|
||||
Note that esbuild's Deno API installs from `registry.npmjs.org` by default, but allows the `NPM_CONFIG_REGISTRY` environment variable to override this with a custom package registry. This change means that the esbuild executable served by `NPM_CONFIG_REGISTRY` must now match the expected content.
|
||||
|
||||
Thanks to [@sondt99](https://github.com/sondt99) for reporting this issue.
|
||||
|
||||
* Avoid inlining `using` and `await using` declarations ([#4482](https://github.com/evanw/esbuild/issues/4482))
|
||||
|
||||
Previously esbuild's minifier sometimes incorrectly inlined `using` and `await using` declarations into subsequent uses of that declaration, which then fails to dispose of the resource correctly. This bug happened because inlining was done for `let` and `const` declarations by avoiding doing it for `var` declarations, which no longer worked when more declaration types were added. Here's an example:
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as ourselves from "./mod"
|
||||
import * as denoflate from "https://deno.land/x/[email protected]/mod.ts"
|
||||
|
||||
declare const ESBUILD_VERSION: string
|
||||
declare const ESBUILD_BINARY_HASHES: Record<string, string>
|
||||
|
||||
export let version = ESBUILD_VERSION
|
||||
|
||||
@@ -59,6 +60,15 @@ export const initialize: typeof types.initialize = async (options) => {
|
||||
initializeWasCalled = true
|
||||
}
|
||||
|
||||
async function binaryIntegrityCheck(pkg: string, subpath: string, bytes: Uint8Array): Promise<void> {
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', bytes as Uint8Array<ArrayBuffer>)
|
||||
const hash = Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('')
|
||||
const key = `${pkg}/${subpath}`
|
||||
const expected = ESBUILD_BINARY_HASHES[key]
|
||||
if (!expected) throw new Error(`Missing hash for "${key}"`)
|
||||
if (hash !== expected) throw new Error(`"${hash.slice(0, 8)}..." doesn't match "${expected.slice(0, 8)}..." for "${pkg}"`)
|
||||
}
|
||||
|
||||
async function installFromNPM(name: string, subpath: string): Promise<string> {
|
||||
const { finalPath, finalDir } = getCachePath(name)
|
||||
try {
|
||||
@@ -71,6 +81,7 @@ async function installFromNPM(name: string, subpath: string): Promise<string> {
|
||||
const url = `${npmRegistry}/${name}/-/${name.replace("@esbuild/", "")}-${version}.tgz`
|
||||
const buffer = await fetch(url).then(r => r.arrayBuffer())
|
||||
const executable = extractFileFromTarGzip(new Uint8Array(buffer), subpath)
|
||||
await binaryIntegrityCheck(name, subpath, executable)
|
||||
|
||||
await Deno.mkdir(finalDir, {
|
||||
recursive: true,
|
||||
|
||||
@@ -230,7 +230,7 @@ function binaryIntegrityCheck(pkg: string, subpath: string, bytes: Uint8Array):
|
||||
const key = `${pkg}/${subpath}`
|
||||
const expected = packageJSON['esbuild.binaryHashes'][key]
|
||||
if (!expected) throw new Error(`Missing hash for "${key}"`)
|
||||
if (hash !== expected) throw new Error(`"${hash.slice(0, 8)}..." doesn't match "${expected.slice(0, 8)}..."`)
|
||||
if (hash !== expected) throw new Error(`"${hash.slice(0, 8)}..." doesn't match "${expected.slice(0, 8)}..." for "${pkg}"`)
|
||||
}
|
||||
|
||||
async function downloadDirectlyFromNPM(pkg: string, subpath: string, binPath: string): Promise<void> {
|
||||
|
||||
@@ -273,12 +273,14 @@ exports.buildWasmLib = async (esbuildPath) => {
|
||||
|
||||
const buildDenoLib = async (esbuildPath) => {
|
||||
// Generate "deno/mod.js"
|
||||
const packageJSON = JSON.parse(fs.readFileSync(path.join(npmDir, 'package.json'), 'utf8'))
|
||||
childProcess.execFileSync(esbuildPath, [
|
||||
path.join(repoDir, 'lib', 'deno', 'mod.ts'),
|
||||
'--bundle',
|
||||
'--outfile=' + path.join(denoDir, 'mod.js'),
|
||||
'--target=' + denoTarget,
|
||||
'--define:ESBUILD_VERSION=' + JSON.stringify(version),
|
||||
'--define:ESBUILD_BINARY_HASHES=' + JSON.stringify(packageJSON['esbuild.binaryHashes'], null, 2),
|
||||
'--platform=neutral',
|
||||
'--log-level=warning',
|
||||
'--banner:js=/// <reference types="./mod.d.ts" />',
|
||||
|
||||
Reference in New Issue
Block a user