Skip to content

Commit 81d252b

Browse files
committed
Add json schema and validate yml files
1 parent 627e45a commit 81d252b

4 files changed

Lines changed: 344 additions & 3 deletions

File tree

Gemfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
source 'https://rubygems.org'
22

33
gem 'rake'
4-
gem 'faraday', '~> 2.0'
5-
gem 'kwalify', '~> 0.1'
6-
gem 'rspec', '~> 3.0'
4+
gem 'faraday', '~> 2.0'
5+
gem 'kwalify', '~> 0.1'
6+
gem 'json_schemer', '~> 2.0'
7+
gem 'rspec', '~> 3.0'
78

89
group :development do
910
gem 'pry'

spec/schema_validation_spec.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
require 'spec_helper'
2+
3+
require 'json_schemer'
4+
require 'yaml'
5+
6+
SCHEMAS_DIR = File.join(ROOT, 'spec', 'schemas')
7+
8+
def schemer_for(schema_path)
9+
JSONSchemer.schema(
10+
JSON.parse(File.read(schema_path)),
11+
meta_schema: 'https://json-schema.org/draft/2020-12/schema'
12+
)
13+
end
14+
15+
def normalize_for_json(value)
16+
case value
17+
when Hash
18+
value.transform_values { |v| normalize_for_json(v) }
19+
when Array
20+
value.map { |v| normalize_for_json(v) }
21+
when Date
22+
value.iso8601
23+
else
24+
value
25+
end
26+
end
27+
28+
def format_errors(errors)
29+
errors.map do |e|
30+
pointer = e['data_pointer'].to_s.empty? ? '<root>' : e['data_pointer']
31+
32+
"↳ #{pointer}: #{e['error']}"
33+
end.join("\n")
34+
end
35+
36+
GEM_SCHEMER = schemer_for(File.join(SCHEMAS_DIR, 'gem.json'))
37+
RUBY_SCHEMER = schemer_for(File.join(SCHEMAS_DIR, 'ruby.json'))
38+
39+
shared_examples 'conforming schema' do |glob:, schemer:|
40+
Dir.glob(File.join(ROOT, glob)).sort.each do |path|
41+
filename = path.split('/')[-2..].join('/')
42+
43+
it "#{filename} conforms to schema" do
44+
data = normalize_for_json(YAML.safe_load_file(path, permitted_classes: [Date]))
45+
errors = schemer.validate(data).to_a
46+
47+
expect(errors).to be_empty, lambda {
48+
"#{filename}\n#{format_errors(errors)}"
49+
}
50+
end
51+
end
52+
end
53+
54+
describe 'JSON Schema validation' do
55+
describe 'for gems' do
56+
include_examples 'conforming schema',
57+
glob: 'gems/*/*.yml',
58+
schemer: GEM_SCHEMER
59+
end
60+
61+
describe 'for rubies' do
62+
include_examples 'conforming schema',
63+
glob: 'rubies/*/*.yml',
64+
schemer: RUBY_SCHEMER
65+
end
66+
end

spec/schemas/gem.json

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://github.com/rubysec/ruby-advisory-db/schemas/gem.json",
4+
"title": "Ruby gem advisory",
5+
"type": "object",
6+
"additionalProperties": false,
7+
"required": ["gem", "url", "title", "date", "description"],
8+
"anyOf": [
9+
{ "required": ["cve"] },
10+
{ "required": ["osvdb"] },
11+
{ "required": ["ghsa"] }
12+
],
13+
"properties": {
14+
"gem": {
15+
"type": "string",
16+
"minLength": 1
17+
},
18+
"library": {
19+
"type": "string",
20+
"minLength": 1
21+
},
22+
"framework": {
23+
"type": "string",
24+
"minLength": 1
25+
},
26+
"platform": {
27+
"type": "string",
28+
"minLength": 1
29+
},
30+
"cve": {
31+
"type": "string",
32+
"pattern": "^\\d{4}-\\d+$"
33+
},
34+
"osvdb": {
35+
"type": "integer",
36+
"minimum": 1
37+
},
38+
"ghsa": {
39+
"type": "string",
40+
"pattern": "^[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$"
41+
},
42+
"url": {
43+
"type": "string",
44+
"pattern": "^https?://",
45+
"not": { "pattern": "^https?://osvdb\\.org" }
46+
},
47+
"title": {
48+
"type": "string",
49+
"minLength": 1,
50+
"pattern": "^(?:\\S|\\S.*\\S)$"
51+
},
52+
"date": {
53+
"type": "string",
54+
"format": "date",
55+
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
56+
},
57+
"description": {
58+
"type": "string",
59+
"minLength": 1,
60+
"allOf": [
61+
{ "pattern": "\\n" },
62+
{ "not": { "pattern": "\\\\n\\\\n" } },
63+
{ "not": { "pattern": "(#+) PoC" } }
64+
]
65+
},
66+
"cvss_v2": {
67+
"type": "number",
68+
"minimum": 0.0,
69+
"maximum": 10.0
70+
},
71+
"cvss_v3": {
72+
"type": "number",
73+
"minimum": 0.0,
74+
"maximum": 10.0
75+
},
76+
"cvss_v4": {
77+
"type": "number",
78+
"minimum": 0.0,
79+
"maximum": 10.0
80+
},
81+
"unaffected_versions": {
82+
"type": "array",
83+
"minItems": 1,
84+
"items": {
85+
"type": "string",
86+
"pattern": "^(?:<=|<|>=|>|~>|=) [0-9A-Za-z.\\-]+(?:, (?:<=|<|>=|>|~>|=) [0-9A-Za-z.\\-]+)?$"
87+
}
88+
},
89+
"patched_versions": {
90+
"type": "array",
91+
"minItems": 1,
92+
"items": {
93+
"type": "string",
94+
"pattern": "^(?:<=|<|>=|>|~>|=) [0-9A-Za-z.\\-]+(?:, (?:<=|<|>=|>|~>|=) [0-9A-Za-z.\\-]+)?$"
95+
}
96+
},
97+
"related": {
98+
"type": "object",
99+
"additionalProperties": false,
100+
"minProperties": 1,
101+
"properties": {
102+
"cve": {
103+
"type": "array",
104+
"minItems": 1,
105+
"items": {
106+
"type": "string",
107+
"pattern": "^\\d{4}-\\d+$"
108+
}
109+
},
110+
"ghsa": {
111+
"type": "array",
112+
"minItems": 1,
113+
"items": {
114+
"type": "string",
115+
"pattern": "^[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$"
116+
}
117+
},
118+
"osvdb": {
119+
"type": "array",
120+
"minItems": 1,
121+
"items": {
122+
"type": "integer",
123+
"minimum": 1
124+
}
125+
},
126+
"url": {
127+
"type": "array",
128+
"minItems": 1,
129+
"items": {
130+
"type": "string",
131+
"pattern": "^https?://"
132+
}
133+
}
134+
}
135+
},
136+
"notes": {
137+
"type": "string",
138+
"minLength": 1
139+
}
140+
}
141+
}

