or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

validate-package-names.mddocs/reference/

Package Name Validation

The ValidatePackageNames class validates that resolved URLs match package names, detecting potential name confusion attacks where a package's resolved URL doesn't match its declared name.

Capabilities

ValidatePackageNames Constructor

Creates a new package name validator instance.

/**
 * Creates a new package name 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 ValidatePackageNames {
  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 { ValidatePackageNames, 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 ValidatePackageNames({ packages: lockfile.object });

Validate Package Names

Validates that all packages' resolved URLs match their declared names.

/**
 * Validates package name consistency between declared name and resolved URL
 * @param {string[]} [packageNameAliases] - Optional array of 'name:alias' strings for legitimate aliases
 * @returns {Object} Validation result with type ('success' or 'error') and errors array
 */
validate(packageNameAliases?: string[]): {
  type: 'success' | 'error';
  errors: Array<{
    message: string;
    package: string;
  }>;
}

Behavior:

  • Checks that the package name in the lockfile matches the package name in the resolved URL
  • Only validates packages from official public registries (npm, yarn, verdaccio)
  • Packages from private registries are automatically skipped
  • Packages without a resolved field are automatically skipped (e.g., local filesystem packages)
  • URL parsing errors are silently ignored (package is considered valid)

Package Name Aliases:

The optional packageNameAliases parameter allows specifying legitimate package name aliases in the format 'declared-name:actual-name'. This is useful for packages that intentionally use different names in the lockfile versus the registry.

Usage Examples:

const { ValidatePackageNames, ParseLockfile } = require('lockfile-lint-api');

// Basic usage
const parser = new ParseLockfile({ lockfilePath: './package-lock.json' });
const lockfile = parser.parseSync();
const validator = new ValidatePackageNames({ packages: lockfile.object });

const result = validator.validate();

if (result.type === 'success') {
  console.log('All package names are consistent');
} else {
  console.error('Package name validation failed:');
  result.errors.forEach(error => {
    console.error(`  ${error.package}: ${error.message}`);
  });
}

Example with Package Aliases:

const { ValidatePackageNames, ParseLockfile } = require('lockfile-lint-api');

const parser = new ParseLockfile({ lockfilePath: './package-lock.json' });
const lockfile = parser.parseSync();
const validator = new ValidatePackageNames({ packages: lockfile.object });

// Allow specific package name aliases
const result = validator.validate([
  'my-package-alias:actual-package-name',
  'another-alias:real-package'
]);

if (result.type === 'error') {
  console.error('Name confusion detected:');
  result.errors.forEach(error => console.error(error.message));
}

CI/CD Integration Example:

const { ValidatePackageNames, ParseLockfile } = require('lockfile-lint-api');

function detectNameConfusionAttacks(lockfilePath) {
  try {
    const parser = new ParseLockfile({ lockfilePath });
    const lockfile = parser.parseSync();
    const validator = new ValidatePackageNames({ packages: lockfile.object });
    const result = validator.validate();

    if (result.type === 'error') {
      console.error('SECURITY ALERT: Package name confusion detected!');
      result.errors.forEach(error => {
        console.error(`  Package: ${error.package}`);
        console.error(`  ${error.message}`);
      });
      return false;
    }

    console.log('✓ All package names are consistent');
    return true;
  } catch (error) {
    console.error('Validation error:', error.message);
    return false;
  }
}

if (!detectNameConfusionAttacks('./package-lock.json')) {
  console.error('Build failed due to security concerns');
  process.exit(1);
}

Validation Result Structure

The validate() method returns a consistent result object:

interface PackageNamesValidationResult {
  type: 'success' | 'error';
  errors: PackageNamesValidationError[];
}

interface PackageNamesValidationError {
  message: string;  // Detailed error message with expected and actual package names
  package: string;  // Package name that failed validation
}

Success Example:

{
  type: 'success',
  errors: []
}

Error Example:

{
  type: 'error',
  errors: [
    {
      message: 'detected resolved URL for package with a different name: lodash\n    expected: lodash\n    actual: malicious-lodash\n',
      package: 'lodash'
    }
  ]
}

Security Use Cases

Package name validation prevents name confusion attacks:

  1. Typosquatting Detection: Detect packages that claim one name but resolve to another
  2. Supply Chain Attack Prevention: Identify malicious packages masquerading as legitimate ones
  3. Registry Compromise Detection: Detect if a registry is serving wrong packages
  4. Compliance Verification: Ensure packages are actually what they claim to be

