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

marshalls.mddocs/reference/

Security Marshalls

npq performs 14 different security and quality checks ("marshalls") on packages before installation. Each marshall targets a specific risk category and can be individually disabled via environment variables.

Capabilities

Package Age Check (age)

Detects newly published packages that may not have been vetted by the community.

/**
 * Marshall: age
 * Category: PackageHealth
 * Title: "Checking package maturity"
 *
 * Checks:
 * - Package creation age < 22 days (Error)
 * - Specific version release > 365 days ago (Warning - unmaintained)
 *
 * Environment Variable: MARSHALL_DISABLE_AGE
 */
interface AgeMarshall extends BaseMarshall {
  name: 'age';
  categoryId: 'PackageHealth';
  
  validate(pkg: PackageMetadata): Promise<{
    packageAge: number;        // Days since package creation
    versionAge: number;        // Days since version release
    packageCreated: Date;      // Package creation date
    versionPublished: Date;    // Version publish date
  }>;
}

Thresholds:

  • PACKAGE_AGE_THRESHOLD: 22 days (Error if package created more recently)
  • PACKAGE_AGE_UNMAINTAINED_RISK: 365 days (Warning if version is older)

Behavior:

  • Checks package creation date from registry metadata
  • Checks specific version release date
  • Reports age in human-readable format (days/years)

Example Return Value:

// Success case
{
  packageAge: 120,
  versionAge: 30,
  packageCreated: new Date('2023-10-01'),
  versionPublished: new Date('2024-11-01')
}

// Error case (package too new)
{
  packageAge: 15,
  versionAge: 15,
  packageCreated: new Date('2024-12-01'),
  versionPublished: new Date('2024-12-01')
}
// Throws Error: "Detected a newly published package: created 15 days. Act carefully"

// Warning case (version too old)
{
  packageAge: 400,
  versionAge: 400,
  packageCreated: new Date('2022-01-01'),
  versionPublished: new Date('2022-01-01')
}
// Throws Warning: "Detected an old package: created 400 days ago"

Example Output:

Error: Detected a newly published package: created < 22 days. Act carefully
Warning: Detected an old package: created 2 years ago

Edge Cases:

  • Packages with no time metadata: Falls back to version-specific time
  • Invalid date formats: Handles gracefully, may skip check
  • Packages with only one version: Uses package creation date for both checks

Disable:

MARSHALL_DISABLE_AGE=1 npq install new-package

Author Reputation Check (author)

Validates package author reputation and publication history.

/**
 * Marshall: author
 * Category: SupplyChainSecurity
 * Title: "Checking package author reputation"
 *
 * Checks:
 * - First publish by author < 21 days (Error - new author risk)
 * - Version published < 7 days (Error)
 * - Version published 7-30 days (Warning)
 * - Version published 30-45 days (Info check, no warning)
 *
 * Environment Variable: MARSHALL_DISABLE_AUTHOR
 */
interface AuthorMarshall extends BaseMarshall {
  name: 'author';
  categoryId: 'SupplyChainSecurity';
  
  validate(pkg: PackageMetadata): Promise<{
    authors: Array<{
      email: string;           // Author email
      name: string;            // Author name
      firstPublishDate: Date;  // First package publish date
      daysSinceFirstPublish: number;
    }>;
    versionPublishDate: Date;
    daysSinceVersionPublish: number;
  }>;
}

Features:

  • Analyzes all versions published by each maintainer/author
  • Validates email format with regex
  • Checks both maintainers array and _npmUser field
  • Identifies first-time publishers

Example Return Value:

// Success case
{
  authors: [
    {
      email: 'maintainer@example.com',
      name: 'John Doe',
      firstPublishDate: new Date('2020-01-01'),
      daysSinceFirstPublish: 1800
    }
  ],
  versionPublishDate: new Date('2024-10-01'),
  daysSinceVersionPublish: 60
}

// Error case (new author)
{
  authors: [
    {
      email: 'newuser@example.com',
      name: 'New User',
      firstPublishDate: new Date('2024-11-15'),
      daysSinceFirstPublish: 15
    }
  ],
  versionPublishDate: new Date('2024-11-15'),
  daysSinceVersionPublish: 15
}
// Throws Error: "New author detected - first publish by newuser@example.com was 15 days ago"

// Error case (version too new)
{
  authors: [...],
  versionPublishDate: new Date('2024-12-20'),
  daysSinceVersionPublish: 3
}
// Throws Error: "Version published 3 days ago"

// Warning case
{
  authors: [...],
  versionPublishDate: new Date('2024-11-20'),
  daysSinceVersionPublish: 12
}
// Throws Warning: "Version published 12 days ago"

Example Output:

Error: New author detected - first publish by user@example.com was 15 days ago
Warning: Version published 12 days ago

Edge Cases:

  • Packages with no maintainers: Checks _npmUser field as fallback
  • Invalid email formats: Skips email validation, checks other fields
  • Multiple authors: Checks all authors, error if any author is new
  • Missing publish dates: Uses package creation date as fallback

Disable:

