The ValidateUrl class validates that package URLs exactly match a whitelist of allowed URLs.
Creates a new URL validator instance.
/**
* Creates a new URL whitelist validator
* @param {Object} options - Validator configuration
* @param {Object} options.packages - Package object from parsed lockfile
* @throws {Error} If packages is not provided or not an object
*/
class ValidateUrl {
constructor(options: {
packages: Object;
});
}Requirements: The packages parameter must be an object (typically from ParseLockfile.parseSync().object). Throws an error if not provided or not an object.
Usage Examples:
const { ValidateUrl, ParseLockfile } = require('lockfile-lint-api');
// Parse lockfile first
const parser = new ParseLockfile({ lockfilePath: './package-lock.json' });
const lockfile = parser.parseSync();
// Create validator
const validator = new ValidateUrl({ packages: lockfile.object });
// Error cases
try {
new ValidateUrl(); // Throws: 'expecting an object passed to validator constructor'
} catch (error) {
// Handle error
}
try {
new ValidateUrl({ packages: null }); // Throws: 'expecting an object passed to validator constructor'
} catch (error) {
// Handle error
}Validates that all packages in the lockfile resolve to whitelisted URLs.
/**
* Validates all packages against allowed URL whitelist
* @param {string[]} allowedUrls - Array of exact URL strings that are allowed
* @param {Object} [options] - Reserved for future use (currently unused)
* @returns {Object} Validation result with type ('success' or 'error') and errors array
* @throws {Error} If allowedUrls is not an array
*/
validate(allowedUrls: string[], options?: Object): {
type: 'success' | 'error';
errors: Array<{
message: string;
package: string;
}>;
}Note: The options parameter is currently unused but reserved for future functionality. It exists for API consistency with other validators.
URL Matching:
resolved URL and whitelistBehavior:
resolved field are automatically skipped (e.g., local filesystem packages)Usage Examples:
const { ValidateUrl, ParseLockfile } = require('lockfile-lint-api');
// Basic usage
const parser = new ParseLockfile({ lockfilePath: './package-lock.json' });
const lockfile = parser.parseSync();
const validator = new ValidateUrl({ packages: lockfile.object });
// Whitelist specific package URLs
const allowedUrls = [
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
'https://registry.npmjs.org/express/-/express-4.18.2.tgz',
'https://registry.npmjs.org/react/-/react-18.2.0.tgz'
];
const result = validator.validate(allowedUrls);
if (result.type === 'success') {
console.log('All packages are from whitelisted URLs');
} else {
console.error('URL validation failed:');
result.errors.forEach(error => {
console.error(` ${error.package}: ${error.message}`);
});
}Extract URLs from Existing Lockfile:
const { ValidateUrl, ParseLockfile } = require('lockfile-lint-api');
// Create whitelist from existing trusted lockfile
function extractAllowedUrls(lockfilePath) {
const parser = new ParseLockfile({ lockfilePath });
const lockfile = parser.parseSync();
const urls = [];
for (const [packageName, metadata] of Object.entries(lockfile.object)) {
if (metadata.resolved) {
urls.push(metadata.resolved);
}
}
return urls;
}
// Generate whitelist from trusted baseline
const allowedUrls = extractAllowedUrls('./baseline-package-lock.json');
// Validate new lockfile against baseline
const parser = new ParseLockfile({ lockfilePath: './package-lock.json' });
const lockfile = parser.parseSync();
const validator = new ValidateUrl({ packages: lockfile.object });
const result = validator.validate(allowedUrls);
if (result.type === 'error') {
console.error('New packages detected:');
result.errors.forEach(error => console.error(error.message));
}CI/CD Strict Validation:
const { ValidateUrl, ParseLockfile } = require('lockfile-lint-api');
const fs = require('fs');
function strictUrlValidation(lockfilePath, whitelistPath) {
try {
// Load URL whitelist
const allowedUrls = JSON.parse(fs.readFileSync(whitelistPath, 'utf-8'));
const parser = new ParseLockfile({ lockfilePath });
const lockfile = parser.parseSync();
const validator = new ValidateUrl({ packages: lockfile.object });
const result = validator.validate(allowedUrls);
if (result.type === 'error') {
console.error('Build failed: Unauthorized package URLs detected!');
result.errors.forEach(error => {
console.error(` Package: ${error.package}`);
console.error(` ${error.message}`);
});
return false;
}
console.log('✓ All package URLs are whitelisted');
return true;
} catch (error) {
console.error('Validation error:', error.message);
return false;
}
}
// whitelist.json contains array of approved URLs
if (!strictUrlValidation('./package-lock.json', './whitelist.json')) {
process.exit(1);
}Validates a single package against allowed URLs.
/**
* Validates a single package against allowed URL whitelist
* @param {string} packageName - Package key from the lockfile (e.g., 'lodash@4.17.21')
* @param {string[]} allowedUrls - Array of exact URL strings that are allowed
* @returns {boolean} True if package URL is in whitelist or package has no resolved URL, false otherwise
*/
validateSingle(packageName: string, allowedUrls: string[]): boolean;Behavior:
true if the package's resolved URL exactly matches any URL in the whitelisttrue if the package has no resolved field (e.g., local filesystem packages)false if the package's URL is not in the whitelistUsage Examples:
const { ValidateUrl, ParseLockfile } = require('lockfile-lint-api');
const parser = new ParseLockfile({ lockfilePath: './package-lock.json' });
const lockfile = parser.parseSync();
const validator = new ValidateUrl({ packages: lockfile.object });
const allowedUrls = [
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz',
'https://registry.npmjs.org/express/-/express-4.18.2.tgz'
];
// Validate specific packages
const isLodashValid = validator.validateSingle('lodash@4.17.21', allowedUrls);
const isReactValid = validator.validateSingle('react@18.2.0', allowedUrls);
if (!isLodashValid) {
console.error('lodash URL is not whitelisted');
}
if (!isReactValid) {
console.error('react URL is not whitelisted');
}
// Check critical packages
const criticalPackages = ['lodash@4.17.21', 'express@4.18.2'];
const unauthorized = criticalPackages.filter(
pkg => !validator.validateSingle(pkg, allowedUrls)
);
if (unauthorized.length > 0) {
console.error('Unauthorized critical packages:', unauthorized);
process.exit(1);
}The validate() method returns a consistent result object:
interface UrlValidationResult {
type: 'success' | 'error';
errors: UrlValidationError[];
}
interface UrlValidationError {
message: string; // Detailed error message with allowed URLs and actual URL
package: string; // Package name that failed validation
}Success Example:
{
type: 'success',
errors: []
}Error Example:
{
type: 'error',
errors: [
{
message: 'detected invalid url(s) for package: unknown-package@1.0.0\n expected: https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz\n actual: https://malicious-registry.com/unknown-package/-/unknown-package-1.0.0.tgz\n',
package: 'unknown-package@1.0.0'
}
]
}URL whitelisting provides the strictest form of package validation:
High-Security Environment Example:
const { ValidateUrl, ParseLockfile } = require('lockfile-lint-api');
const crypto = require('crypto');
const fs = require('fs');
function generateUrlWhitelist(lockfilePath, outputPath) {
const parser = new ParseLockfile({ lockfilePath });
const lockfile = parser.parseSync();
const whitelist = {
generated: new Date().toISOString(),
hash: '',
urls: []
};
// Extract all URLs
for (const metadata of Object.values(lockfile.object)) {
if (metadata.resolved) {
whitelist.urls.push(metadata.resolved);
}
}
// Generate hash for integrity
const whitelistJson = JSON.stringify(whitelist.urls);
whitelist.hash = crypto.createHash('sha256').update(whitelistJson).digest('hex');
fs.writeFileSync(outputPath, JSON.stringify(whitelist, null, 2));
console.log(`Generated whitelist with ${whitelist.urls.length} URLs`);
console.log(`Whitelist hash: ${whitelist.hash}`);
}
function validateAgainstWhitelist(lockfilePath, whitelistPath) {
const whitelist = JSON.parse(fs.readFileSync(whitelistPath, 'utf-8'));
// Verify whitelist integrity
const whitelistJson = JSON.stringify(whitelist.urls);
const currentHash = crypto.createHash('sha256').update(whitelistJson).digest('hex');
if (currentHash !== whitelist.hash) {
console.error('Whitelist integrity check failed!');
return false;
}
const parser = new ParseLockfile({ lockfilePath });
const lockfile = parser.parseSync();
const validator = new ValidateUrl({ packages: lockfile.object });
const result = validator.validate(whitelist.urls);
return result.type === 'success';
}
// Generate baseline
// generateUrlWhitelist('./approved-package-lock.json', './url-whitelist.json');
// Validate against baseline
if (!validateAgainstWhitelist('./package-lock.json', './url-whitelist.json')) {
console.error('Package URLs have changed from approved baseline!');
process.exit(1);
}ValidateUrl is the most restrictive validator:
ValidateHost:
ValidateScheme:
ValidateUrl:
Example Comparison:
const { ValidateUrl, ValidateHost, ParseLockfile } = require('lockfile-lint-api');
const parser = new ParseLockfile({ lockfilePath: './package-lock.json' });
const lockfile = parser.parseSync();
// ValidateHost: Allow any package from npm
const hostValidator = new ValidateHost({ packages: lockfile.object });
const hostResult = hostValidator.validate(['npm']);
// ✓ lodash-4.17.21 from npm - PASS
// ✓ lodash-4.17.22 from npm - PASS (newer version)
// ValidateUrl: Allow only specific URLs
const urlValidator = new ValidateUrl({ packages: lockfile.object });
const urlResult = urlValidator.validate([
'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz'
]);
// ✓ lodash-4.17.21 from npm - PASS
// ✗ lodash-4.17.22 from npm - FAIL (different URL)Constructor Errors:
// Throws: 'expecting an object passed to validator constructor'
const validator = new ValidateUrl(); // Missing packages
const validator = new ValidateUrl({ packages: null }); // Invalid type
const validator = new ValidateUrl({ packages: [] }); // Array instead of objectValidation Errors:
const validator = new ValidateUrl({ packages: lockfile.object });
// Throws: 'validate method requires an array'
validator.validate('https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz'); // String
validator.validate({ url: 'https://...' }); // Object===) for URL comparisonresolved field)const { ParseLockfile } = require('lockfile-lint-api');
const fs = require('fs');
function generateWhitelist(approvedLockfile, outputFile) {
const parser = new ParseLockfile({ lockfilePath: approvedLockfile });
const lockfile = parser.parseSync();
const urls = Object.values(lockfile.object)
.filter(pkg => pkg.resolved)
.map(pkg => pkg.resolved);
fs.writeFileSync(outputFile, JSON.stringify(urls, null, 2));
console.log(`Generated whitelist with ${urls.length} URLs`);
}
generateWhitelist('./approved-package-lock.json', './url-whitelist.json');const { ValidateUrl, ParseLockfile } = require('lockfile-lint-api');
const fs = require('fs');
function validateWithWhitelist(lockfilePath, whitelistPath) {
const whitelist = JSON.parse(fs.readFileSync(whitelistPath, 'utf-8'));
const parser = new ParseLockfile({ lockfilePath });
const lockfile = parser.parseSync();
const validator = new ValidateUrl({ packages: lockfile.object });
return validator.validate(whitelist);
}
const result = validateWithWhitelist('./package-lock.json', './url-whitelist.json');
if (result.type === 'error') {
console.error('Validation failed:');
result.errors.forEach(error => console.error(error.message));
process.exit(1);
}const { ParseLockfile } = require('lockfile-lint-api');
const fs = require('fs');
function updateWhitelist(lockfilePath, whitelistPath) {
const parser = new ParseLockfile({ lockfilePath });
const lockfile = parser.parseSync();
const newUrls = Object.values(lockfile.object)
.filter(pkg => pkg.resolved)
.map(pkg => pkg.resolved);
const existingUrls = JSON.parse(fs.readFileSync(whitelistPath, 'utf-8'));
// Find new URLs
const addedUrls = newUrls.filter(url => !existingUrls.includes(url));
if (addedUrls.length > 0) {
console.log(`Found ${addedUrls.length} new URLs:`);
addedUrls.forEach(url => console.log(` + ${url}`));
// Merge and save
const mergedUrls = [...new Set([...existingUrls, ...addedUrls])];
fs.writeFileSync(whitelistPath, JSON.stringify(mergedUrls, null, 2));
console.log('Whitelist updated');
} else {
console.log('No new URLs to add');
}
}
updateWhitelist('./package-lock.json', './url-whitelist.json');