module
Noir::CliValidation
Defined in:
cli_validation.crConstant Summary
-
MAX_CONCURRENCY =
256 -
Upper bound for --concurrency. A user asking for hundreds of thousands of fibers doesn't get more throughput — it just exhausts file descriptors / sockets and can take the process down. Cap at a value far above any useful setting and warn rather than silently honoring it.
Class Method Summary
- .exit_with_error(message : String) : NoReturn
- .validate!(options : Hash(String, YAML::Any))
-
.validate_acp_target!(provider : String)
Pre-flight rejection of an ACP target Noir will not exec, so an untrusted
--ai-provider "acp:<cmd>"fails cleanly before a scan starts rather than raising from the exec sink partway through one. -
.validate_ai_integer_options!(options : Hash(String, YAML::Any))
--ai-max-token/--ai-agent-max-stepsare bounded bypositive_int_or_die!on the CLI side. -
.validate_ai_native_tools_allowlist!(options : Hash(String, YAML::Any))
--ai-native-tools-allowlistentries that don't canonicalize to a provider Noir supports for native tool-calling will never match and the feature just stays off. -
.validate_ai_provider_pair!(options : Hash(String, YAML::Any))
--ai-providerand--ai-modelneed each other to take effect. - .validate_base_paths!(options : Hash(String, YAML::Any))
- .validate_concurrency!(options : Hash(String, YAML::Any))
-
.validate_config_file!(options : Hash(String, YAML::Any))
--config-file PATHneeds to exist, be a file (not a directory), and parse as a YAML mapping. - .validate_output_format!(options : Hash(String, YAML::Any))
- .validate_output_path!(options : Hash(String, YAML::Any))
-
.validate_passive_scan_paths!(options : Hash(String, YAML::Any))
--passive-scan-path PATHaccepts multiple entries (repeatable), and each must exist + be a directory —NoirPassiveScan.load_ruleswalksPATH/**/*.{yml,yaml}and Dir.glob silently returns zero matches for non-existent paths. -
.validate_passive_scan_severity!(options : Hash(String, YAML::Any))
--passive-scan-severityis checked by its own flag handler in options.cr, but the identicalpassive_scan_severity:config key reachedPassiveScanSeverityuntouched — andmeets_threshold?treats an unrecognized threshold as "include everything" (return true if min_level.nil?). - .validate_tagger_names!(options : Hash(String, YAML::Any))
-
.validate_tech_names!(options : Hash(String, YAML::Any))
-t/--techs,--only-techs,--exclude-techsshould reject unknown tech names eagerly. -
.warn_about_contradictory_probe_filters(options : Hash(String, YAML::Any))
A value listed in both --probe-match and --probe-skip is contradictory: skip wins in the Deliver pipeline, so the matcher is silently defeated.
-
.warn_about_unused_delivery_flags(options : Hash(String, YAML::Any))
--probe-matchand--probe-skiponly run inside the Deliver pipeline (the code that ships endpoints to --probe / --probe-via / --export-es / --export-webhook).
Class Method Detail
Pre-flight rejection of an ACP target Noir will not exec, so an untrusted
--ai-provider "acp:<cmd>" fails cleanly before a scan starts rather than
raising from the exec sink partway through one. The list itself lives in
LLM::ACPTargets and is shared with that sink — it used to be a second
byte-identical literal here, kept in sync by a comment.
--ai-max-token / --ai-agent-max-steps are bounded by
positive_int_or_die! on the CLI side. Their config-file twins had no
bound at all, so a negative or zero step budget went straight into the
agent loop. ConfigInitializer has already coerced the value to an Int by
this point; all that's left is the range.
Zero is meaningful for ai_max_token only — it is the shipped default
and the generated template's own value, documented as "no limit" — so it
is accepted here even though --ai-max-token 0 is not.
--ai-native-tools-allowlist entries that don't canonicalize to a
provider Noir supports for native tool-calling will never match and the
feature just stays off. Warn so a typo like opena is visible instead
of silently disabling tool-calling.
--ai-provider and --ai-model need each other to take effect.
Either flag set on its own silently skips the AI analyzer:
--ai-provider openai(no model) —ai_provider_active?is false, AI never runs, user just gets a plain scan--ai-model gpt-4(no provider) — same outcome The ACP family is the one exception:acp:claude/acp:codexcarry their own default model, so no--ai-modelis required.
--config-file PATH needs to exist, be a file (not a directory),
and parse as a YAML mapping. The previous shape let File.read /
YAML.parse raise straight through to the user, producing a
Crystal stack trace for what should be a one-line "wrong path"
message.
--passive-scan-path PATH accepts multiple entries (repeatable),
and each must exist + be a directory — NoirPassiveScan.load_rules
walks PATH/**/*.{yml,yaml} and Dir.glob silently returns zero
matches for non-existent paths. The result is a passive scan that
ran with 0 rules and surfaced 0 findings, with no indication that
the path was bogus. Surface the typo at CLI parse time instead.
--passive-scan-severity is checked by its own flag handler in
options.cr, but the identical passive_scan_severity: config key reached
PassiveScanSeverity untouched — and meets_threshold? treats an
unrecognized threshold as "include everything" (return true if min_level.nil?). So a typo in the config file didn't loosen the filter
by one level, it removed it, silently, in the direction the user least
wanted. Gate both sources here, in the one place config and CLI meet.
-t/--techs, --only-techs, --exclude-techs should reject
unknown tech names eagerly. Pre-fix, a typo like --only-techs falsk (instead of flask) silently dropped through
NoirTechs.similar_to_tech returning "" and the scan produced
zero endpoints without explanation — indistinguishable from
"no flask code found here". Surfacing the typo at CLI parse
time saves the surprise.
A value listed in both --probe-match and --probe-skip is contradictory: skip wins in the Deliver pipeline, so the matcher is silently defeated. Surface the overlap instead of quietly filtering it out.
--probe-match and --probe-skip only run inside the Deliver
pipeline (the code that ships endpoints to --probe / --probe-via /
--export-es / --export-webhook). With no delivery target
configured they silently no-op — a real surprise for users who
set the flags expecting stdout output to be filtered. Warn at
CLI parse time so the gap is obvious.