MARSHALL_DISABLE_AUTHOR=1 npq install package

Deprecation Detection (deprecation)

Detects deprecated packages and archived GitHub repositories.

/**
 * Marshall: deprecation
 * Category: PackageHealth
 * Title: "Checking for deprecated packages"
 *
 * Checks:
 * - Package version marked as deprecated in npm registry (Error)
 * - GitHub repository marked as archived (Error)
 *
 * Environment Variable: MARSHALL_DISABLE_DEPRECATION
 */
interface DeprecationMarshall extends BaseMarshall {
  name: 'deprecation';
  categoryId: 'PackageHealth';
  
  validate(pkg: PackageMetadata): Promise<{
    isDeprecated: boolean;     // Whether version is deprecated
    deprecationMessage?: string; // Deprecation message from registry
    repoArchived?: boolean;     // Whether GitHub repo is archived
    repoUrl?: string;           // Repository URL
  }>;
}

Features:

  • Parses repository URL to extract GitHub owner/repo
  • Queries GitHub API for archive status
  • Supports GITHUB_TOKEN environment variable for higher rate limits (5000 req/hour vs 60)

Example Return Value:

// Success case
{
  isDeprecated: false,
  repoArchived: false,
  repoUrl: 'https://github.com/owner/repo'
}

// Error case (deprecated)
{
  isDeprecated: true,
  deprecationMessage: 'Please use v3.x instead',
  repoUrl: 'https://github.com/owner/repo'
}
// Throws Error: "Package version 2.0.0 is deprecated: Please use v3.x instead"

// Error case (archived)
{
  isDeprecated: false,
  repoArchived: true,
  repoUrl: 'https://github.com/owner/repo'
}
// Throws Error: "GitHub repository is archived and no longer maintained"

// Both deprecated and archived
{
  isDeprecated: true,
  deprecationMessage: 'Package deprecated',
  repoArchived: true,
  repoUrl: 'https://github.com/owner/repo'
}
// Throws both errors

Example Output:

Error: Package version 2.0.0 is deprecated: Please use v3.x instead
Error: GitHub repository is archived and no longer maintained

Edge Cases:

  • Repository URL not GitHub: Skips archive check, only checks deprecation
  • Invalid repository URL format: Skips archive check gracefully
  • GitHub API rate limit exceeded: Falls back to deprecation check only
  • Missing repository field: Only checks deprecation status
  • Private repositories: Archive check may fail if no token provided

GitHub Token Setup:

export GITHUB_TOKEN=ghp_your_token_here

Disable:

MARSHALL_DISABLE_DEPRECATION=1 npq install old-package

Download Popularity Check (downloads)

Validates package popularity based on download statistics.

/**
 * Marshall: downloads
 * Category: PackageHealth
 * Title: "Checking package popularity"
 *
 * Checks:
 * - Downloads < 100/month (Error - unpopular package)
 * - Downloads 100-10000/month (Warning - low popularity)
 *
 * Environment Variable: MARSHALL_DISABLE_DOWNLOADS
 */
interface DownloadsMarshall extends BaseMarshall {
  name: 'downloads';
  categoryId: 'PackageHealth';
  
  validate(pkg: PackageMetadata): Promise<{
    downloads: number;         // Downloads in last month
    threshold: number;         // Error threshold (100)
    upperThreshold: number;    // Warning threshold (10000)
  }>;
}

Thresholds:

  • DOWNLOAD_COUNT_THRESHOLD: 100 downloads/month (Error)
  • DOWNLOAD_COUNT_UPPER_THRESHOLD: 10,000 downloads/month (Warning)

