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-scheme.mddocs/reference/

URI Scheme Validation

The ValidateScheme class validates that package URLs use whitelisted URI schemes (protocols).

Capabilities

ValidateScheme Constructor

Creates a new URI scheme validator instance.

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

Validate URI Schemes

Validates that all packages in the lockfile use allowed URI schemes.

/**
 * Validates all packages use allowed URI schemes
 * @param {string[]} schemes - Array of allowed scheme strings (e.g., ['https:', 'http:'])
 * @returns {Object} Validation result with type ('success' or 'error') and errors array
 * @throws {Error} If schemes is not an array
 */
validate(schemes: string[]): {
  type: 'success' | 'error';
  errors: Array<{
    message: string;
    package: string;
  }>;
}

Scheme Format:

Schemes should include the trailing colon (:) character:

  • ✓ Correct: 'https:', 'http:', 'git:', 'file:'
  • ✗ Incorrect: 'https', 'http', 'git', 'file'

Behavior:

  • Checks that all package URLs use one of the allowed schemes
  • Packages without a resolved field are automatically skipped (e.g., local filesystem packages)
  • URL parsing errors are silently ignored (package is considered valid)
  • Returns error result if any package uses a non-whitelisted scheme

Usage Examples:

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

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

const result1 = validator.validate(['https:']);

if (result1.type === 'success') {
  console.log('All packages use HTTPS scheme');
} else {
  console.error('Scheme validation failed:');
  result1.errors.forEach(error => {
    console.error(`  ${error.package}: ${error.message}`);
  });
}

// Allow both HTTPS and HTTP
const result2 = validator.validate(['https:', 'http:']);

// Allow Git schemes for development
const result3 = validator.validate(['https:', 'git:', 'git+ssh:']);

// Allow file:// URLs for local development
const result4 = validator.validate(['https:', 'file:']);

CI/CD Integration Example:

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

function validateProductionSchemes(lockfilePath) {
  try {
    const parser = new ParseLockfile({ lockfilePath });
    const lockfile = parser.parseSync();
    const validator = new ValidateScheme({ packages: lockfile.object });

    // Production: Only HTTPS allowed
    const result = validator.validate(['https:']);

    if (result.type === 'error') {
      console.error('Production build failed: Invalid URI schemes detected!');
      result.errors.forEach(error => {
        console.error(`  Package: ${error.package}`);
        console.error(`  ${error.message}`);
      });
      return false;
    }

    console.log('✓ All packages use secure HTTPS scheme');
    return true;
  } catch (error) {
    console.error('Validation error:', error.message);
    return false;
  }
}

if (!validateProductionSchemes('./package-lock.json')) {
  process.exit(1);
}

Environment-Specific Validation:

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

function validateSchemesByEnvironment(lockfilePath) {
  const parser = new ParseLockfile({ lockfilePath });
  const lockfile = parser.parseSync();
  const validator = new ValidateScheme({ packages: lockfile.object });

  const env = process.env.NODE_ENV || 'development';

  let allowedSchemes;
  switch (env) {
    case 'production':
      // Production: Only HTTPS
      allowedSchemes = ['https:'];
      break;
    case 'staging':
      // Staging: HTTPS and HTTP
      allowedSchemes = ['https:', 'http:'];
      break;
    case 'development':
      // Development: Allow all common schemes
      allowedSchemes = ['https:', 'http:', 'git:', 'git+ssh:', 'file:'];
      break;
    default:
      allowedSchemes = ['https:'];
  }

  const result = validator.validate(allowedSchemes);

  if (result.type === 'error') {
    console.error(`Scheme validation failed for ${env} environment:`);
    result.errors.forEach(error => console.error(error.message));
    return false;
  }

  console.log(`✓ All schemes valid for ${env} environment`);
  return true;
}

if (!validateSchemesByEnvironment('./package-lock.json')) {
  process.exit(1);
}

Validation Result Structure

The validate() method returns a consistent result object:

interface SchemeValidationResult {
  type: 'success' | 'error';
  errors: SchemeValidationError[];
}

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

Success Example:

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

Error Example:

{
  type: 'error',
  errors: [
    {
      message: 'detected invalid scheme(s) for package: some-package@1.0.0\n    expected: https:,http:\n    actual: git:\n',
      package: 'some-package@1.0.0'
    }
  ]
}

Common URI Schemes

Here are common URI schemes you might encounter in lockfiles:

Secure Schemes

  • 'https:' - HTTP over TLS/SSL (most common, recommended)

Insecure Schemes

  • 'http:' - Unencrypted HTTP (not recommended for production)

Git Schemes

  • 'git:' - Git protocol (unencrypted)
  • 'git+ssh:' - Git over SSH (secure)
  • 'git+http:' - Git over HTTP (insecure)
  • 'git+https:' - Git over HTTPS (secure)

Other Schemes

  • 'file:' - Local filesystem (common in development)
  • 'ssh:' - SSH protocol
  • 'ftp:' - FTP protocol (rare, not recommended)

Security Use Cases

