@zakkster/lite-argv
v1.0.0
Published
Secure, zero-dependency minimist replacement with prototype pollution protection, Unicode smuggling defense, NFKC normalization, deterministic alias groups, and a sandboxed unknown hook. Drop-in compatible.
Maintainers
Readme
@zakkster/lite-argv
Secure, zero-dependency minimist replacement with prototype pollution defense, Unicode smuggling protection, and a sandboxed unknown hook. Drop-in compatible. 364 lines.
Why replace minimist?
| Feature | lite-argv | minimist | yargs-parser | |---|---|---|---| | Prototype pollution defense | Active blocking | CVE-2021-44906 | Patched | | Unicode smuggling defense | NFKC + strip | None | None | | null-prototype argv | Yes | No | No | | Unknown hook context | reason + keyPath | Boolean only | No | | Hook sandbox (try/catch) | Yes | No | N/A | | Deterministic aliases | Sorted, frozen | Order-dependent | Yes | | --no- alias propagation | Yes | Partial | Yes | | Dotted default → alias propagation | Yes | No | Partial | | Dependencies | 0 | 0 | 6 | | Bundle size | < 3KB | ~3KB | ~30KB |
Installation
npm install @zakkster/lite-argvQuick Start
import parseArgs from '@zakkster/lite-argv';
const argv = parseArgs(process.argv.slice(2), {
string: ['name', 'output'],
boolean: ['verbose', 'dry-run'],
alias: { v: 'verbose', n: 'name', o: 'output' },
default: { verbose: false, output: './dist' },
});
// node app.js --name "my-app" -v --dry-run build
// → { name: 'my-app', verbose: true, 'dry-run': true, output: './dist', _: ['build'] }Drop-in replacement — same API as minimist:
- import parseArgs from 'minimist';
+ import parseArgs from '@zakkster/lite-parse-argv';Security
Prototype Pollution Protection
minimist had CVE-2021-44906 — an attacker could inject --__proto__.polluted=true and modify the global Object prototype.
lite-parse-argv blocks all three prototype chain keys at every segment of dotted paths:
parseArgs(['--__proto__.polluted', 'true']);
console.log({}.polluted); // undefined ✅ — blockedThe unknown hook fires with reason: 'prototype-or-control' so you can log the attempt.
Unicode Smuggling Defense
Invisible Unicode characters can smuggle keys past naive string checks. lite-parse-argv applies NFKC normalization and strips all invisible/control characters before any key is used:
parseArgs(['--a\u200B__proto__.x', '1']);
// After normalization: 'a__proto__.x' → blocked by prototype checknull-Prototype argv
The returned object uses Object.create(null) — no hasOwnProperty, toString, or constructor inherited from Object.prototype. This prevents accidental property shadowing.
Full Options Reference
| Option | Type | Default | Description |
|---|---|---|---|
| string | string | string[] | — | Keys always treated as strings (no numeric coercion) |
| boolean | boolean | string | string[] | — | Keys always treated as booleans. true = all flags without = are boolean. |
| alias | Record<string, string | string[]> | — | Alias mappings. Expanded symmetrically into sorted, deterministic groups. |
| default | Record<string, any> | — | Default values. Supports dotted paths ('a.b': 5). Propagated to aliases. |
| stopEarly | boolean | false | Populate _ with everything after first non-option. |
| unknown | Function | — | (arg, context?) => boolean \| void. Return false to block the arg. |
| '--' | boolean | false | Populate argv['--'] with everything after -- terminator. |
Recipes
import parseArgs from '@zakkster/lite-argv';
const argv = parseArgs(process.argv.slice(2), {
string: ['config', 'output'],
boolean: ['verbose', 'help', 'version'],
alias: { c: 'config', o: 'output', v: 'verbose', h: 'help', V: 'version' },
default: { config: './config.json', verbose: false },
});
if (argv.help) { console.log('Usage: ...'); process.exit(0); }
if (argv.version) { console.log('1.0.0'); process.exit(0); }
console.log('Config:', argv.config);
console.log('Verbose:', argv.verbose);
console.log('Commands:', argv._);const argv = parseArgs(process.argv.slice(2), {
boolean: ['verbose'],
unknown(arg, context) {
if (context?.reason === 'prototype-or-control') {
console.error(`SECURITY: blocked "${context.key}"`);
return false; // block it
}
if (context?.reason === 'unknown') {
console.warn(`Unknown flag: ${arg}`);
return false; // reject unknown flags
}
// positional args pass through
},
});// node app.js --db.host localhost --db.port 5432 --db.ssl
const argv = parseArgs(process.argv.slice(2), {
string: ['db.host'],
boolean: ['db.ssl'],
default: { 'db.port': 5432, 'db.ssl': false },
});
// → { db: { host: 'localhost', port: 5432, ssl: true }, _: [] }const argv = parseArgs(['--no-color'], {
alias: { color: ['c', 'colours'] },
});
// --no-color propagates to ALL aliases:
argv.color; // false
argv.c; // false
argv.colours; // false// git-style: first positional is the command, rest are its args
const argv = parseArgs(['deploy', '--force', '--env', 'prod'], {
stopEarly: true,
});
argv._; // ['deploy', '--force', '--env', 'prod']
// Parse sub-args separately:
const subArgv = parseArgs(argv._.slice(1));const argv = parseArgs(['--verbose', '--', '-x', '--raw', 'data'], {
boolean: ['verbose'],
'--': true,
});
argv.verbose; // true
argv['--']; // ['-x', '--raw', 'data'] — passed through verbatim
argv._; // []// Numbers are auto-coerced by default:
parseArgs(['--port', '3000']).port; // 3000 (number)
parseArgs(['--hex', '0xFF']).hex; // 255 (number)
// Declare as string to prevent coercion:
parseArgs(['--port', '3000'], { string: ['port'] }).port; // '3000' (string)Short Flag Behavior
-abc → a=true, b=true, c=true (combined booleans)
-abc val → a=true, b=true, c='val' (last flag consumes next)
-n123 → n=123 (attached numeric)
-n=123 → n=123 (equals syntax)
-n -1 → n=-1 (negative number consumed as value)
-abn -1 → a=true, b=true, n=-1Long Flag Behavior
--key val → key='val'
--key=val → key='val' (or boolean if declared)
--key → key=true (boolean) or key='' (string)
--no-key → key=false (propagated to aliases)
--key true → key=true
--key false → key=false
--a.b val → { a: { b: 'val' } } (dotted path)API
parseArgs(args, opts?)
| Parameter | Type | Description |
|---|---|---|
| args | string | string[] | CLI arguments or space-separated string |
| opts | ParseArgsOptions | Configuration (see options table) |
Returns: ParsedArgs — null-prototype object with _ (positional), -- (if enabled), and parsed flags.
License
MIT
