Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

### Removed

## [v0.13.7](https://github.com/bugcrowd/vrt-ruby/compare/v0.13.6...v0.13.7) - 2026-03-09

### Added

- CVSS v4 mapping support: `cvss_v4` in `VRT::MAPPINGS` and `node.mappings[:cvss_v4]` when mapping data is present
- Specs for cvss_v4 mappings (mapping file loading, `#get` for leaf/internal/root nodes, default, and node `#mappings`)

## [v0.13.6](https://github.com/bugcrowd/vrt-ruby/compare/v0.13.5...v0.13.6) - 2025-08-19

### Added
Expand Down
2 changes: 1 addition & 1 deletion lib/vrt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module VRT
'name' => 'Other',
'priority' => nil,
'type' => 'category' }.freeze
MAPPINGS = %i[cvss_v3 remediation_advice cwe].freeze
MAPPINGS = %i[cvss_v3 cvss_v4 remediation_advice cwe].freeze

@version_json = {}
@last_update = {}
Expand Down
78 changes: 78 additions & 0 deletions spec/sample_vrt/2.0/mappings/cvss_v4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"metadata": {
"default": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
"content": [
{
"id": "server_security_misconfiguration",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"children": [
{
"id": "unsafe_cross_origin_resource_sharing",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "ssl_attack_breach_poodle_etc",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "using_default_credentials",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "misconfigured_dns",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N"
}
]
},
{
"id": "server_side_injection",
"children": [
{
"id": "file_inclusion",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:L/SC:N/SI:N/SA:N"
},
{
"id": "remote_code_execution_rce",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"
},
{
"id": "sql_injection",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N"
}
]
},
{
"id": "unvalidated_redirects_and_forwards",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N",
"children": [
{
"id": "open_redirect",
"children": [
{
"id": "get_based",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N"
}
]
}
]
},
{
"id": "dumb_v2_category",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N"
},
{
"id": "insecure_data_storage",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "broken_authentication_and_session_management",
"children": [
{
"id": "authentication_bypass",
"cvss_v4": "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"
}
]
}
]
}
141 changes: 141 additions & 0 deletions spec/sample_vrt/999.999/mappings/cvss_v4/cvss_v4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
{
"metadata": {
"default": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
"content": [
{
"id": "server_security_misconfiguration",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"children": [
{
"id": "unsafe_cross_origin_resource_sharing",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "ssl_attack_breach_poodle_etc",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "using_default_credentials",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "misconfigured_dns",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N"
}
]
},
{
"id": "server_side_injection",
"children": [
{
"id": "file_inclusion",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:L/SC:N/SI:N/SA:N"
},
{
"id": "remote_code_execution_rce",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"
},
{
"id": "sql_injection",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N"
}
]
},
{
"id": "unvalidated_redirects_and_forwards",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N",
"children": [
{
"id": "open_redirect",
"children": [
{
"id": "get_based",
"cvss_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N"
}
]
}
]
},
{
"id": "broken_authentication_and_session_management",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "insecure_direct_object_references_idor",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "sensitive_data_exposure",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "cross_site_scripting_xss",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "missing_function_level_access_control",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "cross_site_request_forgery_csrf",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "application_level_denial_of_service_dos",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "external_behavior",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "insufficient_security_configurability",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "using_components_with_known_vulnerabilities",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "insecure_data_storage",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "lack_of_binary_hardening",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "insecure_data_transport",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "insecure_os_firmware",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "broken_cryptography",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "privacy_concerns",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "network_security_misconfiguration",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "mobile_security_misconfiguration",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "client_side_injection",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
},
{
"id": "vrt_category_only_in_test",
"cvss_v4": "CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N"
}
]
}
68 changes: 68 additions & 0 deletions spec/vrt/mappings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
it { is_expected.not_to be nil }
end

context 'when cvss_v4 mapping is nested under a subdirectory' do
let(:scheme) { :cvss_v4 }

it { is_expected.not_to be nil }
end

context 'when mapping is in flat directory' do
let(:scheme) { :cwe }

Expand Down Expand Up @@ -196,4 +202,66 @@
end
end
end

describe 'cvss_v4_mapping' do
let(:cvss_v4) { described_class.new(:cvss_v4) }

describe '#get' do
subject { cvss_v4.get(id_list, version) }

context 'when cvss_v4 on leaf node' do
let(:id_list) { %i[unvalidated_redirects_and_forwards open_redirect get_based] }
let(:version) { '999.999' }

it 'returns the full CVSS v4 vector string' do
is_expected.to eq('CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N')
end
end

context 'when cvss_v4 on internal node' do
let(:id_list) { %i[server_security_misconfiguration unsafe_cross_origin_resource_sharing] }
let(:version) { '999.999' }

it 'returns the most specific cvss_v4 value' do
is_expected.to eq('CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N')
end
end

context 'when cvss_v4 on root node with children' do
let(:id_list) { %i[server_security_misconfiguration] }
let(:version) { '999.999' }

it 'returns the category-level cvss_v4 value' do
is_expected.to eq('CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N')
end
end

context 'when cvss_v4 on root node without children' do
let(:id_list) { %i[insecure_data_storage] }
let(:version) { '999.999' }

it 'returns the node cvss_v4 value' do
is_expected.to eq('CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N')
end
end

context 'when other' do
let(:id_list) { %i[other] }
let(:version) { '999.999' }

it 'returns the default from metadata' do
is_expected.to eq('CVSS:4.0/AV:P/AC:H/AT:N/PR:H/UI:P/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N')
end
end

context 'when using version 2.0 flat mapping' do
let(:id_list) { %i[unvalidated_redirects_and_forwards open_redirect get_based] }
let(:version) { '2.0' }

it 'returns the cvss_v4 value for the leaf node' do
is_expected.to eq('CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N')
end
end
end
end
end
8 changes: 8 additions & 0 deletions spec/vrt/node_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@
end
end

context 'cvss_v4' do
it 'has the expected cvss_v4 full vector string' do
expect(mappings).to include(
cvss_v4: 'CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N'
)
end
end

context 'remediation_advice' do
let(:id) { 'server_security_misconfiguration.unsafe_cross_origin_resource_sharing' }

Expand Down