Class: Scryer::CLI
- Inherits:
-
Object
- Object
- Scryer::CLI
- Defined in:
- lib/scryer/cli.rb
Overview
Backs the scryer executable (see exe/scryer) — a standalone,
Rails-free way to run a scan, mirroring brakeman -o report.json.
Deliberately separate from lib/scryer.rb: OptionParser/Shellwords are
only needed for this CLI entry point, not when the gem is required
inside a host app.
Constant Summary collapse
- EXTENSION_FORMATS =
{ ".json" => "json", ".html" => "html", ".htm" => "html", ".csv" => "csv", ".sarif" => "sarif" }.freeze
Instance Method Summary collapse
-
#initialize(argv, stdout: $stdout, stderr: $stderr) ⇒ CLI
constructor
A new instance of CLI.
-
#run ⇒ Object
Returns a process exit code: 0 if clean, 1 if security findings were found (so
scryer -o report.jsoncan gate CI the waybrakeman -o report.jsondoes), 2 on a usage error.
Constructor Details
#initialize(argv, stdout: $stdout, stderr: $stderr) ⇒ CLI
Returns a new instance of CLI.
14 15 16 17 18 |
# File 'lib/scryer/cli.rb', line 14 def initialize(argv, stdout: $stdout, stderr: $stderr) @argv = argv @stdout = stdout @stderr = stderr end |
Instance Method Details
#run ⇒ Object
Returns a process exit code: 0 if clean, 1 if security findings were
found (so scryer -o report.json can gate CI the way brakeman -o report.json does), 2 on a usage error.
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/scryer/cli.rb', line 23 def run = parse(@argv) return 0 if [:exit_early] # The standalone executable has no equivalent of a Rails app's # config/initializers/scryer.rb getting autoloaded at boot — this is # the only way to run Scryer.configure (set c.ai_client, c.skip_rules, # c.dirs, ...) before a scan starts outside Rails. Array([:require]).each { |path| require File.(path) } return check_gem([:check_gem]) if [:check_gem] root = File.([:path] || Dir.pwd) return audit_deps(root) if [:audit_deps] skip_rules = Scryer.configuration.skip_rules + ([:skip] || []) @stdout.puts "Scryer: skipping #{skip_rules.join(', ')}." if skip_rules.any? result = Scanner.new(root: root, dirs: Scryer.configuration.dirs, skip_rules: skip_rules).call # Dependency auditing (OSV.dev) runs by default — a single `scryer` # invocation is meant to cover the same ground as RuboCop + Brakeman + # bundler-audit + Reek run separately, and that story isn't true if # the dependency half is silently skipped unless you remember a flag. # `--no-deps` opts back out for a fast, fully offline run (e.g. no # network in this environment, or you only want the static scan). ran_deps = ![:no_deps] dependency_findings = [] if ran_deps @stdout.puts "Scryer: querying OSV.dev for known-vulnerable gems (needs network)..." dependency_findings = DependencyAudit.insecure_sources(root) + DependencyAudit.vulnerable_gems(root) + DependencyAudit.ruby_eol_check(root) + DependencyAudit.credentials_exposure_check(root) end if Scryer.configuration.ai_client @stdout.puts "Scryer: rewriting suggested fixes via the configured AI client..." AiFixSuggester.enhance_result!(result) AiFixSuggester.enhance_many!(dependency_findings) unless dependency_findings.empty? end renderer = ReportRenderer.new( result: result, project_name: [:project_name] || File.basename(root), release_label: git(root, "describe --tags --always"), git_commit_sha: git(root, "rev-parse HEAD"), git_branch: [:branch] || git(root, "rev-parse --abbrev-ref HEAD"), dependency_findings: dependency_findings ) outputs = [:outputs].empty? ? default_outputs(root) : [:outputs] outputs.each { |path| write_report(renderer, path) } print_summary(result: result, dependency_findings: dependency_findings, ran_deps: ran_deps, outputs: outputs) result.security_findings.empty? && dependency_findings.empty? ? 0 : 1 rescue UsageError => e @stderr.puts "scryer: #{e.}" 2 end |