mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 02:30:12 +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.
|
||||
|
||||
* Verify prebuilt binaries with a Subresource Integrity check.
|
||||
|
||||
* Standardise WebP `effort` option name, deprecate `reductionEffort`.
|
||||
|
||||
* 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 x86
|
||||
|
||||
An ~7MB tarball containing libvips and its most commonly used dependencies
|
||||
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
|
||||
A ~7MB tarball containing libvips and its most commonly used dependencies
|
||||
is downloaded via HTTPS, verified via Subresource Integrity
|
||||
and decompressed into `node_modules/sharp/vendor` during `npm install`.
|
||||
|
||||
This provides support for the
|
||||
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`.
|
||||
* `--arch`: one of `x64`, `ia32`, `arm` or `arm64`.
|
||||
* `--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,
|
||||
`npm_config_platform`, `npm_config_arch`, `npm_config_arm_version`
|
||||
|
@ -5,6 +5,7 @@ const os = require('os');
|
||||
const path = require('path');
|
||||
const stream = require('stream');
|
||||
const zlib = require('zlib');
|
||||
const { createHash } = require('crypto');
|
||||
|
||||
const detectLibc = require('detect-libc');
|
||||
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 versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch);
|
||||
libvips.mkdirSync(versionedVendorPath);
|
||||
@ -66,6 +94,7 @@ const extractTarball = function (tarPath, platformAndArch) {
|
||||
|
||||
stream.pipeline(
|
||||
fs.createReadStream(tarPath),
|
||||
verifyIntegrity(platformAndArch),
|
||||
new zlib.BrotliDecompress(),
|
||||
tarFs.extract(versionedVendorPath, { ignore }),
|
||||
function (err) {
|
||||
|
@ -8,10 +8,11 @@ const semverCoerce = require('semver/functions/coerce');
|
||||
const semverGreaterThanOrEqualTo = require('semver/functions/gte');
|
||||
|
||||
const platform = require('./platform');
|
||||
const { config } = require('../package.json');
|
||||
|
||||
const env = process.env;
|
||||
const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
|
||||
require('../package.json').config.libvips;
|
||||
config.libvips;
|
||||
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
|
||||
|
||||
const spawnSyncOptions = {
|
||||
@ -19,6 +20,8 @@ const spawnSyncOptions = {
|
||||
shell: true
|
||||
};
|
||||
|
||||
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());
|
||||
|
||||
const mkdirSync = function (dirPath) {
|
||||
try {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
@ -39,6 +42,10 @@ const cachePath = function () {
|
||||
return libvipsCachePath;
|
||||
};
|
||||
|
||||
const integrity = function (platformAndArch) {
|
||||
return env[`npm_package_config_integrity_${platformAndArch.replace('-', '_')}`] || config.integrity[platformAndArch];
|
||||
};
|
||||
|
||||
const log = function (item) {
|
||||
if (item instanceof Error) {
|
||||
console.error(`sharp: Installation error: ${item.message}`);
|
||||
@ -67,10 +74,15 @@ const globalLibvipsVersion = function () {
|
||||
};
|
||||
|
||||
const hasVendoredLibvips = function () {
|
||||
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());
|
||||
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 () {
|
||||
if (process.platform !== 'win32') {
|
||||
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,
|
||||
minimumLibvipsVersionLabelled,
|
||||
cachePath,
|
||||
integrity,
|
||||
log,
|
||||
globalLibvipsVersion,
|
||||
hasVendoredLibvips,
|
||||
removeVendoredLibvips,
|
||||
pkgConfigPath,
|
||||
useGlobalLibvips,
|
||||
mkdirSync
|
||||
|
13
package.json
13
package.json
@ -151,6 +151,19 @@
|
||||
"license": "Apache-2.0",
|
||||
"config": {
|
||||
"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",
|
||||
"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 () {
|
||||
before(function () {
|
||||
mockFS({
|
||||
|
Loading…
x
Reference in New Issue
Block a user