@salesforce/plugin-trust
Version:
validate a digital signature for a npm package
102 lines • 3.75 kB
JavaScript
import { Lifecycle } from '@salesforce/core';
import { NpmModule } from '../shared/npmCommand.js';
export const hook = (options) => Promise.all([registryCheck(options)]);
/**
* Validates that a string is a well-formed HTTP/HTTPS URL
*
* @param urlString - The URL string to validate
* @returns true if valid, false otherwise
*/
const isValidRegistryUrl = (urlString) => {
try {
const url = new URL(urlString);
// Only allow http/https protocols to prevent protocol-based attacks
return url.protocol === 'http:' || url.protocol === 'https:';
}
catch {
return false;
}
};
/**
* Sanitizes a registry URL to prevent command injection
* Validates URL format and ensures no shell metacharacters
*
* @param registryUrl - The registry URL to sanitize
* @returns The sanitized URL or undefined if invalid
*/
const sanitizeRegistryUrl = (registryUrl) => {
if (!registryUrl || typeof registryUrl !== 'string') {
return undefined;
}
// Trim whitespace
const trimmed = registryUrl.trim();
// Check for shell metacharacters that could enable command injection
const dangerousChars = /[;&|`$(){}[\]<>\\'"]/;
if (dangerousChars.test(trimmed)) {
return undefined;
}
// Validate as proper URL
if (!isValidRegistryUrl(trimmed)) {
return undefined;
}
return trimmed;
};
const registryCheck = async (options) => {
const pluginName = '@salesforce/plugin-trust';
// find npm install
const npm = new NpmModule('');
const env = process.env.npm_config_registry ?? process.env.NPM_CONFIG_REGISTRY;
let sanitizedEnv;
if (env) {
sanitizedEnv = sanitizeRegistryUrl(env);
if (sanitizedEnv) {
options.doctor.addSuggestion(`using npm registry ${sanitizedEnv} from environment variable`);
}
else {
options.doctor.addSuggestion(`WARNING: npm registry environment variable contains invalid or potentially unsafe URL: ${env}`);
}
}
const config = npm.run('config get registry').stdout.trim();
let sanitizedConfig;
if (config) {
sanitizedConfig = sanitizeRegistryUrl(config);
if (sanitizedConfig) {
options.doctor.addSuggestion(`using npm registry ${sanitizedConfig} from npm config`);
}
else {
options.doctor.addSuggestion(`WARNING: npm config registry contains invalid or potentially unsafe URL: ${config}`);
}
}
await Promise.all([
...new Set([
// npm and yarn registries
'https://registry.npmjs.org',
'https://registry.yarnpkg.com',
sanitizedEnv ?? sanitizedConfig ?? '',
]),
]
// incase customRegistry is undefined, prevent printing an extra line
.filter((u) => u)
.map(async (url) => {
try {
const results = npm.ping(url);
// timeout after 5000ms, error
if (!results || results.time > 5000) {
// to trigger the catch/fail below
throw Error;
}
await Lifecycle.getInstance().emit('Doctor:diagnostic', {
testName: `[${pluginName}] can ping: ${url}`,
status: 'pass',
});
}
catch (e) {
await Lifecycle.getInstance().emit('Doctor:diagnostic', {
testName: `[${pluginName}] can't ping: ${url}`,
status: 'fail',
});
options.doctor.addSuggestion(`Cannot ping ${url} - potential network configuration error, check proxies, firewalls, environment variables. Verify this by running 'npm ping ${url}'`);
}
}));
};
//# sourceMappingURL=diagnostics.js.map