Name Confusion Attack Example:

A malicious actor could create a lockfile where:

  • Declared name: lodash@4.17.21
  • Resolved URL: https://registry.npmjs.org/malicious-lodash/-/malicious-lodash-4.17.21.tgz

This validator would detect this mismatch and flag it as an error.

Implementation Details

Registry Filtering

The validator only checks packages from official public registries:

  • registry.npmjs.org (npm registry)
  • registry.yarnpkg.com (Yarn registry)
  • registry.verdaccio.org (Verdaccio registry)

Packages from other registries (private, corporate, custom, etc.) are automatically skipped because:

  • Private registries may have different naming conventions
  • Corporate proxies may modify package URLs
  • Custom registries may use non-standard URL structures

Example:

// These will be validated
resolved: 'https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz'
resolved: 'https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz'

// These will be skipped
resolved: 'https://registry.mycompany.com/lodash/-/lodash-4.17.21.tgz'
resolved: 'https://nexus.example.org/lodash/-/lodash-4.17.21.tgz'

Package Name Extraction

The validator handles scoped packages correctly:

// Regular packages
'lodash@4.17.21' → 'lodash'
'express@4.18.0' → 'express'

// Scoped packages
'@babel/core@7.20.0' → '@babel/core'
'@types/node@18.0.0' → '@types/node'

URL Structure Validation

The validator expects the following URL structure for official registries:

https://<registry-host>/<package-name>/-/<package-name>-<version>.tgz

It extracts the package name from the URL path and compares it to the declared package name.

Package Name Aliases

The packageNameAliases parameter is useful for edge cases where packages legitimately use different names:

Format: 'declared-name:actual-name'

Example Scenarios:

const validator = new ValidatePackageNames({ packages: lockfile.object });

// Allow a package that's aliased in package.json
// "dependencies": { "my-lodash": "npm:lodash@4.17.21" }
const result = validator.validate(['my-lodash:lodash']);

// Allow multiple aliases
const result = validator.validate([
  'my-lodash:lodash',
  'my-react:react',
  'company-utils:@company/utils'
]);

Alias Processing:

When an alias is provided:

  1. The validator parses the alias string (splits on :)
  2. Creates a mapping from declared name to actual name
  3. Skips validation for packages that match the declared name
  4. Logs a debug message indicating the package was skipped

Error Handling

Constructor Errors:

// Throws: 'expecting an object passed to validator constructor'
const validator = new ValidatePackageNames();  // Missing packages
const validator = new ValidatePackageNames({ packages: null });  // Invalid type
const validator = new ValidatePackageNames({ packages: [] });  // Array instead of object

Advanced Usage

Comprehensive Security Check:

const {
  ParseLockfile,
  ValidateHost,
  ValidateHttps,
  ValidatePackageNames
} = require('lockfile-lint-api');

function comprehensiveSecurityAudit(lockfilePath) {
  const parser = new ParseLockfile({ lockfilePath });
  const lockfile = parser.parseSync();

  const validators = [
    {
      name: 'HTTPS Protocol',
      validator: new ValidateHttps({ packages: lockfile.object }),
      args: []
    },
    {
      name: 'Allowed Hosts',
      validator: new ValidateHost({ packages: lockfile.object }),
      args: [['npm']]
    },
    {
      name: 'Package Names',
      validator: new ValidatePackageNames({ packages: lockfile.object }),
      args: [[]]
    }
  ];

  let allPassed = true;

  validators.forEach(({ name, validator, args }) => {
    const result = validator.validate(...args);
    if (result.type === 'error') {
      console.error(`\n${name} validation failed:`);
      result.errors.forEach(error => console.error(error.message));
      allPassed = false;
    } else {
      console.log(`✓ ${name} validation passed`);
    }
  });

  return allPassed;
}

if (!comprehensiveSecurityAudit('./package-lock.json')) {
  console.error('\nSecurity audit failed!');
  process.exit(1);
}

Debug Logging

The validator uses the debug package for internal logging. Enable debug output:

DEBUG=lockfile-lint node your-script.js

Debug messages include:

  • Skipped packages (aliased packages)
  • Skipped packages (non-official registries)

Example Debug Output:

lockfile-lint skipping package name validation for aliased package name: my-lodash@4.17.21 resolving to: lodash
lockfile-lint skipping package name 'react@18.2.0' validation for non-official registry 'https://registry.mycompany.com'