URI scheme validation is important for:

  1. Protocol Security Enforcement: Ensure all packages use secure protocols
  2. Corporate Policy Compliance: Block unapproved schemes like git: or ftp:
  3. Production vs Development Environments: Different scheme rules per environment
  4. Supply Chain Security: Prevent packages from untrusted protocols

Security Best Practices:

// Production: Strictest - HTTPS only
const productionSchemes = ['https:'];

// Staging: Moderate - HTTPS and HTTP
const stagingSchemes = ['https:', 'http:'];

// Development: Flexible - Include Git for private repos
const developmentSchemes = ['https:', 'http:', 'git+ssh:', 'git+https:', 'file:'];

// NEVER allow in production
const unsafeSchemes = ['git:', 'http:', 'ftp:'];

Comparison with ValidateHttps

ValidateScheme is more flexible than ValidateHttps:

ValidateHttps:

  • No configuration
  • Strictly enforces https: protocol only
  • Simple, focused use case

ValidateScheme:

  • Configurable list of allowed schemes
  • Can allow multiple schemes
  • More flexible for different environments

Example:

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

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

// ValidateHttps: Strict HTTPS-only
const httpsValidator = new ValidateHttps({ packages: lockfile.object });
const httpsResult = httpsValidator.validate();

// ValidateScheme: Flexible scheme list
const schemeValidator = new ValidateScheme({ packages: lockfile.object });
const schemeResult = schemeValidator.validate(['https:', 'http:']);

// Both check protocols, but ValidateScheme is more configurable

Error Handling

Constructor Errors:

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

Validation Errors:

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

// Throws: 'validate method requires an array'
validator.validate('https:');  // String instead of array
validator.validate({ scheme: 'https:' });  // Object instead of array

Implementation Notes

  1. Protocol Extraction: Uses Node.js URL.protocol property to extract scheme
  2. Colon Suffix: Always includes the colon (:) in the protocol check
  3. Local Packages: Automatically skipped (packages without resolved field)
  4. Error Tolerance: URL parsing errors are silently caught to avoid false positives
  5. Case Sensitivity: Schemes are case-sensitive (use lowercase with colon)

Advanced Usage

Pre-commit Hook with Scheme Validation:

#!/usr/bin/env node
const { ValidateScheme, ParseLockfile } = require('lockfile-lint-api');
const fs = require('fs');

const ALLOWED_PRODUCTION_SCHEMES = ['https:'];

const lockfiles = ['package-lock.json', 'yarn.lock'];

let hasErrors = false;

lockfiles.forEach(lockfile => {
  if (!fs.existsSync(lockfile)) {
    return;
  }

  const parser = new ParseLockfile({ lockfilePath: lockfile });
  const parsed = parser.parseSync();
  const validator = new ValidateScheme({ packages: parsed.object });
  const result = validator.validate(ALLOWED_PRODUCTION_SCHEMES);

  if (result.type === 'error') {
    console.error(`\nScheme validation failed for ${lockfile}:`);
    result.errors.forEach(error => console.error(error.message));
    hasErrors = true;
  }
});

if (hasErrors) {
  console.error('\nCommit rejected: Only HTTPS schemes allowed');
  console.error('Run in development environment or update package sources');
  process.exit(1);
}

console.log('✓ Scheme validation passed');

Comprehensive Security with Multiple Validators:

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

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

  // 1. Validate URI schemes
  const schemeValidator = new ValidateScheme({ packages: lockfile.object });
  const schemeResult = schemeValidator.validate(['https:']);

  // 2. Validate allowed hosts
  const hostValidator = new ValidateHost({ packages: lockfile.object });
  const hostResult = hostValidator.validate(['npm']);

  // 3. Validate integrity hashes
  const integrityValidator = new ValidateIntegrity({ packages: lockfile.object });
  const integrityResult = integrityValidator.validate();

  const allErrors = [
    ...schemeResult.errors,
    ...hostResult.errors,
    ...integrityResult.errors
  ];

  if (allErrors.length > 0) {
    console.error('Lockfile validation failed:');
    allErrors.forEach(error => console.error(error.message));
    return false;
  }

  console.log('✓ All validations passed');
  return true;
}

if (!comprehensiveValidation('./package-lock.json')) {
  process.exit(1);
}

Edge Cases

Handling Scheme Format

// Correct format (with colon)
validator.validate(['https:', 'http:', 'git:']);

// Incorrect format (without colon) - will not match
validator.validate(['https', 'http', 'git']);  // Will fail validation

Handling Multiple Schemes

// Allow multiple schemes
const result = validator.validate(['https:', 'http:', 'git+ssh:']);

// All packages must use one of the allowed schemes
// Package with 'https:' - PASS
// Package with 'http:' - PASS
// Package with 'git+ssh:' - PASS
// Package with 'git:' - FAIL (not in whitelist)

Handling Local Packages

// Packages without resolved field are automatically skipped
{
  'local-package@1.0.0': {
    version: '1.0.0'
    // No resolved field - automatically skipped
  }
}