spec/schemas/ruby.json

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://github.com/rubysec/ruby-advisory-db/schemas/ruby.json",
4+
"title": "Ruby implementation advisory",
5+
"type": "object",
6+
"additionalProperties": false,
7+
"required": ["engine", "url", "title", "date", "description"],
8+
"anyOf": [
9+
{ "required": ["cve"] },
10+
{ "required": ["osvdb"] },
11+
{ "required": ["ghsa"] }
12+
],
13+
"properties": {
14+
"engine": {
15+
"type": "string",
16+
"enum": ["ruby", "jruby", "mruby", "mrubyc", "rbx", "truffleruby"]
17+
},
18+
"platform": {
19+
"type": "string",
20+
"minLength": 1
21+
},
22+
"cve": {
23+
"type": "string",
24+
"pattern": "^\\d{4}-\\d+$"
25+
},
26+
"osvdb": {
27+
"type": "integer",
28+
"minimum": 1
29+
},
30+
"ghsa": {
31+
"type": "string",
32+
"pattern": "^[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$"
33+
},
34+
"url": {
35+
"type": "string",
36+
"pattern": "^https?://",
37+
"not": { "pattern": "^https?://osvdb\\.org" }
38+
},
39+
"title": {
40+
"type": "string",
41+
"minLength": 1,
42+
"pattern": "^(?:\\S|\\S.*\\S)$"
43+
},
44+
"date": {
45+
"type": "string",
46+
"format": "date",
47+
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
48+
},
49+
"description": {
50+
"type": "string",
51+
"minLength": 1,
52+
"allOf": [
53+
{ "pattern": "\\n" },
54+
{ "not": { "pattern": "\\\\n\\\\n" } },
55+
{ "not": { "pattern": "(#+) PoC" } }
56+
]
57+
},
58+
"cvss_v2": {
59+
"type": "number",
60+
"minimum": 0.0,
61+
"maximum": 10.0
62+
},
63+
"cvss_v3": {
64+
"type": "number",
65+
"minimum": 0.0,
66+
"maximum": 10.0
67+
},
68+
"cvss_v4": {
69+
"type": "number",
70+
"minimum": 0.0,
71+
"maximum": 10.0
72+
},
73+
"unaffected_versions": {
74+
"type": "array",
75+
"minItems": 1,
76+
"items": {
77+
"type": "string",
78+
"pattern": "^(?:<=|<|>=|>|~>|=) [0-9A-Za-z.\\-]+(?:, (?:<=|<|>=|>|~>|=) [0-9A-Za-z.\\-]+)?$"
79+
}
80+
},
81+
"patched_versions": {
82+
"type": "array",
83+
"minItems": 1,
84+
"items": {
85+
"type": "string",
86+
"pattern": "^(?:<=|<|>=|>|~>|=) [0-9A-Za-z.\\-]+(?:, (?:<=|<|>=|>|~>|=) [0-9A-Za-z.\\-]+)?$"
87+
}
88+
},
89+
"related": {
90+
"type": "object",
91+
"additionalProperties": false,
92+
"minProperties": 1,
93+
"properties": {
94+
"cve": {
95+
"type": "array",
96+
"minItems": 1,
97+
"items": {
98+
"type": "string",
99+
"pattern": "^\\d{4}-\\d+$"
100+
}
101+
},
102+
"ghsa": {
103+
"type": "array",
104+
"minItems": 1,
105+
"items": {
106+
"type": "string",
107+
"pattern": "^[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}$"
108+
}
109+
},
110+
"osvdb": {
111+
"type": "array",
112+
"minItems": 1,
113+
"items": {
114+
"type": "integer",
115+
"minimum": 1
116+
}
117+
},
118+
"url": {
119+
"type": "array",
120+
"minItems": 1,
121+
"items": {
122+
"type": "string",
123+
"pattern": "^https?://"
124+
}
125+
}
126+
}
127+
},
128+
"notes": {
129+
"type": "string",
130+
"minLength": 1
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)