cvss-to-3.1 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1cab20d1b1295245d4bde938ea5baf30025279c7c7e9df65f2b779a863027d03
4
+ data.tar.gz: 22a198a5eb2c4db6449d0500c3e737c57abcdf632503e00bbd85cbe4c4a07a77
5
+ SHA512:
6
+ metadata.gz: 70b39e5c20802ea48af34880c5021a07092bb3df3fcb391f84de59849ecde766fb0d8aed44312f077f5c16bc94a80bd537015d0f347b58474b1d955f9febadee
7
+ data.tar.gz: 46e513232e7c5eda64e7de8dd60eab879987c112d763719a8d2d3ed41568fbd06bb80edd925074c6de56f35e09c6eb88aa0f7f281f52e09e0720bd7b159d4759
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.0.0] - 2026-09-04
6
+
7
+ First public release
8
+
9
+ ## [0.0.1] - 2026-04-14
10
+
11
+ Internal version
data/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2026, runZero, Inc.
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # cvss-to-3.1
2
+
3
+ A small Ruby gem that normalizes CVSS vectors from mixed versions into a CVSS 3.1 object.
4
+
5
+ It accepts CVSS 2.0, 3.0, 3.1, and 4.0 input and returns a scored `CvssSuite::Cvss31` object so downstream systems can compare and store values in one format.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'cvss-to-3.1'
13
+ ```
14
+
15
+ Then run:
16
+
17
+ ```bash
18
+ bundle install
19
+ ```
20
+
21
+ Or install directly:
22
+
23
+ ```bash
24
+ gem install cvss-to-3.1
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ require 'cvss_to_31'
31
+
32
+ result = CvssTo31.convert('CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H')
33
+
34
+ result.version.to_s # => "3.1"
35
+ result.vector # => "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
36
+ result.base_score # => 10.0
37
+ ```
38
+
39
+ The API accepts either a vector string or a `CvssSuite` object:
40
+
41
+ ```ruby
42
+ v2 = CvssSuite.new('AV:N/AC:L/Au:N/C:C/I:C/A:C')
43
+ CvssTo31.convert(v2)
44
+ ```
45
+
46
+ ## Supported Conversion Logic
47
+
48
+ ### CVSS 3.1 -> 3.1
49
+
50
+ Passthrough. A valid 3.1 vector/object is returned unchanged.
51
+
52
+ ### CVSS 3.0 -> 3.1
53
+
54
+ Lossless version re-stamp (`CVSS:3.0` -> `CVSS:3.1`) with metrics preserved.
55
+
56
+ ### CVSS 2.0 -> 3.1
57
+
58
+ CVSS 2.0 lacks some fields required by 3.1, so this conversion is approximate.
59
+
60
+ | CVSS 2.0 metric | CVSS 2.0 value | CVSS 3.1 metric | CVSS 3.1 value | Rationale |
61
+ |---|---|---|---|---|
62
+ | AV | N / A / L | AV | N / A / L | Direct equivalents |
63
+ | AC | L / M | AC | L | Low/Medium collapse to Low (worst-case: maximizes score) |
64
+ | AC | H | AC | H | High complexity stays High |
65
+ | Au | N | PR | N | No authentication required |
66
+ | Au | S / M | PR | H | Any authentication requirement maps to High privileges |
67
+ | C / I / A | N | C / I / A | N | No impact |
68
+ | C / I / A | P | C / I / A | L | Partial impact maps to Low |
69
+ | C / I / A | C | C / I / A | H | Complete impact maps to High |
70
+ | (none) | - | UI | N | No CVSS 2.0 equivalent |
71
+ | (none) | - | S | U | No CVSS 2.0 equivalent |
72
+
73
+ ### CVSS 4.0 -> 3.1
74
+
75
+ CVSS 4.0 includes metrics not present in 3.1 and vice versa. The gem applies the following normalization:
76
+
77
+ | CVSS 4.0 metric | CVSS 4.0 value | CVSS 3.1 metric | CVSS 3.1 value | Rationale |
78
+ |---|---|---|---|---|
79
+ | AV | N / A / L / P | AV | N / A / L / P | Direct mapping |
80
+ | AC | L / H | AC | L / H | Direct mapping |
81
+ | PR | N / L / H | PR | N / L / H | Direct mapping |
82
+ | UI | N | UI | N | Direct mapping |
83
+ | UI | A / P | UI | R | Active/Passive collapse to Required |
84
+ | VC / VI / VA | N / L / H | C / I / A | N / L / H | Victim impacts map to CIA |
85
+ | SC / SI / SA | any L or H present | S | C | Any subsequent impact implies Scope Changed |
86
+ | SC / SI / SA | all N | S | U | No subsequent impact implies Scope Unchanged |
87
+
88
+ ## Errors
89
+
90
+ `CvssTo31.convert` raises:
91
+
92
+ - `CvssTo31::Error` for invalid vectors
93
+ - `CvssTo31::UnsupportedVersionError` for unsupported CVSS versions
94
+
95
+ ## Development
96
+
97
+ Run tests:
98
+
99
+ ```bash
100
+ bundle exec rspec
101
+ ```
102
+
103
+ Build the gem:
104
+
105
+ ```bash
106
+ gem build cvss-to-3.1.gemspec
107
+ ```
108
+
109
+ ## License
110
+
111
+ Licensed under the BSD 2-Clause License. See LICENSE.
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cvss_suite'
4
+ require_relative 'error'
5
+
6
+ module CvssTo31
7
+ # Converts any supported CVSS vector to a CVSS 3.1 {CvssSuite} object.
8
+ #
9
+ # Supported source versions:
10
+ # - 2.0 (approximate — see notes below)
11
+ # - 3.0 (lossless re-stamp of the version component)
12
+ # - 3.1 (passthrough)
13
+ # - 4.0 (down-conversion — see notes below)
14
+ #
15
+ # == CVSS 2.0 → 3.1 mapping notes
16
+ #
17
+ # CVSS v2 lacks several metrics that v3.1 requires, so this conversion is
18
+ # necessarily approximate. The mapping applied is:
19
+ #
20
+ # | v2 metric | v2 value | v3.1 metric | v3.1 value | Rationale |
21
+ # |-----------|----------|-------------|------------|-----------|
22
+ # | AV | N/A/L | AV | N/A/L | Direct equivalents |
23
+ # | AC | L/M | AC | L | Low/Medium collapse to Low (worst-case: maximises score) |
24
+ # | AC | H | AC | H | High complexity stays High |
25
+ # | Au | N | PR | N | No authentication required |
26
+ # | Au | S/M | PR | H | Any authentication ≈ high privilege required |
27
+ # | C/I/A | N | C/I/A | N | No impact |
28
+ # | C/I/A | P | C/I/A | L | Partial impact ≈ Low |
29
+ # | C/I/A | C | C/I/A | H | Complete impact ≈ High |
30
+ # | (none) | — | UI | N | No v2 equivalent; defaults to None |
31
+ # | (none) | — | S | U | No v2 equivalent; defaults to Unchanged |
32
+ #
33
+ # == CVSS 4.0 → 3.1 mapping notes
34
+ #
35
+ # The down-conversion maps Base metrics directly where names match, collapses
36
+ # the v4.0 User Interaction values (Active/Passive → Required, None → None),
37
+ # and infers Scope from the three Subsequent Impact metrics (SC/SI/SA: any
38
+ # High or Low value → Changed; all None → Unchanged). Victim/Consumer Impact
39
+ # metrics (VC/VI/VA) are used for the v3.1 C/I/A values.
40
+ class Converter
41
+
42
+ # CVSS 4.0 vectors can be the longest valid form because they include base,
43
+ # threat, environmental, and supplemental fields. Even a full environmental
44
+ # 4.0 vector remains well under 512 characters, so this should be fine to
45
+ # prevent DoS effects by offering stupid long vectors.
46
+ MAX_INPUT_LENGTH = 512
47
+
48
+ # No shell escapes or SQL escapes or any of that nonsense.
49
+ ALLOWED_CVSS_CHARACTER_PATTERN = /\A[A-Za-z0-9.:\/]+\z/
50
+
51
+ class << self
52
+ # Convert a CVSS vector to CVSS 3.1.
53
+ #
54
+ # @param input [String, CvssSuite::Cvss] A CVSS vector string or an
55
+ # existing {CvssSuite} object.
56
+ # @return [CvssSuite::Cvss31] A valid CVSS 3.1 object.
57
+ # @raise [CvssTo31::Error] If the vector is invalid.
58
+ # @raise [CvssTo31::UnsupportedVersionError] If the CVSS version is not
59
+ # one of 2.0, 3.0, 3.1, or 4.0.
60
+ def convert(input)
61
+ vector = validate_input(input)
62
+ cvss = input.is_a?(String) ? CvssSuite.new(vector) : input
63
+
64
+ unless cvss.respond_to?(:valid?) && cvss.valid?
65
+ raise Error, "Invalid CVSS vector: #{vector}"
66
+ end
67
+
68
+ case cvss.version.to_s
69
+ when "3.1" then cvss
70
+ when "3.0" then from_3_0(cvss)
71
+ when "4.0" then from_4_0(cvss)
72
+ when "2" then from_2_0(cvss)
73
+ else
74
+ raise UnsupportedVersionError, "Unsupported CVSS version: #{cvss.version}"
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def validate_input(input)
81
+ case input
82
+ when String
83
+ validate_vector_string(input)
84
+ when CvssSuite::Cvss
85
+ validate_vector_string(input.vector)
86
+ else
87
+ raise Error, "Invalid input type: expected String or CvssSuite::Cvss, got #{input.class}"
88
+ end
89
+ end
90
+
91
+ def validate_vector_string(vector)
92
+ unless vector.is_a?(String)
93
+ raise Error, 'Invalid CVSS input: expected a String value'
94
+ end
95
+
96
+ if vector.length > MAX_INPUT_LENGTH
97
+ raise Error, "Invalid CVSS input: value too long (#{vector.length} > #{MAX_INPUT_LENGTH})"
98
+ end
99
+
100
+ unless vector.match?(ALLOWED_CVSS_CHARACTER_PATTERN)
101
+ raise Error, 'Invalid CVSS input: only letters, numbers, periods, colons, and slashes are allowed'
102
+ end
103
+
104
+ vector
105
+ end
106
+
107
+ def from_3_0(cvss)
108
+ CvssSuite.new(cvss.vector.sub("CVSS:3.0", "CVSS:3.1"))
109
+ end
110
+
111
+ def from_4_0(cvss)
112
+ m = parse_vector(cvss.vector)
113
+ ui = case m["UI"]; when "A", "P" then "R"; else "N"; end
114
+ scope = [m["SC"], m["SI"], m["SA"]].any? { |v| v =~ /[HL]/ } ? "C" : "U"
115
+
116
+ CvssSuite.new(
117
+ "CVSS:3.1/AV:#{m["AV"]}/AC:#{m["AC"]}/PR:#{m["PR"]}" \
118
+ "/UI:#{ui}/S:#{scope}/C:#{m["VC"]}/I:#{m["VI"]}/A:#{m["VA"]}"
119
+ )
120
+ end
121
+
122
+ def from_2_0(cvss)
123
+ m = parse_vector(cvss.vector)
124
+
125
+ CvssSuite.new(
126
+ "CVSS:3.1/AV:#{m["AV"]}/AC:#{map_v2_ac(m["AC"])}/PR:#{map_v2_au(m["Au"])}" \
127
+ "/UI:N/S:U/C:#{map_v2_impact(m["C"])}/I:#{map_v2_impact(m["I"])}/A:#{map_v2_impact(m["A"])}"
128
+ )
129
+ end
130
+
131
+ # Split a CVSS vector string into a metric hash.
132
+ # Works for all versions; the leading "CVSS:X.Y" token is stored under
133
+ # the key "CVSS" and is harmlessly ignored during metric lookups.
134
+ def parse_vector(vector)
135
+ vector.split('/').each_with_object({}) do |part, h|
136
+ key, val = part.split(':', 2)
137
+ h[key] = val
138
+ end
139
+ end
140
+
141
+ # CVSS v2 AC: L/M → L (worst-case: collapse medium to low so the score is maximised); H → H
142
+ def map_v2_ac(ac)
143
+ case ac
144
+ when "L", "M" then "L"
145
+ when "H" then "H"
146
+ else raise Error, "Unknown CVSS v2 AC value: #{ac}"
147
+ end
148
+ end
149
+
150
+ # CVSS v2 Au → v3.1 PR: N → N; S or M → H (any auth requirement = high privilege)
151
+ def map_v2_au(au)
152
+ case au
153
+ when "S", "M" then "H"
154
+ when "N" then "N"
155
+ else raise Error, "Unknown CVSS v2 Au value: #{au}"
156
+ end
157
+ end
158
+
159
+ # CVSS v2 C/I/A impact: N → N, P → L, C → H
160
+ def map_v2_impact(val)
161
+ case val
162
+ when "N" then "N"
163
+ when "P" then "L"
164
+ when "C" then "H"
165
+ else raise Error, "Unknown CVSS v2 impact value: #{val}"
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CvssTo31
4
+ # Raised when a CVSS vector string is malformed or fails cvss-suite validation.
5
+ class Error < StandardError; end
6
+
7
+ # Raised when the CVSS version of the input cannot be converted to 3.1
8
+ # (e.g. an unrecognised future version).
9
+ class UnsupportedVersionError < Error; end
10
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CvssTo31
4
+ VERSION = "1.0.0"
5
+ end
data/lib/cvss_to_31.rb ADDED
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cvss_to_31/version'
4
+ require_relative 'cvss_to_31/error'
5
+ require_relative 'cvss_to_31/converter'
6
+
7
+ # Top-level namespace for the cvss-to-3.1 gem.
8
+ #
9
+ # Converts any supported CVSS vector (v2.0, v3.0, v3.1, or v4.0) to a
10
+ # normalised CVSS 3.1 {CvssSuite} object.
11
+ #
12
+ # @example Convert a CVSS 4.0 vector
13
+ # result = CvssTo31.convert("CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H")
14
+ # result.base_score #=> 10.0
15
+ # result.vector #=> "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"
16
+ #
17
+ # @example Convert a CVSS 2.0 vector
18
+ # result = CvssTo31.convert("AV:N/AC:L/Au:N/C:C/I:C/A:C")
19
+ # result.base_score #=> 9.8
20
+ # result.vector #=> "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
21
+ module CvssTo31
22
+ # Convert a CVSS vector string or {CvssSuite} object to CVSS 3.1.
23
+ #
24
+ # @param input [String, CvssSuite::Cvss] CVSS vector or existing object.
25
+ # @return [CvssSuite::Cvss31] A valid, scored CVSS 3.1 object.
26
+ # @raise [CvssTo31::Error] On invalid input.
27
+ # @raise [CvssTo31::UnsupportedVersionError] On an unrecognised CVSS version.
28
+ def self.convert(input)
29
+ Converter.convert(input)
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cvss-to-3.1
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tod Beardsley
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: cvss-suite
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '4.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '4.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.13'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.13'
40
+ description: |
41
+ A small, focused gem that normalises CVSS vectors from any version
42
+ (2.0, 3.0, 3.1, or 4.0) to a CVSS 3.1 CvssSuite object. Useful
43
+ whenever a tool must compare or store scores across a live CVE feed
44
+ that mixes CVSS versions.
45
+ email:
46
+ - todb@runzero.com
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - CHANGELOG.md
52
+ - LICENSE
53
+ - README.md
54
+ - lib/cvss_to_31.rb
55
+ - lib/cvss_to_31/converter.rb
56
+ - lib/cvss_to_31/error.rb
57
+ - lib/cvss_to_31/version.rb
58
+ homepage: https://github.com/runZeroInc/cvss-to-3.1
59
+ licenses:
60
+ - BSD-2-Clause
61
+ metadata:
62
+ source_code_uri: https://github.com/runZeroInc/cvss-to-3.1
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '3.1'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 4.0.20
78
+ specification_version: 4
79
+ summary: Convert any CVSS vector (v2, v3.0, v4.0) to a normalised CVSS 3.1 score
80
+ test_files: []