mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Install: verify prebuilt binaries with Subresource Integrity check
This commit is contained in:
parent
3da258f6fb
commit
3b492ea423
@ -10,6 +10,8 @@ Requires libvips v8.12.1
|
|||||||
|
|
||||||
* Reduce minimum Linux ARM64v8 glibc requirement to 2.17.
|
* Reduce minimum Linux ARM64v8 glibc requirement to 2.17.
|
||||||
|
|
||||||
|
* Verify prebuilt binaries with a Subresource Integrity check.
|
||||||
|
|
||||||
* Standardise WebP `effort` option name, deprecate `reductionEffort`.
|
* Standardise WebP `effort` option name, deprecate `reductionEffort`.
|
||||||
|
|
||||||
* Standardise HEIF `effort` option name, deprecate `speed`.
|
* Standardise HEIF `effort` option name, deprecate `speed`.
|
||||||
|
@ -23,8 +23,9 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
|
|||||||
* Windows x64
|
* Windows x64
|
||||||
* Windows x86
|
* Windows x86
|
||||||
|
|
||||||
An ~7MB tarball containing libvips and its most commonly used dependencies
|
A ~7MB tarball containing libvips and its most commonly used dependencies
|
||||||
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
|
is downloaded via HTTPS, verified via Subresource Integrity
|
||||||
|
and decompressed into `node_modules/sharp/vendor` during `npm install`.
|
||||||
|
|
||||||
This provides support for the
|
This provides support for the
|
||||||
JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG (input) image formats.
|
JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG (input) image formats.
|
||||||
@ -78,7 +79,7 @@ npm install --platform=... --arch=... --arm-version=... sharp
|
|||||||
* `--platform`: one of `linux`, `linuxmusl`, `darwin` or `win32`.
|
* `--platform`: one of `linux`, `linuxmusl`, `darwin` or `win32`.
|
||||||
* `--arch`: one of `x64`, `ia32`, `arm` or `arm64`.
|
* `--arch`: one of `x64`, `ia32`, `arm` or `arm64`.
|
||||||
* `--arm-version`: one of `6`, `7` or `8` (`arm` defaults to `6`, `arm64` defaults to `8`).
|
* `--arm-version`: one of `6`, `7` or `8` (`arm` defaults to `6`, `arm64` defaults to `8`).
|
||||||
* `--sharp-install-force`: skip version compatibility checks.
|
* `--sharp-install-force`: skip version compatibility and Subresource Integrity checks.
|
||||||
|
|
||||||
These values can also be set via environment variables,
|
These values can also be set via environment variables,
|
||||||
`npm_config_platform`, `npm_config_arch`, `npm_config_arm_version`
|
`npm_config_platform`, `npm_config_arch`, `npm_config_arm_version`
|
||||||
|
@ -5,6 +5,7 @@ const os = require('os');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const zlib = require('zlib');
|
const zlib = require('zlib');
|
||||||
|
const { createHash } = require('crypto');
|
||||||
|
|
||||||
const detectLibc = require('detect-libc');
|
const detectLibc = require('detect-libc');
|
||||||
const semverLessThan = require('semver/functions/lt');
|
const semverLessThan = require('semver/functions/lt');
|
||||||
@ -55,6 +56,33 @@ const handleError = function (err) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const verifyIntegrity = function (platformAndArch) {
|
||||||
|
const expected = libvips.integrity(platformAndArch);
|
||||||
|
if (installationForced || !expected) {
|
||||||
|
libvips.log(`Integrity check skipped for ${platformAndArch}`);
|
||||||
|
return new stream.PassThrough();
|
||||||
|
}
|
||||||
|
const hash = createHash('sha512');
|
||||||
|
return new stream.Transform({
|
||||||
|
transform: function (chunk, _encoding, done) {
|
||||||
|
hash.update(chunk);
|
||||||
|
done(null, chunk);
|
||||||
|
},
|
||||||
|
flush: function (done) {
|
||||||
|
const digest = `sha512-${hash.digest('base64')}`;
|
||||||
|
if (expected !== digest) {
|
||||||
|
libvips.removeVendoredLibvips();
|
||||||
|
libvips.log(`Integrity expected: ${expected}`);
|
||||||
|
libvips.log(`Integrity received: ${digest}`);
|
||||||
|
done(new Error(`Integrity check failed for ${platformAndArch}`));
|
||||||
|
} else {
|
||||||
|
libvips.log(`Integrity check passed for ${platformAndArch}`);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const extractTarball = function (tarPath, platformAndArch) {
|
const extractTarball = function (tarPath, platformAndArch) {
|
||||||
const versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch);
|
const versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch);
|
||||||
libvips.mkdirSync(versionedVendorPath);
|
libvips.mkdirSync(versionedVendorPath);
|
||||||
@ -66,6 +94,7 @@ const extractTarball = function (tarPath, platformAndArch) {
|
|||||||
|
|
||||||
stream.pipeline(
|
stream.pipeline(
|
||||||
fs.createReadStream(tarPath),
|
fs.createReadStream(tarPath),
|
||||||
|
verifyIntegrity(platformAndArch),
|
||||||
new zlib.BrotliDecompress(),
|
new zlib.BrotliDecompress(),
|
||||||
tarFs.extract(versionedVendorPath, { ignore }),
|
tarFs.extract(versionedVendorPath, { ignore }),
|
||||||
function (err) {
|
function (err) {
|
||||||
|
@ -8,10 +8,11 @@ const semverCoerce = require('semver/functions/coerce');
|
|||||||
const semverGreaterThanOrEqualTo = require('semver/functions/gte');
|
const semverGreaterThanOrEqualTo = require('semver/functions/gte');
|
||||||
|
|
||||||
const platform = require('./platform');
|
const platform = require('./platform');
|
||||||
|
const { config } = require('../package.json');
|
||||||
|
|
||||||
const env = process.env;
|
const env = process.env;
|
||||||
const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
|
const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
|
||||||
require('../package.json').config.libvips;
|
config.libvips;
|
||||||
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
|
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
|
||||||
|
|
||||||
const spawnSyncOptions = {
|
const spawnSyncOptions = {
|
||||||
@ -19,6 +20,8 @@ const spawnSyncOptions = {
|
|||||||
shell: true
|
shell: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());
|
||||||
|
|
||||||
const mkdirSync = function (dirPath) {
|
const mkdirSync = function (dirPath) {
|
||||||
try {
|
try {
|
||||||
fs.mkdirSync(dirPath, { recursive: true });
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
@ -39,6 +42,10 @@ const cachePath = function () {
|
|||||||
return libvipsCachePath;
|
return libvipsCachePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const integrity = function (platformAndArch) {
|
||||||
|
return env[`npm_package_config_integrity_${platformAndArch.replace('-', '_')}`] || config.integrity[platformAndArch];
|
||||||
|
};
|
||||||
|
|
||||||
const log = function (item) {
|
const log = function (item) {
|
||||||
if (item instanceof Error) {
|
if (item instanceof Error) {
|
||||||
console.error(`sharp: Installation error: ${item.message}`);
|
console.error(`sharp: Installation error: ${item.message}`);
|
||||||
@ -67,10 +74,15 @@ const globalLibvipsVersion = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hasVendoredLibvips = function () {
|
const hasVendoredLibvips = function () {
|
||||||
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());
|
|
||||||
return fs.existsSync(vendorPath);
|
return fs.existsSync(vendorPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
const removeVendoredLibvips = function () {
|
||||||
|
const rm = fs.rmSync ? fs.rmSync : fs.rmdirSync;
|
||||||
|
rm(vendorPath, { recursive: true, maxRetries: 3, force: true });
|
||||||
|
};
|
||||||
|
|
||||||
const pkgConfigPath = function () {
|
const pkgConfigPath = function () {
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
|
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
|
||||||
@ -99,9 +111,11 @@ module.exports = {
|
|||||||
minimumLibvipsVersion,
|
minimumLibvipsVersion,
|
||||||
minimumLibvipsVersionLabelled,
|
minimumLibvipsVersionLabelled,
|
||||||
cachePath,
|
cachePath,
|
||||||
|
integrity,
|
||||||
log,
|
log,
|
||||||
globalLibvipsVersion,
|
globalLibvipsVersion,
|
||||||
hasVendoredLibvips,
|
hasVendoredLibvips,
|
||||||
|
removeVendoredLibvips,
|
||||||
pkgConfigPath,
|
pkgConfigPath,
|
||||||
useGlobalLibvips,
|
useGlobalLibvips,
|
||||||
mkdirSync
|
mkdirSync
|
||||||
|
13
package.json
13
package.json
@ -151,6 +151,19 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"config": {
|
"config": {
|
||||||
"libvips": "8.12.1",
|
"libvips": "8.12.1",
|
||||||
|
"integrity": {
|
||||||
|
"darwin-arm64v8": "sha512-Keb3wpyDLdYad3NFvh0qpXXpUDTK/QvUukMuQkUNoIdkMlVL73HI7TOW29UKxCS4C1xFn284miOh3UczvXwToQ==",
|
||||||
|
"darwin-x64": "sha512-Q0laQN+afWnXjlTLebPLnqLmDBKj8nN6g/VW2jfU3XfzUNqxFM39gCmddKdN5ekIfGka6tZAf8tTJuCqXotVUg==",
|
||||||
|
"linux-arm64v8": "sha512-gaUQsJReRd2GfUpQWRJnQtRAoLr+CABsGxyMOXk3m4ajp53rGr34l4Pcyet4r/XFOtF9jDvcAOKpyydZMv3qsg==",
|
||||||
|
"linux-armv6": "sha512-s+nQWZNsclbQopLxbuSAuHfluX31vEpsU020mNAS5VUE2Pemhn4CCGX79thyvXF0hlXxBsxAb18dzU7hXOz/PQ==",
|
||||||
|
"linux-armv7": "sha512-ra8Yp0FvO3b5dJQ2nqBxc1sBz3R8LQOno8MzNlubg6jWM5FIfGlfFe1yxXLKEXVgqOLf4ABjNPhX6kDQx5v+Kw==",
|
||||||
|
"linux-x64": "sha512-q2JPFq9FXCEIYKlfdhF70hEtpQquqGMnecYnTlB0Vcjyy1CY6aN+91difiEnvCSCHYzpoXDGv6C/ZNCMmdcD3Q==",
|
||||||
|
"linuxmusl-arm64v8": "sha512-JwG5rTa2UneL0uZCjbEmw0pRoQY5gnqtT2g9OkSuuGrlbHsrJuBGFVKEnqzox7C3g/yXgk0ePn4JaxefJXFaOQ==",
|
||||||
|
"linuxmusl-x64": "sha512-Quj7YHPNWZu+HvdQbreSxaWUc9gg1nYTrN/NQLkf7DmME5XrVxA2tkIy7+IIuFz2Tk7LfG8jLJ8c6ZKuYEMBSg==",
|
||||||
|
"win32-arm64v8": "sha512-QWchuNLKgZSpdh+Ixr9tve1XJFYRir9wFdPfwncJpOAAa5cbmJRMFReYa0iF4ncqO2MxTw6aHcDvfbg6bwaDtw==",
|
||||||
|
"win32-ia32": "sha512-/uyO2tP0okJOD1UjXIiGurwLlF7M+7MYaldQ9p2in+rUMZxdSuglEhgHS8tjMXHc7QeXMfGaGSW0bxk2Yu1L0A==",
|
||||||
|
"win32-x64": "sha512-PF+ZcWnDYbsQXWaaJ3yngWD+E0bPq1I7S+xeqms8frrA8trPdQxC8Jjvw4Aaylynn9YMXzPYY7RAeIzopOqFxA=="
|
||||||
|
},
|
||||||
"runtime": "napi",
|
"runtime": "napi",
|
||||||
"target": 5
|
"target": 5
|
||||||
},
|
},
|
||||||
|
@ -76,6 +76,27 @@ describe('libvips binaries', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('integrity', function () {
|
||||||
|
it('reads value from environment variable', function () {
|
||||||
|
const prev = process.env.npm_package_config_integrity_platform_arch;
|
||||||
|
process.env.npm_package_config_integrity_platform_arch = 'sha512-test';
|
||||||
|
|
||||||
|
const integrity = libvips.integrity('platform-arch');
|
||||||
|
assert.strictEqual('sha512-test', integrity);
|
||||||
|
|
||||||
|
process.env.npm_package_config_integrity_platform_arch = prev;
|
||||||
|
});
|
||||||
|
it('reads value from package.json', function () {
|
||||||
|
const prev = process.env.npm_package_config_integrity_linux_x64;
|
||||||
|
delete process.env.npm_package_config_integrity_linux_x64;
|
||||||
|
|
||||||
|
const integrity = libvips.integrity('linux-x64');
|
||||||
|
assert.strictEqual('sha512-', integrity.substr(0, 7));
|
||||||
|
|
||||||
|
process.env.npm_package_config_integrity_linux_x64 = prev;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('safe directory creation', function () {
|
describe('safe directory creation', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
mockFS({
|
mockFS({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user