Class: Brew::Vulns::OsvClient

Inherits:
Object
  • Object
show all
Defined in:
lib/brew/vulns/osv_client.rb

Defined Under Namespace

Classes: ApiError, Error

Constant Summary collapse

API_BASE =
"https://api.osv.dev/v1"
BATCH_SIZE =
1000
OPEN_TIMEOUT =
10
READ_TIMEOUT =
30

Instance Method Summary collapse

Instance Method Details

#execute_request(uri, request) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/brew/vulns/osv_client.rb', line 80

def execute_request(uri, request)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == "https"
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  http.open_timeout = OPEN_TIMEOUT
  http.read_timeout = READ_TIMEOUT

  response = http.request(request)

  case response
  when Net::HTTPSuccess
    JSON.parse(response.body)
  else
    raise ApiError, "OSV API error: #{response.code} #{response.message}"
  end
rescue JSON::ParserError => e
  raise ApiError, "Invalid JSON response from OSV API: #{e.message}"
rescue Net::OpenTimeout, Net::ReadTimeout => e
  raise ApiError, "OSV API timeout: #{e.message}"
rescue SocketError, Errno::ECONNREFUSED => e
  raise ApiError, "OSV API connection error: #{e.message}"
rescue OpenSSL::SSL::SSLError => e
  raise ApiError, "OSV API SSL error: #{e.message}"
end

#fetch_all_pages(response, original_payload) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/brew/vulns/osv_client.rb', line 105

def fetch_all_pages(response, original_payload)
  vulns = response["vulns"] || []
  page_token = response["next_page_token"]

  while page_token
    payload = original_payload.merge(page_token: page_token)
    response = post("/query", payload)
    vulns.concat(response["vulns"] || [])
    page_token = response["next_page_token"]
  end

  vulns
end

#get(path) ⇒ Object



72
73
74
75
76
77
78
# File 'lib/brew/vulns/osv_client.rb', line 72

def get(path)
  uri = URI("#{API_BASE}#{path}")
  request = Net::HTTP::Get.new(uri)
  request["Content-Type"] = "application/json"

  execute_request(uri, request)
end

#get_vulnerability(vuln_id) ⇒ Object



59
60
61
# File 'lib/brew/vulns/osv_client.rb', line 59

def get_vulnerability(vuln_id)
  get("/vulns/#{URI.encode_uri_component(vuln_id)}")
end

#post(path, payload) ⇒ Object



63
64
65
66
67
68
69
70
# File 'lib/brew/vulns/osv_client.rb', line 63

def post(path, payload)
  uri = URI("#{API_BASE}#{path}")
  request = Net::HTTP::Post.new(uri)
  request["Content-Type"] = "application/json"
  request.body = JSON.generate(payload)

  execute_request(uri, request)
end

#query(repo_url:, version:) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/brew/vulns/osv_client.rb', line 18

def query(repo_url:, version:)
  payload = {
    package: {
      name: repo_url,
      ecosystem: "GIT"
    },
    version: version
  }

  response = post("/query", payload)
  fetch_all_pages(response, payload)
end

#query_batch(packages) ⇒ Object



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
# File 'lib/brew/vulns/osv_client.rb', line 31

def query_batch(packages)
  return [] if packages.empty?

  results = Array.new(packages.size) { [] }

  packages.each_slice(BATCH_SIZE).with_index do |batch, batch_idx|
    queries = batch.map do |pkg|
      {
        package: {
          name: pkg[:repo_url],
          ecosystem: "GIT"
        },
        version: pkg[:version]
      }
    end

    response = post("/querybatch", { queries: queries })
    batch_results = response["results"] || []

    batch_results.each_with_index do |result, idx|
      global_idx = batch_idx * BATCH_SIZE + idx
      results[global_idx] = result["vulns"] || []
    end
  end

  results
end