Security auditing CLI tool that performs pre-installation checks on npm packages to detect vulnerabilities, malware, and supply chain risks before installation
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.
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:
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 agoEdge Cases:
time metadata: Falls back to version-specific timeDisable:
MARSHALL_DISABLE_AGE=1 npq install new-packageValidates 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:
maintainers array and _npmUser fieldExample 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 agoEdge Cases:
_npmUser field as fallbackDisable:
MARSHALL_DISABLE_AUTHOR=1 npq install packageDetects 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:
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 errorsExample Output:
Error: Package version 2.0.0 is deprecated: Please use v3.x instead
Error: GitHub repository is archived and no longer maintainedEdge Cases:
GitHub Token Setup:
export GITHUB_TOKEN=ghp_your_token_hereDisable:
MARSHALL_DISABLE_DEPRECATION=1 npq install old-packageValidates 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:
Disable:
MARSHALL_DISABLE_DOWNLOADS=1 npq install unpopular-packageDetects 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:
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 domainExample Output:
Error: Maintainer email domain expired: user@expireddomain.comEdge Cases:
Disable:
MARSHALL_DISABLE_MAINTAINERS_EXPIRED_EMAILS=1 npq install packageValidates 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:
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 packageEdge Cases:
Disable:
MARSHALL_DISABLE_LICENSE=1 npq install unlicensed-packageDetects 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:
bin field between current and previous versionnode_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 versionExample Output:
Warning: New binary introduced: malicious-cmd (./bin/cmd.js)Edge Cases:
Disable:
MARSHALL_DISABLE_NEWBIN=1 npq install packageVerifies 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:
https://registry.npmjs.org/-/npm/v1/keysProvenance 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 attestationsEdge Cases:
Disable:
MARSHALL_DISABLE_PROVENANCE=1 npq install packageValidates 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:
URL Sources:
repository field in package.jsonhomepage field in package.jsonExample 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 homepageEdge Cases:
Disable:
MARSHALL_DISABLE_REPO=1 npq install packageDetects 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:
installpreinstallpostinstallSecurity Risk: Install scripts execute arbitrary code during package installation, which can be used for:
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 scriptExample Output:
Error: Package has preinstall script (potential malware)
Error: Package has postinstall script (potential malware)Edge Cases:
Disable:
MARSHALL_DISABLE_SCRIPTS=1 npq install package-with-scriptsVerifies 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:
registryKeysCache singletonVerification Process:
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 signatureEdge Cases:
Disable:
MARSHALL_DISABLE_SIGNATURES=1 npq install packageChecks 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:
Snyk API (Primary, if token available):
SNYK_TOKEN or SNYK_API_TOKEN environment variable~/.config/configstore/snyk.jsonhttps://snyk.io/api/v1/vuln/npm/{package}@{version}OSV API (Fallback, if no Snyk token):
https://api.osv.dev/v1/queryMalicious Package Detection:
vulnerability.title === 'Malicious Package'vuln.database_specific['malicious-packages-origins']Environment Variables:
SNYK_TOKEN or SNYK_API_TOKEN: API authenticationSNYK_API_URL or SNYK_API: Custom API endpointExample 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-nameEdge Cases:
Snyk Setup:
# Option 1: Environment variable
export SNYK_TOKEN=your-snyk-token
# Option 2: Snyk CLI (creates config file)
npm install -g snyk
snyk authDisable:
MARSHALL_DISABLE_SNYK=1 npq install packageDetects 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:
data/top-packages.json['npq', 'ai', 'bun', 'deno'] (exempted from checks)Typosquatting Examples:
expresss → express (distance: 1)loadash → lodash (distance: 1)reqeust → request (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 matchExample Output:
Error: Package name is similar to popular package "express" (possible typosquatting)Edge Cases:
Disable:
MARSHALL_DISABLE_TYPOSQUATTING=1 npq install expresssDetects 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:
Difference from Age Marshall:
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:
Disable:
MARSHALL_DISABLE_VERSION_MATURITY=1 npq install package@latest| Marshall | Category | Error Conditions | Warning Conditions | Env Variable |
|---|---|---|---|---|
| age | PackageHealth | Package < 22 days old | Version > 365 days old | MARSHALL_DISABLE_AGE |
| author | SupplyChainSecurity | First publish by author < 21 days, Version < 7 days | Version 7-30 days old | MARSHALL_DISABLE_AUTHOR |
| deprecation | PackageHealth | Deprecated, Repo archived | - | MARSHALL_DISABLE_DEPRECATION |
| downloads | PackageHealth | < 100/month | 100-10000/month | MARSHALL_DISABLE_DOWNLOADS |
| expiredDomains | PackageHealth | Maintainer domain expired | - | MARSHALL_DISABLE_MAINTAINERS_EXPIRED_EMAILS |
| license | SupplyChainSecurity | No license field | - | MARSHALL_DISABLE_LICENSE |
| newBin | SupplyChainSecurity | - | New binary introduced | MARSHALL_DISABLE_NEWBIN |
| provenance | SupplyChainSecurity | - | No provenance attestations | MARSHALL_DISABLE_PROVENANCE |
| repo | PackageHealth | No accessible repo/homepage | Repo/homepage inaccessible | MARSHALL_DISABLE_REPO |
| scripts | MalwareDetection | Pre/post install scripts | - | MARSHALL_DISABLE_SCRIPTS |
| signatures | SupplyChainSecurity | Invalid signature, Missing key | Expired key | MARSHALL_DISABLE_SIGNATURES |
| snyk | SupplyChainSecurity | Vulnerabilities found, Malicious package | - | MARSHALL_DISABLE_SNYK |
| typosquatting | PackageHealth | Name similar to popular package | - | MARSHALL_DISABLE_TYPOSQUATTING |
| version-maturity | SupplyChainSecurity | Version < 7 days old | - | MARSHALL_DISABLE_VERSION_MATURITY |
Errors (Fatal):
Warnings (Non-fatal):
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.
# 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# 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# 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 packageAll 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;
};
}To add a custom marshall:
lib/marshalls/custom.marshall.jsBaseMarshallvalidate(pkg) methodError 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.
Install with Tessl CLI
npx tessl i tessl/npm-npq