Class: Zwischen::Scanner::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/zwischen/scanner/base.rb

Direct Known Subclasses

Gitleaks, Semgrep

Constant Summary collapse

ZWISCHEN_BIN_DIR =
File.expand_path("~/.zwischen/bin")

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, command:) ⇒ Base

Returns a new instance of Base.



13
14
15
16
17
# File 'lib/zwischen/scanner/base.rb', line 13

def initialize(name:, command:)
  @name = name
  @command = command
  @executable_path = nil
end

Instance Attribute Details

#commandObject (readonly)

Returns the value of attribute command.



9
10
11
# File 'lib/zwischen/scanner/base.rb', line 9

def command
  @command
end

#nameObject (readonly)

Returns the value of attribute name.



9
10
11
# File 'lib/zwischen/scanner/base.rb', line 9

def name
  @name
end

Instance Method Details

#available?Boolean

Returns:

  • (Boolean)


19
20
21
# File 'lib/zwischen/scanner/base.rb', line 19

def available?
  !executable_path.nil?
end

#executable_pathObject

Find executable in ~/.zwischen/bin or system PATH



24
25
26
# File 'lib/zwischen/scanner/base.rb', line 24

def executable_path
  @executable_path ||= find_executable(@command)
end

#find_executable(name) ⇒ Object



28
29
30
31
32
33
34
35
# File 'lib/zwischen/scanner/base.rb', line 28

def find_executable(name)
  # Check ~/.zwischen/bin first
  local = File.join(ZWISCHEN_BIN_DIR, name)
  return local if File.executable?(local)

  # Fall back to system PATH
  system("which", name, out: File::NULL, err: File::NULL) ? name : nil
end

#parse_output(_output) ⇒ Object

Raises:

  • (NotImplementedError)


62
63
64
# File 'lib/zwischen/scanner/base.rb', line 62

def parse_output(_output)
  raise NotImplementedError, "Subclasses must implement parse_output"
end

#scan(project_root = Dir.pwd, files: nil) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/zwischen/scanner/base.rb', line 37

def scan(project_root = Dir.pwd, files: nil)
  return [] unless available?

  if files && !files.empty?
    return scan_files(files, project_root) if respond_to?(:scan_files, true)
    command = build_command_for_files(files, project_root)
  else
    command = build_command(project_root)
  end

  stdout, stderr, status = Open3.capture3(*command, chdir: project_root)

  # Most security scanners use exit code 0 = clean, 1 = findings found, 2+ = error
  # We treat both 0 and 1 as success since findings are valid results
  if status.exitstatus <= 1
    parse_output(stdout)
  else
    warn "Warning: #{@name} scan failed (exit #{status.exitstatus}): #{stderr}" unless stderr.empty?
    []
  end
rescue StandardError => e
  warn "Error running #{@name}: #{e.message}"
  []
end