iodef 0.1.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.
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Iodef
4
+ class Validator
5
+ attr_reader :limits
6
+
7
+ def initialize(**limits)
8
+ @limits = Iodef.limits(limits)
9
+ end
10
+
11
+ def validate(value, type: "Document")
12
+ @violations = []
13
+ @ids = {}
14
+ @references = []
15
+ data = Iodef.copy(value, limits)
16
+ check_object(data, type, "")
17
+ if type == "Document"
18
+ @references.each do |id, path|
19
+ add(path, "reference", "reference has no matching identifier") unless @ids.key?(id)
20
+ end
21
+ end
22
+ @violations.freeze
23
+ rescue LimitError
24
+ raise
25
+ rescue ValidationError => e
26
+ [Violation.new(pointer: e.pointer, rule: "json", message: "value is not a finite UTF-8 JSON tree")].freeze
27
+ end
28
+
29
+ private
30
+
31
+ def add(path, rule, message)
32
+ return if @violations.size >= limits[:max_violations]
33
+
34
+ @violations << Violation.new(pointer: path.freeze, rule: rule.freeze, message: message.freeze)
35
+ end
36
+
37
+ def check_object(value, type, path)
38
+ return add(path, type, "expected an object") unless value.is_a?(Hash)
39
+
40
+ definition = SCHEMA.fetch(type)
41
+ definition.fetch("fields").each do |name, descriptor|
42
+ field_path = Iodef.pointer(path, name)
43
+ unless value.key?(name)
44
+ add(field_path, "required", "required property is absent") if descriptor.end_with?("!")
45
+ next
46
+ end
47
+ check_field(value[name], descriptor.delete_suffix("!"), field_path)
48
+ end
49
+ check_groups(value, definition, path)
50
+ check_extensions(value, definition, path)
51
+ check_semantics(value, type, path)
52
+ end
53
+
54
+ def check_field(value, type, path)
55
+ if type.end_with?("[]")
56
+ return add(path, type, "expected a nonempty array") unless value.is_a?(Array) && !value.empty?
57
+
58
+ value.each_with_index { |item, index| check_field(item, type.delete_suffix("[]"), Iodef.pointer(path, index)) }
59
+ elsif type == "mlstring"
60
+ unless value.is_a?(String)
61
+ if value.is_a?(Hash)
62
+ check_object(value, "MLString",
63
+ path)
64
+ else
65
+ add(path, type, "expected a string or multilingual string object")
66
+ end
67
+ end
68
+ elsif SCHEMA.key?(type)
69
+ check_object(value, type, path)
70
+ else
71
+ add(path, type, "invalid #{type} value") unless Types.valid?(type, value)
72
+ if type == "binary" && value.is_a?(String) && (value.bytesize / 4 * 3) - value.count("=") > limits[:max_binary_size]
73
+ raise LimitError.new("binary data exceeds max_binary_size", pointer: path)
74
+ end
75
+ end
76
+ end
77
+
78
+ def check_groups(value, definition, path)
79
+ { "at_least_one" => (1..), "exactly_one" => (1..1), "at_most_one" => (0..1) }.each do |rule, count|
80
+ fields = definition[rule]
81
+ next unless fields
82
+
83
+ add(path, rule, "invalid combination of #{fields.join(", ")}") unless count.cover?(fields.count { |field| value.key?(field) })
84
+ end
85
+ end
86
+
87
+ def check_extensions(value, definition, path)
88
+ definition.fetch("fields").each_key do |name|
89
+ next unless name.start_with?("ext-")
90
+
91
+ original = name.delete_prefix("ext-")
92
+ # SpecID uses "private" for its extension selector, unlike ordinary enumerations.
93
+ marker = original == "SpecID" ? "private" : "ext-value"
94
+ if value[original] == marker
95
+ unless value[name].is_a?(String) && !value[name].empty?
96
+ add(Iodef.pointer(path, name), "extension",
97
+ "extension selector requires its companion property")
98
+ end
99
+ elsif value.key?(name)
100
+ add(Iodef.pointer(path, name), "extension", "extension property requires the extension selector")
101
+ end
102
+ end
103
+ end
104
+
105
+ def check_semantics(value, type, path)
106
+ register_identifiers(value, type, path)
107
+ case type
108
+ when "Document"
109
+ require_field(value, "private-enum-name", path) if value.key?("private-enum-id")
110
+ when "SoftwareReference"
111
+ require_field(value, "dtype", path) if value["spec-name"] == "custom"
112
+ check_typed_content(value, path)
113
+ when "Extension" then check_typed_content(value, path)
114
+ when "Confidence"
115
+ if value["rating"] == "numeric"
116
+ require_field(value, "value", path)
117
+ elsif value.key?("value")
118
+ add(Iodef.pointer(path, "value"), "confidence", "value is only allowed for a numeric rating")
119
+ end
120
+ when "HistoryItem", "Expectation"
121
+ require_field(value, "DefinedCOA", path) if value["action"] == "defined-coa"
122
+ when "NameServers" then check_name_server_addresses(value, path)
123
+ when "Service" then check_service(value, path)
124
+ when "EmailData" then check_headers(value, "EmailHeaderField", path, string: true)
125
+ when "HashData" then check_hash_data(value, path)
126
+ when "BulkObservable"
127
+ if value["type"] == "file-hash" && !value["BulkObservableFormat"].is_a?(Hash)
128
+ require_field(value, "BulkObservableFormat", path)
129
+ elsif value["type"] == "file-hash" && !value["BulkObservableFormat"].key?("Hash")
130
+ add("#{path}/BulkObservableFormat/Hash", "required", "file hashes require a hash description")
131
+ end
132
+ when "IndicatorExpression"
133
+ operands = %w[IndicatorExpression Observable uid-ref IndicatorReference].sum { |key| value[key].is_a?(Array) ? value[key].size : 0 }
134
+ add(path, "operator", "negation requires exactly one operand") if value["operator"] == "not" && operands != 1
135
+ end
136
+ end
137
+
138
+ def register_identifiers(value, type, path)
139
+ fields = SCHEMA.fetch(type).fetch("fields").key?("observable-id") ? ["observable-id"] : []
140
+ fields << "id" if type == "IndicatorID"
141
+ fields.each do |field|
142
+ next unless value[field].is_a?(String)
143
+
144
+ location = Iodef.pointer(path, field)
145
+ add(location, "identifier", "identifier is already defined") if @ids.key?(value[field]) && field == "observable-id"
146
+ @ids[value[field]] = location
147
+ end
148
+ refs = value["uid-ref"] if SCHEMA.fetch(type).fetch("fields").key?("uid-ref")
149
+ (refs.is_a?(Array) ? refs : [refs]).each_with_index do |id, index|
150
+ @references << [id, refs.is_a?(Array) ? Iodef.pointer(Iodef.pointer(path, "uid-ref"), index) : Iodef.pointer(path, "uid-ref")] if id.is_a?(String)
151
+ end
152
+ end
153
+
154
+ def require_field(value, name, path)
155
+ add(Iodef.pointer(path, name), "required", "required property is absent") unless value.key?(name)
156
+ end
157
+
158
+ def check_name_server_addresses(value, path)
159
+ return unless value["Address"].is_a?(Array)
160
+
161
+ value["Address"].each_with_index do |address, index|
162
+ next unless address.is_a?(Hash)
163
+ next if %w[ipv4-addr ipv6-addr].include?(address["category"])
164
+
165
+ add("#{path}/Address/#{index}/category", "address", "name servers require an IP address")
166
+ end
167
+ end
168
+
169
+ def check_service(value, path)
170
+ require_field(value, "ip-protocol", path) if %w[Port Portlist ProtoCode ProtoType ProtoField].any? { |key| value.key?(key) }
171
+ { "Port" => 65_535, "ip-protocol" => 255, "ProtoCode" => 255, "ProtoType" => 255 }.each do |field, maximum|
172
+ item = value[field]
173
+ add(Iodef.pointer(path, field), "range", "protocol number is outside its wire range") if item.is_a?(Integer) && !item.between?(0, maximum)
174
+ end
175
+ check_headers(value, "ApplicationHeaderField", path)
176
+ end
177
+
178
+ def check_headers(value, field, path, string: false)
179
+ return unless value[field].is_a?(Array)
180
+
181
+ value[field].each_with_index do |header, index|
182
+ next unless header.is_a?(Hash)
183
+
184
+ header_path = "#{path}/#{field}/#{index}"
185
+ require_field(header, "name", header_path)
186
+ add("#{header_path}/dtype", "header", "email header data must use the string data type") if string && header["dtype"] != "string"
187
+ end
188
+ end
189
+
190
+ def check_hash_data(value, path)
191
+ require_field(value, "HashTargetID", path) if %w[file-pe-section file-pe-resource file-pdf-object].include?(value["scope"])
192
+ return unless value["scope"] == "file-pe-iat"
193
+
194
+ hashes = [*value["Hash"], *value["FuzzyHash"]]
195
+ return if hashes.any? { |item| item.is_a?(Hash) && item.key?("Application") }
196
+
197
+ add(path, "hash", "import table hashing requires the tool in Application")
198
+ end
199
+
200
+ def check_typed_content(value, path)
201
+ content = value["value"]
202
+ return unless content.is_a?(String)
203
+
204
+ valid = case value["dtype"]
205
+ when "byte", "file" then Types.binary?(content)
206
+ when "bytes", "frame", "packet", "ipv4-packet", "ipv6-packet" then content.match?(/\A(?:[0-9A-Fa-f]{2})*\z/)
207
+ when "boolean" then %w[true false 0 1].include?(content)
208
+ when "integer" then content.match?(/\A[+-]?\d+\z/)
209
+ when "real" then content.match?(/\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\z/)
210
+ when "date-time", "ntpstamp", "ntp-stamp" then Types.datetime?(content)
211
+ when "portlist" then Types.portlist?(content)
212
+ when "url" then Types.uri?(content)
213
+ when "character" then content.length == 1
214
+ when "json" then json_content?(content)
215
+ else true
216
+ end
217
+ add(Iodef.pointer(path, "value"), "dtype", "content does not match the declared data type") unless valid
218
+ binary_size = if %w[byte file].include?(value["dtype"])
219
+ (content.bytesize / 4 * 3) - content.count("=")
220
+ elsif %w[bytes frame packet ipv4-packet ipv6-packet].include?(value["dtype"])
221
+ content.bytesize / 2
222
+ end
223
+ raise LimitError.new("binary data exceeds max_binary_size", pointer: "#{path}/value") if binary_size && binary_size > limits[:max_binary_size]
224
+
225
+ return unless value["dtype"] == "byte" && valid && content.unpack1("m0").bytesize != 1
226
+
227
+ add("#{path}/value", "dtype", "byte content must encode exactly one octet")
228
+ end
229
+
230
+ def json_content?(content)
231
+ JSON.parse(content, allow_duplicate_key: false, max_nesting: limits[:max_depth])
232
+ true
233
+ rescue JSON::ParserError
234
+ false
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Iodef
4
+ VERSION = "0.1.0"
5
+ end
data/lib/iodef.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "yaml"
5
+ require "date"
6
+ require "uri"
7
+ require_relative "iodef/version"
8
+ require_relative "iodef/errors"
9
+ require_relative "iodef/types"
10
+ require_relative "iodef/validator"
11
+ require_relative "iodef/model"
12
+
13
+ IODEF = Iodef unless defined?(IODEF)
14
+
15
+ module Iodef
16
+ class << self
17
+ # Reads one JSON IODEF document; unknown properties survive round trips.
18
+ def parse(source, strict: true, validate: true, **limits)
19
+ options = self.limits(limits)
20
+ raise ParseError, "JSON input must be a String" unless source.is_a?(String)
21
+ raise LimitError, "input exceeds max_input_size" if source.bytesize > options[:max_input_size]
22
+
23
+ value = JSON.parse(source, allow_duplicate_key: false, max_nesting: options[:max_depth])
24
+ Document.new(value, strict: strict && validate, **options)
25
+ rescue JSON::NestingError
26
+ raise LimitError, "input exceeds max_depth"
27
+ rescue JSON::ParserError, EncodingError
28
+ raise ParseError, "invalid JSON input"
29
+ end
30
+
31
+ # Returns all bounded diagnostics, without embedding confidential input values.
32
+ def validate(source, **limits)
33
+ return parse(source, strict: false, **limits).warnings if source.is_a?(String)
34
+
35
+ Validator.new(**limits).validate(source.is_a?(Model) ? source.to_h : source)
36
+ rescue ParseError => e
37
+ [Violation.new(pointer: "", rule: "json", message: e.message)]
38
+ end
39
+ end
40
+ end
data/sig/iodef.rbs ADDED
@@ -0,0 +1,278 @@
1
+ module Iodef
2
+ VERSION: String
3
+ DEFAULT_LIMITS: Hash[Symbol, Integer]
4
+ SCHEMA: Hash[String, untyped]
5
+ ENUMS: Hash[String, Array[String]]
6
+ MAPKEYS: Hash[String, Integer]
7
+ KEYNAMES: Hash[Integer, String]
8
+
9
+ def self.parse: (String source, ?strict: bool, ?validate: bool, **Integer limits) -> Document
10
+ def self.parse_cbor: (String source, ?strict: bool, ?validate: bool, **Integer limits) -> Document
11
+ def self.validate: (untyped source, **Integer limits) -> Array[Violation]
12
+ def self.generate_json: (untyped value, ?untyped options) -> String
13
+ def self.pointer: (String parent, untyped key) -> String
14
+
15
+ class Violation
16
+ attr_reader pointer: String
17
+ attr_reader rule: String
18
+ attr_reader message: String
19
+ def self.new: (pointer: String, rule: String, message: String) -> instance
20
+ end
21
+ class Error < StandardError
22
+ end
23
+ class ParseError < Error
24
+ end
25
+ class ConversionError < Error
26
+ end
27
+ class ValidationError < Error
28
+ attr_reader pointer: String
29
+ attr_reader violations: Array[Violation]
30
+ def initialize: (?String message, ?pointer: String, ?violations: Array[Violation]) -> void
31
+ end
32
+ class LimitError < ValidationError
33
+ end
34
+ class Validator
35
+ attr_reader limits: Hash[Symbol, Integer]
36
+ def initialize: (**Integer limits) -> void
37
+ def validate: (untyped value, ?type: String) -> Array[Violation]
38
+ end
39
+ class Model
40
+ attr_reader limits: Hash[Symbol, Integer]
41
+ attr_reader warnings: Array[Violation]
42
+ def initialize: (?Hash[untyped, untyped] data, ?strict: bool, **Integer limits) -> void
43
+ def []: (String | Symbol key) -> untyped
44
+ def []=: (String | Symbol key, untyped value) -> untyped
45
+ def delete: (String | Symbol key) -> untyped
46
+ def to_h: () -> Hash[String, untyped]
47
+ def to_json: (?untyped options) -> String
48
+ def validate!: () -> self
49
+ def description: () -> MultilingualText
50
+ def description=: (Array[untyped] value) -> untyped
51
+ end
52
+ class MultilingualText
53
+ include Enumerable[untyped]
54
+ def initialize: (untyped values, ?lang: String?) -> void
55
+ def each: () { (untyped) -> untyped } -> Array[untyped]
56
+ | () -> Enumerator[untyped, Array[untyped]]
57
+ def text: (?String? language) -> String?
58
+ def to_a: () -> Array[untyped]
59
+ end
60
+ class Document < Model
61
+ def self.build: (?lang: String?, **Integer limits) { (Document) -> untyped } -> Document
62
+ def incidents: () -> Array[Incident]
63
+ def add_incident: (purpose: String, **untyped attributes) ?{ (Incident) -> untyped } -> Incident
64
+ def to_cbor: () -> String
65
+ end
66
+ class Incident < Model
67
+ def contacts: () -> Array[Contact]
68
+ def assessments: () -> Array[Assessment]
69
+ def indicators: () -> Array[Indicator]
70
+ def add_contact: (role: String, type: String, **untyped attributes) ?{ (Contact) -> untyped } -> Contact
71
+ end
72
+ class Contact < Model
73
+ end
74
+ class Assessment < Model
75
+ end
76
+ class EventData < Model
77
+ end
78
+ class Indicator < Model
79
+ end
80
+ end
81
+ module IODEF = Iodef
82
+
83
+ module Iodef
84
+ class Document < Model
85
+ def additional_data: () -> (Array[Hash[String, untyped]])?
86
+ def additional_data=: (untyped value) -> untyped
87
+ def version: () -> (String)?
88
+ def version=: (untyped value) -> untyped
89
+ def lang: () -> (String)?
90
+ def lang=: (untyped value) -> untyped
91
+ def format_id: () -> (String)?
92
+ def format_id=: (untyped value) -> untyped
93
+ def private_enum_name: () -> (String)?
94
+ def private_enum_name=: (untyped value) -> untyped
95
+ def private_enum_id: () -> (String)?
96
+ def private_enum_id=: (untyped value) -> untyped
97
+ def incident: () -> (Array[Incident])?
98
+ def incident=: (untyped value) -> untyped
99
+ end
100
+ class Incident < Model
101
+ def restriction: () -> (String)?
102
+ def restriction=: (untyped value) -> untyped
103
+ def ext_restriction: () -> (String)?
104
+ def ext_restriction=: (untyped value) -> untyped
105
+ def observable_id: () -> (String)?
106
+ def observable_id=: (untyped value) -> untyped
107
+ def additional_data: () -> (Array[Hash[String, untyped]])?
108
+ def additional_data=: (untyped value) -> untyped
109
+ def detect_time: () -> (String)?
110
+ def detect_time=: (untyped value) -> untyped
111
+ def start_time: () -> (String)?
112
+ def start_time=: (untyped value) -> untyped
113
+ def end_time: () -> (String)?
114
+ def end_time=: (untyped value) -> untyped
115
+ def recovery_time: () -> (String)?
116
+ def recovery_time=: (untyped value) -> untyped
117
+ def report_time: () -> (String)?
118
+ def report_time=: (untyped value) -> untyped
119
+ def purpose: () -> (String)?
120
+ def purpose=: (untyped value) -> untyped
121
+ def ext_purpose: () -> (String)?
122
+ def ext_purpose=: (untyped value) -> untyped
123
+ def status: () -> (String)?
124
+ def status=: (untyped value) -> untyped
125
+ def ext_status: () -> (String)?
126
+ def ext_status=: (untyped value) -> untyped
127
+ def lang: () -> (String)?
128
+ def lang=: (untyped value) -> untyped
129
+ def incident_id: () -> (Hash[String, untyped])?
130
+ def incident_id=: (untyped value) -> untyped
131
+ def alternative_id: () -> (Hash[String, untyped])?
132
+ def alternative_id=: (untyped value) -> untyped
133
+ def related_activity: () -> (Array[Hash[String, untyped]])?
134
+ def related_activity=: (untyped value) -> untyped
135
+ def generation_time: () -> (String)?
136
+ def generation_time=: (untyped value) -> untyped
137
+ def discovery: () -> (Array[Hash[String, untyped]])?
138
+ def discovery=: (untyped value) -> untyped
139
+ def assessment: () -> (Array[Assessment])?
140
+ def assessment=: (untyped value) -> untyped
141
+ def method: () -> (Array[Hash[String, untyped]])?
142
+ def method=: (untyped value) -> untyped
143
+ def contact: () -> (Array[Contact])?
144
+ def contact=: (untyped value) -> untyped
145
+ def event_data: () -> (Array[EventData])?
146
+ def event_data=: (untyped value) -> untyped
147
+ def indicator: () -> (Array[Indicator])?
148
+ def indicator=: (untyped value) -> untyped
149
+ def history: () -> (Hash[String, untyped])?
150
+ def history=: (untyped value) -> untyped
151
+ end
152
+ class Contact < Model
153
+ def restriction: () -> (String)?
154
+ def restriction=: (untyped value) -> untyped
155
+ def ext_restriction: () -> (String)?
156
+ def ext_restriction=: (untyped value) -> untyped
157
+ def additional_data: () -> (Array[Hash[String, untyped]])?
158
+ def additional_data=: (untyped value) -> untyped
159
+ def role: () -> (String)?
160
+ def role=: (untyped value) -> untyped
161
+ def ext_role: () -> (String)?
162
+ def ext_role=: (untyped value) -> untyped
163
+ def type: () -> (String)?
164
+ def type=: (untyped value) -> untyped
165
+ def ext_type: () -> (String)?
166
+ def ext_type=: (untyped value) -> untyped
167
+ def contact_name: () -> (Array[String | Hash[String, untyped]])?
168
+ def contact_name=: (untyped value) -> untyped
169
+ def contact_title: () -> (Array[String | Hash[String, untyped]])?
170
+ def contact_title=: (untyped value) -> untyped
171
+ def registry_handle: () -> (Array[Hash[String, untyped]])?
172
+ def registry_handle=: (untyped value) -> untyped
173
+ def postal_address: () -> (Array[Hash[String, untyped]])?
174
+ def postal_address=: (untyped value) -> untyped
175
+ def email: () -> (Array[Hash[String, untyped]])?
176
+ def email=: (untyped value) -> untyped
177
+ def telephone: () -> (Array[Hash[String, untyped]])?
178
+ def telephone=: (untyped value) -> untyped
179
+ def timezone: () -> (String)?
180
+ def timezone=: (untyped value) -> untyped
181
+ def contact: () -> (Array[Contact])?
182
+ def contact=: (untyped value) -> untyped
183
+ end
184
+ class Assessment < Model
185
+ def restriction: () -> (String)?
186
+ def restriction=: (untyped value) -> untyped
187
+ def ext_restriction: () -> (String)?
188
+ def ext_restriction=: (untyped value) -> untyped
189
+ def observable_id: () -> (String)?
190
+ def observable_id=: (untyped value) -> untyped
191
+ def additional_data: () -> (Array[Hash[String, untyped]])?
192
+ def additional_data=: (untyped value) -> untyped
193
+ def occurrence: () -> (String)?
194
+ def occurrence=: (untyped value) -> untyped
195
+ def incident_category: () -> (Array[String | Hash[String, untyped]])?
196
+ def incident_category=: (untyped value) -> untyped
197
+ def impact: () -> (Array[Hash[String, untyped]])?
198
+ def impact=: (untyped value) -> untyped
199
+ def counter: () -> (Array[Hash[String, untyped]])?
200
+ def counter=: (untyped value) -> untyped
201
+ def mitigating_factor: () -> (Array[String | Hash[String, untyped]])?
202
+ def mitigating_factor=: (untyped value) -> untyped
203
+ def cause: () -> (Array[String | Hash[String, untyped]])?
204
+ def cause=: (untyped value) -> untyped
205
+ def confidence: () -> (Hash[String, untyped])?
206
+ def confidence=: (untyped value) -> untyped
207
+ end
208
+ class EventData < Model
209
+ def restriction: () -> (String)?
210
+ def restriction=: (untyped value) -> untyped
211
+ def ext_restriction: () -> (String)?
212
+ def ext_restriction=: (untyped value) -> untyped
213
+ def observable_id: () -> (String)?
214
+ def observable_id=: (untyped value) -> untyped
215
+ def additional_data: () -> (Array[Hash[String, untyped]])?
216
+ def additional_data=: (untyped value) -> untyped
217
+ def detect_time: () -> (String)?
218
+ def detect_time=: (untyped value) -> untyped
219
+ def start_time: () -> (String)?
220
+ def start_time=: (untyped value) -> untyped
221
+ def end_time: () -> (String)?
222
+ def end_time=: (untyped value) -> untyped
223
+ def recovery_time: () -> (String)?
224
+ def recovery_time=: (untyped value) -> untyped
225
+ def report_time: () -> (String)?
226
+ def report_time=: (untyped value) -> untyped
227
+ def contact: () -> (Array[Contact])?
228
+ def contact=: (untyped value) -> untyped
229
+ def discovery: () -> (Array[Hash[String, untyped]])?
230
+ def discovery=: (untyped value) -> untyped
231
+ def assessment: () -> (Assessment)?
232
+ def assessment=: (untyped value) -> untyped
233
+ def method: () -> (Array[Hash[String, untyped]])?
234
+ def method=: (untyped value) -> untyped
235
+ def system: () -> (Array[Hash[String, untyped]])?
236
+ def system=: (untyped value) -> untyped
237
+ def expectation: () -> (Array[Hash[String, untyped]])?
238
+ def expectation=: (untyped value) -> untyped
239
+ def record_data: () -> (Array[Hash[String, untyped]])?
240
+ def record_data=: (untyped value) -> untyped
241
+ def event_data: () -> (Array[EventData])?
242
+ def event_data=: (untyped value) -> untyped
243
+ end
244
+ class Indicator < Model
245
+ def restriction: () -> (String)?
246
+ def restriction=: (untyped value) -> untyped
247
+ def ext_restriction: () -> (String)?
248
+ def ext_restriction=: (untyped value) -> untyped
249
+ def additional_data: () -> (Array[Hash[String, untyped]])?
250
+ def additional_data=: (untyped value) -> untyped
251
+ def indicator_id: () -> (Hash[String, untyped])?
252
+ def indicator_id=: (untyped value) -> untyped
253
+ def alternative_indicator_id: () -> (Array[Hash[String, untyped]])?
254
+ def alternative_indicator_id=: (untyped value) -> untyped
255
+ def start_time: () -> (String)?
256
+ def start_time=: (untyped value) -> untyped
257
+ def end_time: () -> (String)?
258
+ def end_time=: (untyped value) -> untyped
259
+ def confidence: () -> (Hash[String, untyped])?
260
+ def confidence=: (untyped value) -> untyped
261
+ def contact: () -> (Array[Contact])?
262
+ def contact=: (untyped value) -> untyped
263
+ def observable: () -> (Hash[String, untyped])?
264
+ def observable=: (untyped value) -> untyped
265
+ def uid_ref: () -> (String)?
266
+ def uid_ref=: (untyped value) -> untyped
267
+ def indicator_expression: () -> (Hash[String, untyped])?
268
+ def indicator_expression=: (untyped value) -> untyped
269
+ def indicator_reference: () -> (Hash[String, untyped])?
270
+ def indicator_reference=: (untyped value) -> untyped
271
+ def node_role: () -> (Array[Hash[String, untyped]])?
272
+ def node_role=: (untyped value) -> untyped
273
+ def attack_phase: () -> (Array[Hash[String, untyped]])?
274
+ def attack_phase=: (untyped value) -> untyped
275
+ def reference: () -> (Array[Hash[String, untyped]])?
276
+ def reference=: (untyped value) -> untyped
277
+ end
278
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iodef
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
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: json
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 2.19.4
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '4'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 2.19.4
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '4'
32
+ description: RFC 8727 JSON incident reports, typed IODEF v2 models, validation, and
33
+ optional CBOR conversion.
34
+ email:
35
+ - t.yudai92@gmail.com
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - CHANGELOG.md
41
+ - LICENSE.txt
42
+ - README.md
43
+ - SECURITY.md
44
+ - data/enums.yml
45
+ - data/mapkeys.yml
46
+ - data/types.yml
47
+ - lib/iodef.rb
48
+ - lib/iodef/cbor.rb
49
+ - lib/iodef/cbor_guard.rb
50
+ - lib/iodef/errors.rb
51
+ - lib/iodef/model.rb
52
+ - lib/iodef/types.rb
53
+ - lib/iodef/validator.rb
54
+ - lib/iodef/version.rb
55
+ - sig/iodef.rbs
56
+ homepage: https://github.com/ydah/iodef
57
+ licenses:
58
+ - MIT
59
+ metadata:
60
+ allowed_push_host: https://rubygems.org
61
+ source_code_uri: https://github.com/ydah/iodef
62
+ changelog_uri: https://github.com/ydah/iodef/blob/main/CHANGELOG.md
63
+ rubygems_mfa_required: 'true'
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.3.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 4.0.16
79
+ specification_version: 4
80
+ summary: Read, validate, and build IODEF incident reports
81
+ test_files: []