class
Analyzer::Javascript::Cli
Overview
Surfaces the command-line attack surface of JavaScript/TypeScript programs
(Node, Deno, Bun) as cli:// endpoints: one endpoint per (sub)command
with named options (param_type "flag"), positional arguments ("argument")
and consumed environment variables ("env"). Covers the util.parseArgs
builtin plus commander, yargs, cac, sade, meow, minimist, arg,
command-line-args, getopts and citty. A single analyzer scans every JS/TS
extension so a .ts CLI isn't double-counted.
Defined in:
analyzer/analyzers/javascript/cli.crConstant Summary
-
ARG_CALL_RE =
/\barg\s*\(\s*\{/ -
arg:
arg({ '--name': String, '-n': '--name' }). Keys whose value is a quoted string are aliases pointing at the canonical flag and are skipped so an alias doesn't surface as a second, meaningless flag. -
ARG_KEY_RE =
/^\s*['"](-{1,2}[A-Za-z][\w-]*)['"]\s*:\s*(.+?),?\s*$/ -
ARGUMENT_RE =
/\.\s*argument\s*\(\s*['"][<\[]?\.{0,3}([A-Za-z0-9_-]+)/ -
ARGV_SLICE =
/\bprocess\.argv\.slice\s*\(\s*2\s*\)/ -
BUN_ARGV =
/\bBun\.argv\b/ -
BUN_ENV =
/\bBun\.env\.([A-Za-z_][A-Za-z0-9_]*)/ -
CITTY_ARGS_HEADER =
/\bargs\s*:\s*\{/ -
CITTY_DEFINE_ASSIGN_RE =
/^\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*defineCommand\s*\(\s*\{/ -
citty also allows each subcommand to be declared as its own top-level
const NAME = defineCommand({...})and merely referenced by identifier insidesubCommands: { key: NAME }, instead of nesting thedefineCommandcall inline.resolve_citty_var_subcommandsresolves those same-file references soargs:inside NAME's block attributes to<root>/<key>rather than falling back to root. -
CITTY_SUBCOMMAND_RE =
/^\s*['"]?([A-Za-z_$][\w$-]*)['"]?\s*:\s*defineCommand\s*\(\s*\{/ -
citty:
serve: defineCommand({ args: { port: { type: 'string' } } }). Subcommands nest their owndefineCommandobject insidesubCommands, so args are attributed via a brace-depth stack (never a sticky cursor). -
CITTY_SUBCOMMAND_REF_RE =
/^\s*['"]?([A-Za-z_$][\w$-]*)['"]?\s*:\s*([A-Za-z_$][\w$]*)\s*,?\s*$/ -
CITTY_SUBCOMMANDS_HEADER =
/\bsubCommands\s*:\s*\{/ -
CLA_ARRAY_ASSIGN_RE =
/^\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*\[/ -
command-line-args:
commandLineArgs(optionDefinitions)referencing a separately-declaredconst optionDefinitions = [...]array (the library's own documented convention), or an inlinecommandLineArgs([...])array. Matching is bounded to the resolved array's own brackets (never a whole-file scan) so an unrelated same-shaped object literal elsewhere in the file (e.g. a content-field schema) is never picked up as a bogus flag, and each{...}entry is scanned as its own brace-bounded block so Prettier-style multi-line-per-key formatting isn't silently dropped. -
CLA_CALL_INLINE_RE =
/\bcommandLineArgs\s*\(\s*\[/ -
CLA_CALL_VAR_RE =
/\bcommandLineArgs\s*\(\s*([A-Za-z_$][\w$]*)\s*[,)]/ -
CLA_NAME_RE =
/\bname\s*:\s*['"]([A-Za-z0-9_-]+)['"]/ -
CLA_TYPE_KEY_RE =
/\btype\s*:/ -
CLI_MARKERS_RE =
Regex.union("commander", "yargs", "cac", "meow", "minimist", "clipanion", "@oclif", "sade", "parseArgs", "Deno.args", "Bun.argv") -
Precompiled once — a single PCRE2-JIT scan replaces up to eleven naive String#includes? substring scans over the whole file content. Regex.union auto-escapes each literal (including the dots in "Deno.args"/"Bun.argv"), so it is provably equivalent to the OR-of-includes? it replaces.
-
COMMAND_RE =
/\.\s*command\s*\(\s*['"]([^'"]+)['"]/ -
commander / cac / sade / yargs subcommand. The command string may carry an arg spec (
serve <port>), so the first whitespace token is the name. -
DENO_ARGS =
/\bDeno\.args\b/ -
builtin / runtime argv markers (root surface).
-
DENO_ENV =
/\bDeno\.env\.get\s*\(\s*['"]([^'"]+)['"]/ -
ENV_DOT =
/\bprocess\.env\.([A-Za-z_][A-Za-z0-9_]*)/ -
env reads (gated). NODE_ENV is config plumbing, not an input.
-
ENV_INDEX =
/\bprocess\.env\s*\[\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]\s*\]/ -
GETOPTS_ALIAS_HEADER =
/\balias\s*:\s*\{/ -
GETOPTS_BOOLEAN_HEADER =
/\bboolean\s*:\s*\[/ -
GETOPTS_CALL_RE =
/\bgetopts\s*\(/ -
getopts:
getopts(process.argv.slice(2), { alias: {...}, default: {...}, boolean: [...], string: [...] }). Bounded to the call's own parens so unrelatedalias/defaultobject literals elsewhere aren't picked up. -
GETOPTS_DEFAULT_HEADER =
/\bdefault\s*:\s*\{/ -
GETOPTS_STRING_HEADER =
/\bstring\s*:\s*\[/ -
LIB_IMPORT_ONLY_RE =
/(?:require\s*\(\s*|from\s+)['"](?:arg|getopts|citty|command-line-args)['"]/ -
require('arg')/from 'arg', and likewise for getopts/citty/ command-line-args — these are short/generic tokens too easy to trip as a bare substring (e.g. bash's owngetoptsbuiltin, a stray comment, or an ordinary identifier fragment), so they require an actual import statement rather thancontent.includes?. -
LONG_FLAG_RE =
/(--[A-Za-z0-9][\w-]*)/ -
MEOW_FLAGS_HEADER =
/\bflags\s*:\s*\{/ -
NEW_CHAIN_RE =
/^[A-Za-z_$][\w$]*\s*\.\s*(?:command|option|argument)\b/ -
A fresh statement that chains command/option/argument off an identifier (
program.option(...),cli.command(...)) starts at the root program, not the previously-seen subcommand — used to reset the cursor so a global option declared after a subcommand isn't mis-attributed to it. -
OBJECT_KEY_RE =
/^\s*['"]?([A-Za-z_$][\w$-]*)['"]?\s*:/ -
OBJECT_VALUE_RE =
/^\s*['"]?[A-Za-z_$][\w$-]*['"]?\s*:\s*['"]([A-Za-z0-9_-]+)['"]/ -
OPTION_RE =
/\.\s*(?:required)?[Oo]ption\s*\(\s*['"]([^'"]+)['"]/ -
commander/cac/sade:
.option('-p, --port <n>'); yargs:.option('port'). -
PARSEARGS_HEADER =
/\boptions\s*:\s*\{/ -
object-literal schema headers (keys become root flags).
-
POSITIONAL_RE =
/\.\s*positional\s*\(\s*['"]([^'"]+)['"]/ -
SHORT_FLAG_RE =
/(-[A-Za-z0-9])\b/ -
SOURCE_EXTS =
[".js", ".mjs", ".cjs", ".jsx", ".ts", ".mts", ".cts", ".tsx"] -
STRING_LIT_RE =
/['"]([A-Za-z0-9_.\/-]+)['"]/ -
WEB_FRAMEWORK_RE =
/(?:require\s*\(\s*|from\s+)['"](?:express|fastify|koa|@hapi\/hapi|@nestjs\/[\w-]+|next|nuxt|hono|@hono\/[\w-]+|restify|@adonisjs\/[\w-]+|elysia|polka|connect|h3|@sveltejs\/[\w-]+|@remix-run\/[\w-]+|apollo-server|@apollo\/server)['"]/ -
WEB_LISTEN_RE =
/\.\s*listen\s*\(|\bcreateServer\s*\(/