Data Source: npm registry API (https://api.npmjs.org/downloads/point/last-month/{package})

Example Return Value:

// Success case
{
  downloads: 50000,
  threshold: 100,
  upperThreshold: 10000
}

// Error case (too few downloads)
{
  downloads: 45,
  threshold: 100,
  upperThreshold: 10000
}
// Throws Error: "Package has only 45 downloads in the last month"

// Warning case (low popularity)
{
  downloads: 2500,
  threshold: 100,
  upperThreshold: 10000
}
// Throws Warning: "Package has 2,500 downloads in the last month (low popularity)"

Example Output:

Error: Package has only 45 downloads in the last month
Warning: Package has 2,500 downloads in the last month (low popularity)

Edge Cases:

  • API returns null/undefined: Treats as 0 downloads (error)
  • Network timeout: May skip check or retry
  • New packages with no download data: May return 0 (error)
  • Scoped packages: Downloads counted correctly

Disable:

MARSHALL_DISABLE_DOWNLOADS=1 npq install unpopular-package

Expired Maintainer Domains (expiredDomains)

Detects maintainer email addresses with expired or invalid domains.

/**
 * Marshall: maintainers_expired_emails
 * Category: PackageHealth
 * Title: "Checking for expired maintainer domains"
 *
 * Checks:
 * - Maintainer email domain expired/invalid (Error - account takeover risk)
 *
 * Environment Variable: MARSHALL_DISABLE_MAINTAINERS_EXPIRED_EMAILS
 */
interface ExpiredDomainsMarshall extends BaseMarshall {
  name: 'maintainers_expired_emails';
  categoryId: 'PackageHealth';
  
  validate(pkg: PackageMetadata): Promise<{
    maintainers: Array<{
      email: string;           // Maintainer email
      domain: string;          // Email domain
      hasValidNS: boolean;     // Whether domain has valid NS records
      expired: boolean;        // Whether domain appears expired
    }>;
  }>;
}

Features:

  • Custom DNS resolver using Cloudflare (1.1.1.1) and Google (8.8.8.8) DNS
  • Checks NS records for each maintainer's email domain
  • Validates email format before checking
  • Detects expired domains that could be re-registered for package takeover attacks

Security Impact: Expired domains can be re-registered by attackers to gain control of npm accounts via password reset emails (Account Takeover attacks).

Example Return Value:

// Success case
{
  maintainers: [
    {
      email: 'maintainer@example.com',
      domain: 'example.com',
      hasValidNS: true,
      expired: false
    }
  ]
}

// Error case (expired domain)
{
  maintainers: [
    {
      email: 'user@expireddomain.com',
      domain: 'expireddomain.com',
      hasValidNS: false,
      expired: true
    }
  ]
}
// Throws Error: "Maintainer email domain expired: user@expireddomain.com"

// Multiple maintainers
{
  maintainers: [
    { email: 'user1@valid.com', domain: 'valid.com', hasValidNS: true, expired: false },
    { email: 'user2@expired.com', domain: 'expired.com', hasValidNS: false, expired: true }
  ]
}
// Throws Error for expired domain

Example Output:

Error: Maintainer email domain expired: user@expireddomain.com

Edge Cases:

  • Invalid email format: Skips DNS check, no error
  • DNS resolution timeout: Treats as potentially expired (error)
  • No maintainers: Skips check (no error)
  • DNS server unavailable: May retry with alternate DNS (1.1.1.1, 8.8.8.8)

Disable:

MARSHALL_DISABLE_MAINTAINERS_EXPIRED_EMAILS=1 npq install package

License Verification (license)

Validates presence of package license information.

/**
 * Marshall: license
 * Category: SupplyChainSecurity
 * Title: "Checking for package license"
 *
 * Checks:
 * - LICENSE field missing or empty (Error)
 * - Security placeholder package detected (Error)
 *
 * Environment Variable: MARSHALL_DISABLE_LICENSE
 */
interface LicenseMarshall extends BaseMarshall {
  name: 'license';
  categoryId: 'SupplyChainSecurity';
  
  validate(pkg: PackageMetadata): Promise<{
    license: string | null;    // License string or null
    hasLicense: boolean;       // Whether license field exists
    isPlaceholder: boolean;     // Whether package is security placeholder
  }>;
}

Features:

  • Checks license field in package.json
  • Detects npm security placeholder packages
  • Validates license is not empty or undefined

Example Return Value:

// Success case
{
  license: 'MIT',
  hasLicense: true,
  isPlaceholder: false
}

// Error case (no license)
{
  license: null,
  hasLicense: false,
  isPlaceholder: false
}
// Throws Error: "Package has no license field"

// Error case (placeholder)
{
  license: null,
  hasLicense: false,
  isPlaceholder: true
}
// Throws Error: "Package is a security placeholder package"

// License as object (SPDX format)
{
  license: { type: 'MIT', url: 'https://...' },
  hasLicense: true,
  isPlaceholder: false
}

Example Output:

Error: Package has no license field
Error: Package is a security placeholder package

Edge Cases:

  • License field is empty string: Treated as no license (error)
  • License field is object: Extracts type field
  • Security placeholder packages: Detected by package name pattern
  • Multiple license formats: Handles string, object, and array formats

Disable:

MARSHALL_DISABLE_LICENSE=1 npq install unlicensed-package

New Binary Detection (newBin)

Detects newly introduced command-line executables between versions.

/**
 * Marshall: newBin
 * Category: SupplyChainSecurity
 * Title: "Checking for newly introduced binaries"
 *
 * Checks:
 * - New binary executables introduced in this version (Warning)
 *
 * Environment Variable: MARSHALL_DISABLE_NEWBIN
 */
interface NewBinMarshall extends BaseMarshall {
  name: 'newBin';
  categoryId: 'SupplyChainSecurity';
  
  validate(pkg: PackageMetadata): Promise<{
    currentBinaries: Record<string, string>;  // Current version binaries
    previousBinaries: Record<string, string>;  // Previous version binaries
    newBinaries: Array<{                      // Newly introduced binaries
      name: string;                           // Binary name
      path: string;                           // Binary path
    }>;
  }>;
}

Features:

  • Compares bin field between current and previous version
  • Normalizes bin format (handles both string and object formats)
  • Lists all newly added bin entries
  • Helps identify unexpected new executables being added to node_modules/.bin/

Bin Format Normalization:

// String format
"bin": "./bin/cli.js"
// Normalized to:
{ "package-name": "./bin/cli.js" }

// Object format
"bin": {
  "cmd1": "./bin/cmd1.js",
  "cmd2": "./bin/cmd2.js"
}

Example Return Value:

// Success case (no new binaries)
{
  currentBinaries: { 'package-name': './bin/cli.js' },
  previousBinaries: { 'package-name': './bin/cli.js' },
  newBinaries: []
}

// Warning case (new binary)
{
  currentBinaries: {
    'package-name': './bin/cli.js',
    'malicious-cmd': './bin/cmd.js'
  },
  previousBinaries: {
    'package-name': './bin/cli.js'
  },
  newBinaries: [
    { name: 'malicious-cmd', path: './bin/cmd.js' }
  ]
}
// Throws Warning: "New binary introduced: malicious-cmd (./bin/cmd.js)"

// First version (no previous binaries)
{
  currentBinaries: { 'package-name': './bin/cli.js' },
  previousBinaries: {},
  newBinaries: [
    { name: 'package-name', path: './bin/cli.js' }
  ]
}
// Throws Warning for all binaries in first version

Example Output:

Warning: New binary introduced: malicious-cmd (./bin/cmd.js)

Edge Cases:

  • First version of package: All binaries are considered new (warning)
  • Binary path changed but name same: Not detected as new
  • Bin field format changes: Normalized before comparison
  • Missing previous version: Uses empty object for comparison

Disable:

MARSHALL_DISABLE_NEWBIN=1 npq install package

Provenance Verification (provenance)

Verifies package provenance and attestations using cryptographic signatures.

/**
 * Marshall: provenance
 * Category: SupplyChainSecurity
 * Title: "Checking package provenance"
 *
 * Checks:
 * - Package provenance/attestations present and valid (Warning if missing)
 *
 * Environment Variable: MARSHALL_DISABLE_PROVENANCE
 */
interface ProvenanceMarshall extends BaseMarshall {
  name: 'provenance';
  categoryId: 'SupplyChainSecurity';
  
  validate(pkg: PackageMetadata): Promise<{
    hasProvenance: boolean;     // Whether provenance attestations exist
    isValid: boolean;          // Whether attestations are valid
    attestations?: Array<{     // Attestation details
      type: string;            // Attestation type
      verified: boolean;       // Whether verification succeeded
    }>;
  }>;
}

Features:

  • Fetches npm registry public keys from https://registry.npmjs.org/-/npm/v1/keys
  • Converts registry keys to PEM format
  • Verifies attestations using NpmRegistry helper and sigstore library
  • Handles malformed checkpoint data gracefully
  • Validates provenance metadata for build system transparency

Provenance Attestations: Cryptographic proof of package build and publish process, linking package contents to source code and build environment.

Example Return Value:

// Success case (has valid provenance)
{
  hasProvenance: true,
  isValid: true,
  attestations: [
    { type: 'provenance', verified: true }
  ]
}

// Warning case (no provenance)
{
  hasProvenance: false,
  isValid: false
}
// Throws Warning: "Package has no provenance attestations"

// Warning case (invalid provenance)
{
  hasProvenance: true,
  isValid: false,
  attestations: [
    { type: 'provenance', verified: false }
  ]
}
// Throws Warning: "Package provenance attestations are invalid"

Example Output:

Warning: Package has no provenance attestations

Edge Cases:

  • Registry keys unavailable: May skip verification (warning)
  • Malformed attestation data: Handles gracefully (warning)
  • Expired registry keys: Still verifies if key was valid at publish time
  • Multiple attestations: Verifies all, warning if any invalid

Disable:

MARSHALL_DISABLE_PROVENANCE=1 npq install package

Repository Availability (repo)

Validates that package repository and homepage URLs are accessible.

/**
 * Marshall: repo
 * Category: PackageHealth
 * Title: "Checking repository availability"
 *
 * Checks:
 * - Repository URL accessible (Warning if not)
 * - Homepage accessible as fallback (Warning if not)
 * - Either repo or homepage exists (Error if neither)
 *
 * Environment Variable: MARSHALL_DISABLE_REPO
 */
interface RepoMarshall extends BaseMarshall {
  name: 'repo';
  categoryId: 'PackageHealth';
  
  validate(pkg: PackageMetadata): Promise<{
    repoUrl?: string;          // Repository URL
    homepageUrl?: string;       // Homepage URL
    repoAccessible: boolean;    // Whether repo URL is accessible
    homepageAccessible: boolean; // Whether homepage URL is accessible
    hasAnyUrl: boolean;         // Whether at least one URL exists
  }>;
}

Features:

  • HEAD request to check URL accessibility
  • Timeout handling (5 seconds)
  • Fallback to homepage if repo check fails
  • Validates at least one contact/information URL is available

URL Sources:

  • repository field in package.json
  • homepage field in package.json

Example Return Value:

// Success case
{
  repoUrl: 'https://github.com/owner/repo',
  homepageUrl: 'https://example.com',
  repoAccessible: true,
  homepageAccessible: true,
  hasAnyUrl: true
}

// Warning case (repo not accessible)
{
  repoUrl: 'https://github.com/owner/repo',
  homepageUrl: 'https://example.com',
  repoAccessible: false,
  homepageAccessible: true,
  hasAnyUrl: true
}
// Throws Warning: "Repository URL is not accessible"

// Error case (no accessible URLs)
{
  repoUrl: 'https://github.com/owner/repo',
  homepageUrl: 'https://example.com',
  repoAccessible: false,
  homepageAccessible: false,
  hasAnyUrl: true
}
// Throws Error: "Package has no accessible repository or homepage"

// Error case (no URLs at all)
{
  repoAccessible: false,
  homepageAccessible: false,
  hasAnyUrl: false
}
// Throws Error: "Package has no accessible repository or homepage"

Example Output:

Warning: Repository URL is not accessible
Warning: Homepage URL is not accessible
Error: Package has no accessible repository or homepage

Edge Cases:

  • Timeout (5 seconds): Treats as not accessible (warning/error)
  • HTTP redirects: Follows redirects, checks final URL
  • HTTPS certificate errors: May treat as not accessible
  • Private repositories: Returns 403/404, treated as not accessible

Disable:

MARSHALL_DISABLE_REPO=1 npq install package

Install Scripts Detection (scripts)

Detects potentially malicious pre/post install scripts.

/**
 * Marshall: scripts
 * Category: MalwareDetection
 * Title: "Checking for install scripts"
 *
 * Checks:
 * - Pre/post install scripts present (Error - malware risk)
 *
 * Environment Variable: MARSHALL_DISABLE_SCRIPTS
 */
interface ScriptsMarshall extends BaseMarshall {
  name: 'scripts';
  categoryId: 'MalwareDetection';
  
  validate(pkg: PackageMetadata): Promise<{
    hasInstallScripts: boolean; // Whether install scripts exist
    scripts: Array<{           // Detected scripts
      name: string;             // Script name (preinstall, install, postinstall)
      command: string;          // Script command
    }>;
  }>;
}

Blacklisted Scripts:

  • install
  • preinstall
  • postinstall

Security Risk: Install scripts execute arbitrary code during package installation, which can be used for:

  • Stealing environment variables (API keys, tokens)
  • Installing backdoors
  • Cryptocurrency mining
  • Data exfiltration

Example Return Value:

// Success case (no install scripts)
{
  hasInstallScripts: false,
  scripts: []
}

// Error case (preinstall)
{
  hasInstallScripts: true,
  scripts: [
    { name: 'preinstall', command: 'node setup.js' }
  ]
}
// Throws Error: "Package has preinstall script (potential malware)"

// Error case (postinstall)
{
  hasInstallScripts: true,
  scripts: [
    { name: 'postinstall', command: 'node postinstall.js' }
  ]
}
// Throws Error: "Package has postinstall script (potential malware)"

// Error case (both)
{
  hasInstallScripts: true,
  scripts: [
    { name: 'preinstall', command: 'node setup.js' },
    { name: 'postinstall', command: 'node postinstall.js' }
  ]
}
// Throws Error for each script

Example Output:

Error: Package has preinstall script (potential malware)
Error: Package has postinstall script (potential malware)

Edge Cases:

  • Scripts field missing: Treated as no scripts (success)
  • Empty scripts object: Treated as no scripts (success)
  • Only non-install scripts: Not flagged (success)
  • Script command is empty string: Still flagged (error)

Disable:

MARSHALL_DISABLE_SCRIPTS=1 npq install package-with-scripts

Registry Signature Verification (signatures)

Verifies npm registry cryptographic signatures on packages.

/**
 * Marshall: signatures
 * Category: SupplyChainSecurity
 * Title: "Checking package signatures"
 *
 * Checks:
 * - Package signature valid (Error if invalid)
 * - Registry key expired (Warning)
 * - Registry key missing (Error)
 *
 * Environment Variable: MARSHALL_DISABLE_SIGNATURES
 */
interface SignaturesMarshall extends BaseMarshall {
  name: 'signatures';
  categoryId: 'SupplyChainSecurity';
  
  validate(pkg: PackageMetadata): Promise<{
    hasSignature: boolean;     // Whether package has signature
    isValid: boolean;          // Whether signature is valid
    keyExpired?: boolean;      // Whether registry key is expired
    keyMissing?: boolean;      // Whether key is missing
    keyId?: string;            // Signature key ID
  }>;
}

Features:

  • Fetches and caches npm registry public keys
  • Verifies signatures using NpmRegistry helper
  • Module-level caching with registryKeysCache singleton
  • Validates signature integrity and key expiration dates
  • Uses Node.js crypto module for verification

Verification Process:

  1. Fetch registry public keys from npm
  2. Find key matching signature keyid
  3. Check key expiration date
  4. Verify signature using SHA256

Example Return Value:

// Success case
{
  hasSignature: true,
  isValid: true,
  keyId: 'key-123'
}

// Error case (invalid signature)
{
  hasSignature: true,
  isValid: false,
  keyId: 'key-123'
}
// Throws Error: "Package has invalid registry signature"

// Warning case (expired key)
{
  hasSignature: true,
  isValid: true,
  keyExpired: true,
  keyId: 'key-123'
}
// Throws Warning: "Registry key for signature has expired"

// Error case (missing key)
{
  hasSignature: true,
  isValid: false,
  keyMissing: true,
  keyId: 'key-123'
}
// Throws Error: "No corresponding public key found for signature"

// No signature
{
  hasSignature: false,
  isValid: false
}
// No error or warning (signatures optional)

Example Output:

Error: Package has invalid registry signature
Warning: Registry key for signature has expired
Error: No corresponding public key found for signature

Edge Cases:

  • No signature present: Not an error (signatures are optional)
  • Registry keys API unavailable: May skip verification
  • Key cache expired: Refetches keys from registry
  • Multiple signatures: Verifies all, error if any invalid

Disable:

MARSHALL_DISABLE_SIGNATURES=1 npq install package

Vulnerability Detection (snyk)

Checks for known security vulnerabilities using Snyk or OSV databases.

/**
 * Marshall: snyk
 * Category: SupplyChainSecurity
 * Title: "Checking for known vulnerabilities"
 *
 * Checks:
 * - Known vulnerabilities in package (Error if count > 0)
 * - Malicious package detection (Error)
 *
 * Environment Variable: MARSHALL_DISABLE_SNYK
 */
interface SnykMarshall extends BaseMarshall {
  name: 'snyk';
  categoryId: 'SupplyChainSecurity';
  
  validate(pkg: PackageMetadata): Promise<{
    vulnerabilityCount: number; // Number of vulnerabilities found
    isMalicious: boolean;       // Whether package is marked as malicious
    vulnerabilities?: Array<{   // Vulnerability details
      id: string;              // Vulnerability ID
      title: string;           // Vulnerability title
      severity: string;        // Severity level
      url: string;             // Vulnerability URL
    }>;
    source: 'snyk' | 'osv';    // Data source used
  }>;
}

Data Sources:

  1. Snyk API (Primary, if token available):

    • Requires SNYK_TOKEN or SNYK_API_TOKEN environment variable
    • Or token in ~/.config/configstore/snyk.json
    • URL: https://snyk.io/api/v1/vuln/npm/{package}@{version}
  2. OSV API (Fallback, if no Snyk token):

    • Open Source Vulnerabilities database
    • No authentication required
    • URL: https://api.osv.dev/v1/query

Malicious Package Detection:

  • Snyk: Looks for vulnerability.title === 'Malicious Package'
  • OSV: Looks for vuln.database_specific['malicious-packages-origins']

Environment Variables:

  • SNYK_TOKEN or SNYK_API_TOKEN: API authentication
  • SNYK_API_URL or SNYK_API: Custom API endpoint

Example Return Value:

// Success case (no vulnerabilities)
{
  vulnerabilityCount: 0,
  isMalicious: false,
  source: 'snyk'
}

// Error case (vulnerabilities)
{
  vulnerabilityCount: 3,
  isMalicious: false,
  vulnerabilities: [
    { id: 'SNYK-123', title: 'SQL Injection', severity: 'high', url: 'https://...' },
    { id: 'SNYK-456', title: 'XSS', severity: 'medium', url: 'https://...' },
    { id: 'SNYK-789', title: 'DoS', severity: 'low', url: 'https://...' }
  ],
  source: 'snyk'
}
// Throws Error: "3 vulnerable path(s) found: https://snyk.io/vuln/npm:package-name"

// Error case (malicious)
{
  vulnerabilityCount: 1,
  isMalicious: true,
  vulnerabilities: [
    { id: 'SNYK-MAL', title: 'Malicious Package', severity: 'critical', url: 'https://...' }
  ],
  source: 'snyk'
}
// Throws Error: "Malicious package found: https://snyk.io/vuln/npm:package-name"

// OSV fallback
{
  vulnerabilityCount: 5,
  isMalicious: false,
  source: 'osv'
}
// Throws Error: "5 vulnerabilities found by OSV for package-name"

Example Output:

Error: Malicious package found: https://snyk.io/vuln/npm:package-name
Error: 3 vulnerable path(s) found: https://snyk.io/vuln/npm:package-name
Error: 5 vulnerabilities found by OSV for package-name

Edge Cases:

  • Snyk API unavailable: Falls back to OSV API
  • No Snyk token: Uses OSV API (no authentication required)
  • OSV API unavailable: May skip check or error
  • Rate limiting: May retry or fall back to OSV
  • Package not in database: Returns 0 vulnerabilities (success)

Snyk Setup:

# Option 1: Environment variable
export SNYK_TOKEN=your-snyk-token

# Option 2: Snyk CLI (creates config file)
npm install -g snyk
snyk auth

Disable:

MARSHALL_DISABLE_SNYK=1 npq install package

Typosquatting Detection (typosquatting)

Detects packages with names similar to popular packages.

/**
 * Marshall: typosquatting
 * Category: PackageHealth
 * Title: "Checking for typosquatting"
 *
 * Checks:
 * - Package name similar to popular package (Error if distance < 3)
 *
 * Environment Variable: MARSHALL_DISABLE_TYPOSQUATTING
 */
interface TyposquattingMarshall extends BaseMarshall {
  name: 'typosquatting';
  categoryId: 'PackageHealth';
  
  validate(pkg: PackageMetadata): Promise<{
    similarPackages: Array<{   // Packages with similar names
      name: string;            // Popular package name
      distance: number;        // Levenshtein distance
    }>;
    minDistance: number;        // Minimum distance to any popular package
  }>;
}

Features:

  • Loads top packages from data/top-packages.json
  • Uses Levenshtein distance algorithm (edit distance)
  • Threshold: Edit distance < 3 triggers error
  • Allowlist: ['npq', 'ai', 'bun', 'deno'] (exempted from checks)

Typosquatting Examples:

  • expresssexpress (distance: 1)
  • loadashlodash (distance: 1)
  • reqeustrequest (distance: 2)

Levenshtein Distance: Minimum number of single-character edits (insertions, deletions, substitutions) needed to change one string into another.

Example Return Value:

// Success case (not similar)
{
  similarPackages: [],
  minDistance: 5
}

// Error case (typosquatting)
{
  similarPackages: [
    { name: 'express', distance: 1 }
  ],
  minDistance: 1
}
// Throws Error: "Package name is similar to popular package \"express\" (possible typosquatting)"

// Multiple similar packages
{
  similarPackages: [
    { name: 'express', distance: 1 },
    { name: 'lodash', distance: 2 }
  ],
  minDistance: 1
}
// Throws Error for closest match

Example Output:

Error: Package name is similar to popular package "express" (possible typosquatting)

Edge Cases:

  • Package in allowlist: Skips check (success)
  • Empty top-packages list: Skips check (success)
  • Case sensitivity: Comparison is case-sensitive
  • Scoped packages: Compares unscoped name only

Disable:

MARSHALL_DISABLE_TYPOSQUATTING=1 npq install expresss

Version Maturity Check (version-maturity)

Detects recently published versions that may not have been vetted.

/**
 * Marshall: version_maturity
 * Category: SupplyChainSecurity
 * Title: "Checking version maturity"
 *
 * Checks:
 * - Version published < 7 days ago (Error)
 *
 * Environment Variable: MARSHALL_DISABLE_VERSION_MATURITY
 */
interface VersionMaturityMarshall extends BaseMarshall {
  name: 'version_maturity';
  categoryId: 'SupplyChainSecurity';
  
  validate(pkg: PackageMetadata): Promise<{
    versionPublishDate: Date;  // When version was published
    daysSincePublish: number;   // Days since publish
    isRecent: boolean;          // Whether version is < 7 days old
  }>;
}

Threshold: VERSION_AGE_THRESHOLD: 7 days

Features:

  • Checks specific version publish date (not package creation date)
  • Reports age in human-readable format (days/hours/years)
  • Helps identify versions that haven't been reviewed by the community

Difference from Age Marshall:

  • age: Checks package creation date (first publish ever)
  • version-maturity: Checks specific version publish date

Example Return Value:

// Success case
{
  versionPublishDate: new Date('2024-10-01'),
  daysSincePublish: 60,
  isRecent: false
}

// Error case (too recent)
{
  versionPublishDate: new Date('2024-12-20'),
  daysSincePublish: 3,
  isRecent: true
}
// Throws Error: "Version published 3 days ago (too recent)"

// Edge case (exactly 7 days)
{
  versionPublishDate: new Date('2024-12-13'),
  daysSincePublish: 7,
  isRecent: false
}
// No error (threshold is < 7 days)

Example Output:

Error: Version published 3 days ago (too recent)

Edge Cases:

  • Missing publish date: Uses package creation date as fallback
  • Invalid date format: Handles gracefully, may skip check
  • Future dates: Handles gracefully, treats as recent (error)

Disable:

MARSHALL_DISABLE_VERSION_MATURITY=1 npq install package@latest

Marshall Summary Table

MarshallCategoryError ConditionsWarning ConditionsEnv Variable
agePackageHealthPackage < 22 days oldVersion > 365 days oldMARSHALL_DISABLE_AGE
authorSupplyChainSecurityFirst publish by author < 21 days, Version < 7 daysVersion 7-30 days oldMARSHALL_DISABLE_AUTHOR
deprecationPackageHealthDeprecated, Repo archived-MARSHALL_DISABLE_DEPRECATION
downloadsPackageHealth< 100/month100-10000/monthMARSHALL_DISABLE_DOWNLOADS
expiredDomainsPackageHealthMaintainer domain expired-MARSHALL_DISABLE_MAINTAINERS_EXPIRED_EMAILS
licenseSupplyChainSecurityNo license field-MARSHALL_DISABLE_LICENSE
newBinSupplyChainSecurity-New binary introducedMARSHALL_DISABLE_NEWBIN
provenanceSupplyChainSecurity-No provenance attestationsMARSHALL_DISABLE_PROVENANCE
repoPackageHealthNo accessible repo/homepageRepo/homepage inaccessibleMARSHALL_DISABLE_REPO
scriptsMalwareDetectionPre/post install scripts-MARSHALL_DISABLE_SCRIPTS
signaturesSupplyChainSecurityInvalid signature, Missing keyExpired keyMARSHALL_DISABLE_SIGNATURES
snykSupplyChainSecurityVulnerabilities found, Malicious package-MARSHALL_DISABLE_SNYK
typosquattingPackageHealthName similar to popular package-MARSHALL_DISABLE_TYPOSQUATTING
version-maturitySupplyChainSecurityVersion < 7 days old-MARSHALL_DISABLE_VERSION_MATURITY

Error vs Warning Behavior

Errors (Fatal):

  • Block installation by default
  • Require explicit user confirmation to proceed
  • Indicated with ✖ symbol in rich output

Warnings (Non-fatal):

  • Allow installation with countdown (15 seconds by default)
  • Can be auto-proceeded with 'y' keypress or timeout
  • Indicated with ⚠ symbol in rich output

Auto-Continue: When only warnings are detected, npq automatically proceeds after 15 seconds. Disable with --disable-auto-continue flag or NPQ_DISABLE_AUTO_CONTINUE=true environment variable.

Custom Marshall Configuration Patterns

Disable Multiple Marshalls

# Disable checks for internal/corporate packages
export MARSHALL_DISABLE_DOWNLOADS=1
export MARSHALL_DISABLE_AGE=1
export MARSHALL_DISABLE_TYPOSQUATTING=1
npq install @company/internal-package

Enable Only Critical Security Checks

# Disable all non-security checks
export MARSHALL_DISABLE_AGE=1
export MARSHALL_DISABLE_AUTHOR=1
export MARSHALL_DISABLE_DOWNLOADS=1
export MARSHALL_DISABLE_REPO=1
export MARSHALL_DISABLE_TYPOSQUATTING=1
export MARSHALL_DISABLE_DEPRECATION=1

# Keep security checks enabled:
# - snyk (vulnerabilities)
# - scripts (malware)
# - signatures (integrity)
# - provenance (supply chain)
# - expiredDomains (account takeover)

npq install package

Performance-Focused Configuration

# Disable slow network-dependent checks
export MARSHALL_DISABLE_REPO=1              # Avoids HTTP requests
export MARSHALL_DISABLE_DEPRECATION=1       # Avoids GitHub API calls
export MARSHALL_DISABLE_PROVENANCE=1        # Avoids sigstore verification

npq install package

Marshall Architecture

All marshalls extend the BaseMarshall class and implement the validate(pkg) method:

/**
 * Base class for all marshall implementations
 */
class BaseMarshall {
  constructor(options: { packageRepoUtils: PackageRepoUtils });
  
  // Marshall metadata
  name: string;                    // Marshall identifier
  categoryId: string;              // Category ID
  title(): string;                 // Human-readable title

  // Initialize result structure
  init(ctx: ExecutionContext): void;

  // Run validation on all packages
  run(ctx: ExecutionContext): Promise<MarshallResult[]>;

  // Validate single package
  checkPackage(pkg: PackageMetadata, ctx: ExecutionContext): Promise<any>;

  // Check if marshall is enabled
  isEnabled(): boolean;

  // Add error or warning message
  setMessage(msg: { pkg: string; message: string }, isWarning: boolean): void;

  // Resolve version specs to actual versions
  resolvePackageVersion(
    packageName: string,
    versionSpec: string,
    packageData?: any
  ): Promise<string | null>;

  // Abstract method to be implemented by subclasses
  validate(pkg: PackageMetadata): Promise<any>;
}

/**
 * Execution context passed to marshalls
 */
interface ExecutionContext {
  pkgs: PackageMetadata[];
  marshalls: {
    [marshallName: string]: MarshallResult;
  };
}

Adding Custom Marshalls

To add a custom marshall:

  1. Create a file lib/marshalls/custom.marshall.js
  2. Extend BaseMarshall
  3. Implement validate(pkg) method
  4. Throw Error for fatal issues, Warning for non-fatal
// Example custom marshall
const BaseMarshall = require('./baseMarshall');
const Warning = require('../helpers/warning');
const { marshallCategories } = require('./constants');

const MARSHALL_NAME = 'custom';

class Marshall extends BaseMarshall {
  constructor(options) {
    super(options);
    this.name = MARSHALL_NAME;
    this.categoryId = marshallCategories.PackageHealth.id;
  }

  title() {
    return 'Checking custom criteria';
  }

  async validate(pkg) {
    // Fetch package data
    const data = await this.packageRepoUtils.getPackageInfo(pkg.packageName);

    // Perform checks
    if (/* error condition */) {
      throw new Error('Error message');
    }

    if (/* warning condition */) {
      throw new Warning('Warning message');
    }

    // Return data on success
    return data;
  }
}

module.exports = Marshall;

Marshall will be automatically discovered and executed.