module Noir::CliValidation

Defined in:

cli_validation.cr

Constant Summary

ACP_KNOWN_TARGETS = ["codex", "gemini", "claude", "claude-code"] of ::String

ACP targets Noir knows how to launch. Kept in sync with LLM::ACPClient::KNOWN_TARGETS (the actual exec sink). Anything else would be run as an arbitrary local process, so a --ai-provider "acp:<cmd>" from an untrusted config file is refused here — a clean pre-flight error instead of the sink's mid-scan raise. See issue: acp arbitrary exec.

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.

VALID_OUTPUT_FORMATS = ["plain", "yaml", "json", "jsonl", "toml", "markdown-table", "sarif", "html", "curl", "httpie", "powershell", "adb", "simctl", "oas2", "oas3", "postman", "only-url", "only-param", "only-header", "only-cookie", "only-tag", "mermaid"] of ::String

Class Method Summary

Class Method Detail

def self.exit_with_error(message : String) : NoReturn #

[View source]
def self.validate!(options : Hash(String, YAML::Any)) #

[View source]
def self.validate_acp_target!(provider : String) #

[View source]
def self.validate_ai_native_tools_allowlist!(options : Hash(String, YAML::Any)) #

--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.


[View source]
def self.validate_ai_provider_pair!(options : Hash(String, YAML::Any)) #

--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:codex carry their own default model, so no --ai-model is required.

[View source]
def self.validate_base_paths!(options : Hash(String, YAML::Any)) #

[View source]
def self.validate_concurrency!(options : Hash(String, YAML::Any)) #

[View source]
def self.validate_config_file!(options : Hash(String, YAML::Any)) #

--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.


[View source]
def self.validate_output_format!(options : Hash(String, YAML::Any)) #

[View source]
def self.validate_output_path!(options : Hash(String, YAML::Any)) #

[View source]
def self.validate_passive_scan_paths!(options : Hash(String, YAML::Any)) #

--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.


[View source]
def self.validate_tagger_names!(options : Hash(String, YAML::Any)) #

[View source]
def self.validate_tech_names!(options : Hash(String, YAML::Any)) #

-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.


[View source]
def self.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. Surface the overlap instead of quietly filtering it out.


[View source]
def self.warn_about_unused_delivery_flags(options : Hash(String, YAML::Any)) #

--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.


[View source]