mihari 0.17.4 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +155 -0
- data/.travis.yml +7 -1
- data/Gemfile +2 -0
- data/README.md +41 -72
- data/config/pre_commit.yml +3 -0
- data/docker/Dockerfile +1 -1
- data/lib/mihari.rb +12 -8
- data/lib/mihari/alert_viewer.rb +16 -34
- data/lib/mihari/analyzers/base.rb +7 -19
- data/lib/mihari/analyzers/basic.rb +3 -1
- data/lib/mihari/analyzers/binaryedge.rb +3 -3
- data/lib/mihari/analyzers/censys.rb +2 -2
- data/lib/mihari/analyzers/circl.rb +2 -2
- data/lib/mihari/analyzers/onyphe.rb +3 -3
- data/lib/mihari/analyzers/passivetotal.rb +2 -2
- data/lib/mihari/analyzers/pulsedive.rb +2 -2
- data/lib/mihari/analyzers/securitytrails.rb +2 -2
- data/lib/mihari/analyzers/securitytrails_domain_feed.rb +2 -2
- data/lib/mihari/analyzers/shodan.rb +2 -2
- data/lib/mihari/analyzers/virustotal.rb +2 -2
- data/lib/mihari/analyzers/zoomeye.rb +2 -2
- data/lib/mihari/cli.rb +13 -4
- data/lib/mihari/config.rb +68 -2
- data/lib/mihari/configurable.rb +1 -1
- data/lib/mihari/database.rb +68 -0
- data/lib/mihari/emitters/base.rb +1 -1
- data/lib/mihari/emitters/database.rb +29 -0
- data/lib/mihari/emitters/misp.rb +8 -1
- data/lib/mihari/emitters/slack.rb +4 -2
- data/lib/mihari/emitters/stdout.rb +2 -1
- data/lib/mihari/emitters/the_hive.rb +28 -14
- data/lib/mihari/models/alert.rb +11 -0
- data/lib/mihari/models/artifact.rb +27 -0
- data/lib/mihari/models/tag.rb +10 -0
- data/lib/mihari/models/tagging.rb +10 -0
- data/lib/mihari/notifiers/slack.rb +7 -4
- data/lib/mihari/serializers/alert.rb +12 -0
- data/lib/mihari/serializers/artifact.rb +9 -0
- data/lib/mihari/serializers/tag.rb +9 -0
- data/lib/mihari/slack_monkeypatch.rb +16 -0
- data/lib/mihari/status.rb +1 -1
- data/lib/mihari/type_checker.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/mihari.gemspec +13 -6
- metadata +140 -36
- data/lib/mihari/artifact.rb +0 -36
- data/lib/mihari/cache.rb +0 -35
- data/lib/mihari/the_hive.rb +0 -42
- data/lib/mihari/the_hive/alert.rb +0 -25
- data/lib/mihari/the_hive/artifact.rb +0 -33
- data/lib/mihari/the_hive/base.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c43df888d661331830b74ecbf097f2bf7f0450850fca0ad8bfe86d58fe5d32f
|
4
|
+
data.tar.gz: 123d3a13867550f57557472e63819b1bc8a70fd80bb0ef3fa0543d9414fc84a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96693d5a7ca81b7a1f6834ffedb7ad9897dba6fe510dc632c204a2497595ae67c6bf7c70895bccc5738d6d83369cafc7f309962e6e29a941a2ccb5f5fc84b68a
|
7
|
+
data.tar.gz: 9b4d2ccb878b2aec9b82ac158b5da5bea76653b443c3d53c5d4146a4e3900f400680d690cb0ab6becb526f827d1cb482d663719625f10ee6011e598411c29b67
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Relaxed.Ruby.Style
|
2
|
+
## Version 2.5
|
3
|
+
|
4
|
+
require:
|
5
|
+
- rubocop-performance
|
6
|
+
|
7
|
+
Style/Alias:
|
8
|
+
Enabled: false
|
9
|
+
StyleGuide: https://relaxed.ruby.style/#stylealias
|
10
|
+
|
11
|
+
Style/AsciiComments:
|
12
|
+
Enabled: false
|
13
|
+
StyleGuide: https://relaxed.ruby.style/#styleasciicomments
|
14
|
+
|
15
|
+
Style/BeginBlock:
|
16
|
+
Enabled: false
|
17
|
+
StyleGuide: https://relaxed.ruby.style/#stylebeginblock
|
18
|
+
|
19
|
+
Style/BlockDelimiters:
|
20
|
+
Enabled: false
|
21
|
+
StyleGuide: https://relaxed.ruby.style/#styleblockdelimiters
|
22
|
+
|
23
|
+
Style/CommentAnnotation:
|
24
|
+
Enabled: false
|
25
|
+
StyleGuide: https://relaxed.ruby.style/#stylecommentannotation
|
26
|
+
|
27
|
+
Style/Documentation:
|
28
|
+
Enabled: false
|
29
|
+
StyleGuide: https://relaxed.ruby.style/#styledocumentation
|
30
|
+
|
31
|
+
Layout/DotPosition:
|
32
|
+
Enabled: false
|
33
|
+
StyleGuide: https://relaxed.ruby.style/#layoutdotposition
|
34
|
+
|
35
|
+
Style/DoubleNegation:
|
36
|
+
Enabled: false
|
37
|
+
StyleGuide: https://relaxed.ruby.style/#styledoublenegation
|
38
|
+
|
39
|
+
Style/EndBlock:
|
40
|
+
Enabled: false
|
41
|
+
StyleGuide: https://relaxed.ruby.style/#styleendblock
|
42
|
+
|
43
|
+
Style/FormatString:
|
44
|
+
Enabled: false
|
45
|
+
StyleGuide: https://relaxed.ruby.style/#styleformatstring
|
46
|
+
|
47
|
+
Style/IfUnlessModifier:
|
48
|
+
Enabled: false
|
49
|
+
StyleGuide: https://relaxed.ruby.style/#styleifunlessmodifier
|
50
|
+
|
51
|
+
Style/Lambda:
|
52
|
+
Enabled: false
|
53
|
+
StyleGuide: https://relaxed.ruby.style/#stylelambda
|
54
|
+
|
55
|
+
Style/ModuleFunction:
|
56
|
+
Enabled: false
|
57
|
+
StyleGuide: https://relaxed.ruby.style/#stylemodulefunction
|
58
|
+
|
59
|
+
Style/MultilineBlockChain:
|
60
|
+
Enabled: false
|
61
|
+
StyleGuide: https://relaxed.ruby.style/#stylemultilineblockchain
|
62
|
+
|
63
|
+
Style/NegatedIf:
|
64
|
+
Enabled: false
|
65
|
+
StyleGuide: https://relaxed.ruby.style/#stylenegatedif
|
66
|
+
|
67
|
+
Style/NegatedWhile:
|
68
|
+
Enabled: false
|
69
|
+
StyleGuide: https://relaxed.ruby.style/#stylenegatedwhile
|
70
|
+
|
71
|
+
Style/NumericPredicate:
|
72
|
+
Enabled: false
|
73
|
+
StyleGuide: https://relaxed.ruby.style/#stylenumericpredicate
|
74
|
+
|
75
|
+
Style/ParallelAssignment:
|
76
|
+
Enabled: false
|
77
|
+
StyleGuide: https://relaxed.ruby.style/#styleparallelassignment
|
78
|
+
|
79
|
+
Style/PercentLiteralDelimiters:
|
80
|
+
Enabled: false
|
81
|
+
StyleGuide: https://relaxed.ruby.style/#stylepercentliteraldelimiters
|
82
|
+
|
83
|
+
Style/PerlBackrefs:
|
84
|
+
Enabled: false
|
85
|
+
StyleGuide: https://relaxed.ruby.style/#styleperlbackrefs
|
86
|
+
|
87
|
+
Style/Semicolon:
|
88
|
+
Enabled: false
|
89
|
+
StyleGuide: https://relaxed.ruby.style/#stylesemicolon
|
90
|
+
|
91
|
+
Style/SignalException:
|
92
|
+
Enabled: false
|
93
|
+
StyleGuide: https://relaxed.ruby.style/#stylesignalexception
|
94
|
+
|
95
|
+
Style/SingleLineBlockParams:
|
96
|
+
Enabled: false
|
97
|
+
StyleGuide: https://relaxed.ruby.style/#stylesinglelineblockparams
|
98
|
+
|
99
|
+
Style/SingleLineMethods:
|
100
|
+
Enabled: false
|
101
|
+
StyleGuide: https://relaxed.ruby.style/#stylesinglelinemethods
|
102
|
+
|
103
|
+
Layout/SpaceBeforeBlockBraces:
|
104
|
+
Enabled: false
|
105
|
+
StyleGuide: https://relaxed.ruby.style/#layoutspacebeforeblockbraces
|
106
|
+
|
107
|
+
Layout/SpaceInsideParens:
|
108
|
+
Enabled: false
|
109
|
+
StyleGuide: https://relaxed.ruby.style/#layoutspaceinsideparens
|
110
|
+
|
111
|
+
Style/SpecialGlobalVars:
|
112
|
+
Enabled: false
|
113
|
+
StyleGuide: https://relaxed.ruby.style/#stylespecialglobalvars
|
114
|
+
|
115
|
+
Style/StringLiterals:
|
116
|
+
Enabled: false
|
117
|
+
StyleGuide: https://relaxed.ruby.style/#stylestringliterals
|
118
|
+
|
119
|
+
Style/TrailingCommaInArguments:
|
120
|
+
Enabled: false
|
121
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarguments
|
122
|
+
|
123
|
+
Style/TrailingCommaInArrayLiteral:
|
124
|
+
Enabled: false
|
125
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainarrayliteral
|
126
|
+
|
127
|
+
Style/TrailingCommaInHashLiteral:
|
128
|
+
Enabled: false
|
129
|
+
StyleGuide: https://relaxed.ruby.style/#styletrailingcommainhashliteral
|
130
|
+
|
131
|
+
Style/SymbolArray:
|
132
|
+
Enabled: false
|
133
|
+
StyleGuide: http://relaxed.ruby.style/#stylesymbolarray
|
134
|
+
|
135
|
+
Style/WhileUntilModifier:
|
136
|
+
Enabled: false
|
137
|
+
StyleGuide: https://relaxed.ruby.style/#stylewhileuntilmodifier
|
138
|
+
|
139
|
+
Style/WordArray:
|
140
|
+
Enabled: false
|
141
|
+
StyleGuide: https://relaxed.ruby.style/#stylewordarray
|
142
|
+
|
143
|
+
Lint/AmbiguousRegexpLiteral:
|
144
|
+
Enabled: false
|
145
|
+
StyleGuide: https://relaxed.ruby.style/#lintambiguousregexpliteral
|
146
|
+
|
147
|
+
Lint/AssignmentInCondition:
|
148
|
+
Enabled: false
|
149
|
+
StyleGuide: https://relaxed.ruby.style/#lintassignmentincondition
|
150
|
+
|
151
|
+
Layout/LineLength:
|
152
|
+
Enabled: false
|
153
|
+
|
154
|
+
Metrics:
|
155
|
+
Enabled: false
|
data/.travis.yml
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
---
|
2
|
-
sudo: false
|
3
2
|
language: ruby
|
4
3
|
cache: bundler
|
4
|
+
services:
|
5
|
+
- postgresql
|
5
6
|
rvm:
|
6
7
|
- 2.6
|
8
|
+
- 2.7
|
9
|
+
env:
|
10
|
+
- DATABASE=":memory:"
|
11
|
+
- DATABASE="postgresql://postgres@0.0.0.0:5432/travis_ci_test"
|
7
12
|
before_install: gem install bundler -v 2.1
|
13
|
+
before_script: psql -c 'create database travis_ci_test;' -U postgres
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -10,19 +10,15 @@ Mihari is a helper to run queries & manage results continuously. Mihari can be u
|
|
10
10
|
|
11
11
|
## How it works
|
12
12
|
|
13
|
-
- Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts from the results.
|
14
|
-
- Mihari checks whether
|
13
|
+
- Mihari makes a query against Shodan, Censys, VirusTotal, SecurityTrails, etc. and extracts artifacts (IP addresses, domains, URLs and hashes) from the results.
|
14
|
+
- Mihari checks whether a DB (SQLite3 or PostgreSQL) contains the artifacts or not.
|
15
15
|
- If it doesn't contain the artifacts:
|
16
|
-
- Mihari creates an alert on TheHive.
|
16
|
+
- Mihari creates an alert on TheHive. (Optional)
|
17
17
|
- Mihari sends a notification to Slack. (Optional)
|
18
18
|
- Mihari creates an event on MISP. (Optional)
|
19
19
|
|
20
20
|
![img](https://github.com/ninoseki/mihari/raw/master/screenshots/eyecatch.png)
|
21
21
|
|
22
|
-
Check this blog post for more details: [Continuous C2 hunting with Censys, Shodan, Onyphe and TheHive](https://hackmd.io/s/SkUaSrqoE).
|
23
|
-
|
24
|
-
You can use mihari without TheHive but note that mihari depends on TheHive to manage artifacts. It means mihari might make duplications when without TheHive.
|
25
|
-
|
26
22
|
### Screenshots
|
27
23
|
|
28
24
|
- TheHive alert example
|
@@ -37,6 +33,17 @@ You can use mihari without TheHive but note that mihari depends on TheHive to ma
|
|
37
33
|
|
38
34
|
![img](https://github.com/ninoseki/mihari/raw/master/screenshots/misp.png)
|
39
35
|
|
36
|
+
## Requirements
|
37
|
+
|
38
|
+
- Ruby 2.6+
|
39
|
+
- SQLite3
|
40
|
+
- libpq
|
41
|
+
|
42
|
+
```bash
|
43
|
+
# For Debian / Ubuntu
|
44
|
+
apt-get install sqlite3 libsqlite3-dev libpq-dev
|
45
|
+
```
|
46
|
+
|
40
47
|
## Installation
|
41
48
|
|
42
49
|
```bash
|
@@ -156,49 +163,13 @@ mihari http_hash --html /tmp/index.html
|
|
156
163
|
|
157
164
|
```bash
|
158
165
|
# Censys lookup for PANDA C2
|
159
|
-
|
160
|
-
{
|
161
|
-
"title": "PANDA C2",
|
162
|
-
"description": "query = (\"PANDA\" AND \"SMAdmin\" AND \"layui\")",
|
163
|
-
"artifacts": [
|
164
|
-
"154.223.165.223",
|
165
|
-
"154.194.2.31",
|
166
|
-
"45.114.127.119",
|
167
|
-
"..."
|
168
|
-
],
|
169
|
-
"tags": []
|
170
|
-
}
|
166
|
+
mihari censys '("PANDA" AND "SMAdmin" AND "layui")' --title "PANDA C2"
|
171
167
|
|
172
168
|
# VirusTotal passive DNS lookup of a FAKESPY host
|
173
|
-
|
174
|
-
{
|
175
|
-
"title": "FAKESPY host passive DNS results",
|
176
|
-
"description": "indicator = jppost-hi.top",
|
177
|
-
"artifacts": [
|
178
|
-
"185.22.152.28",
|
179
|
-
"192.236.200.44",
|
180
|
-
"193.148.69.12",
|
181
|
-
"..."
|
182
|
-
],
|
183
|
-
"tags": []
|
184
|
-
}
|
169
|
+
mihari virustotal "jppost-hi.top" --title "FAKESPY passive DNS"
|
185
170
|
|
186
171
|
# You can pass a "defanged" indicator as an input
|
187
|
-
|
188
|
-
|
189
|
-
# SecurityTrails domain feed lookup for finding (possibly) Apple phishing websites
|
190
|
-
$ mihari securitytrails_domain_feed "apple-" --type new
|
191
|
-
{
|
192
|
-
"title": "SecurityTrails domain feed lookup",
|
193
|
-
"description": "Regexp = /apple-/",
|
194
|
-
"artifacts": [
|
195
|
-
"apple-sign.online",
|
196
|
-
"apple-log-in.com",
|
197
|
-
"apple-locator-id.info",
|
198
|
-
"..."
|
199
|
-
],
|
200
|
-
"tags": []
|
201
|
-
}
|
172
|
+
mihari virustotal "jppost-hi[.]top" --title "FAKESPY passive DNS"
|
202
173
|
```
|
203
174
|
|
204
175
|
### Import from JSON
|
@@ -229,28 +200,29 @@ The input is a JSON data should have `title`, `description` and `artifacts` key.
|
|
229
200
|
|
230
201
|
Configuration can be done via environment variables or a YAML file.
|
231
202
|
|
232
|
-
| Key |
|
233
|
-
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
| ZOOMEYE_PASSWORD | ZoomEye password
|
203
|
+
| Key | Description | Default |
|
204
|
+
|------------------------|-------------------------------------------------------------------------------------------------|-------------|
|
205
|
+
| DATABASE | A path to the SQLite database or a DB URL (e.g. `postgres://postgres:pass@db.host:5432/somedb`) | `mihari.db` |
|
206
|
+
| BINARYEDGE_API_KEY | BinaryEdge API key | |
|
207
|
+
| CENSYS_ID | Censys API ID | |
|
208
|
+
| CENSYS_SECRET | Censys secret | |
|
209
|
+
| CIRCL_PASSIVE_PASSWORD | CIRCL passive DNS/SSL password | |
|
210
|
+
| CIRCL_PASSIVE_USERNAME | CIRCL passive DNS/SSL username | |
|
211
|
+
| MISP_API_ENDPOINT | MISP URL | |
|
212
|
+
| MISP_API_KEY | MISP API key | |
|
213
|
+
| ONYPHE_API_KEY | Onyphe API key | |
|
214
|
+
| PASSIVETOTAL_API_KEY | PassiveTotal API key | |
|
215
|
+
| PASSIVETOTAL_USERNAME | PassiveTotal username | |
|
216
|
+
| PULSEDIVE_API_KEY | Pulsedive API key | |
|
217
|
+
| SECURITYTRAILS_API_KEY | SecurityTrails API key | |
|
218
|
+
| SHODAN_API_KEY | Shodan API key | |
|
219
|
+
| SLACK_CHANNEL | Slack channel name | `#general` |
|
220
|
+
| SLACK_WEBHOOK_URL | Slack Webhook URL | |
|
221
|
+
| THEHIVE_API_ENDPOINT | TheHive URL | |
|
222
|
+
| THEHIVE_API_KEY | TheHive API key | |
|
223
|
+
| VIRUSTOTAL_API_KEY | VirusTotal API key | |
|
224
|
+
| ZOOMEYE_PASSWORD | ZoomEye password | |
|
225
|
+
| ZOOMEYE_USERNAMME | ZoomEye username | |
|
254
226
|
|
255
227
|
Instead of using environment variables, you can use a YAML file for configuration.
|
256
228
|
|
@@ -261,6 +233,7 @@ mihari virustotal 1.1.1.1 --config /path/to/yaml.yml
|
|
261
233
|
The YAML file should be a YAML hash like below:
|
262
234
|
|
263
235
|
```yaml
|
236
|
+
database: /tmp/mihari.db
|
264
237
|
thehive_api_endpoint: https://localhost
|
265
238
|
thehive_api_key: foo
|
266
239
|
virustotal_api_key: foo
|
@@ -314,10 +287,6 @@ example.run
|
|
314
287
|
|
315
288
|
See `/examples` for more.
|
316
289
|
|
317
|
-
## Caching
|
318
|
-
|
319
|
-
Mihari caches execution results in `/tmp/mihari` and the default cache duration is 7 days. If you want to clear the cache, please clear `/tmp/mihari`.
|
320
|
-
|
321
290
|
## Using it with Docker
|
322
291
|
|
323
292
|
```bash
|
data/docker/Dockerfile
CHANGED
data/lib/mihari.rb
CHANGED
@@ -19,24 +19,27 @@ module Mihari
|
|
19
19
|
end
|
20
20
|
|
21
21
|
require "mihari/version"
|
22
|
-
|
23
22
|
require "mihari/errors"
|
24
23
|
|
25
|
-
require "mihari/artifact"
|
26
|
-
require "mihari/cache"
|
27
24
|
require "mihari/config"
|
25
|
+
|
26
|
+
require "mihari/database"
|
28
27
|
require "mihari/type_checker"
|
29
28
|
|
29
|
+
require "mihari/models/alert"
|
30
|
+
require "mihari/models/artifact"
|
31
|
+
require "mihari/models/tag"
|
32
|
+
require "mihari/models/tagging"
|
33
|
+
|
34
|
+
require "mihari/serializers/alert"
|
35
|
+
require "mihari/serializers/artifact"
|
36
|
+
require "mihari/serializers/tag"
|
37
|
+
|
30
38
|
require "mihari/html"
|
31
39
|
|
32
40
|
require "mihari/configurable"
|
33
41
|
require "mihari/retriable"
|
34
42
|
|
35
|
-
require "mihari/the_hive/base"
|
36
|
-
require "mihari/the_hive/alert"
|
37
|
-
require "mihari/the_hive/artifact"
|
38
|
-
require "mihari/the_hive"
|
39
|
-
|
40
43
|
require "mihari/analyzers/base"
|
41
44
|
require "mihari/analyzers/basic"
|
42
45
|
|
@@ -68,6 +71,7 @@ require "mihari/notifiers/slack"
|
|
68
71
|
require "mihari/notifiers/exception_notifier"
|
69
72
|
|
70
73
|
require "mihari/emitters/base"
|
74
|
+
require "mihari/emitters/database"
|
71
75
|
require "mihari/emitters/misp"
|
72
76
|
require "mihari/emitters/slack"
|
73
77
|
require "mihari/emitters/stdout"
|
data/lib/mihari/alert_viewer.rb
CHANGED
@@ -2,40 +2,22 @@
|
|
2
2
|
|
3
3
|
module Mihari
|
4
4
|
class AlertViewer
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
alerts.map { |alert| convert alert }
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def validate_limit
|
27
|
-
return true if limit == "all"
|
28
|
-
|
29
|
-
raise ArgumentError, "limit should be bigger than zero" unless limit.to_i.positive?
|
30
|
-
end
|
31
|
-
|
32
|
-
def convert(alert)
|
33
|
-
attributes = alert.select { |k, _v| ALERT_KEYS.include? k }
|
34
|
-
attributes["createdAt"] = Time.at(attributes["createdAt"] / 1000).to_s
|
35
|
-
attributes["artifacts"] = (attributes.dig("artifacts") || []).map do |artifact|
|
36
|
-
artifact.dig("data")
|
37
|
-
end.sort
|
38
|
-
attributes
|
5
|
+
def list(title: nil, source: nil, tag: nil, limit: 5)
|
6
|
+
limit = limit.to_i
|
7
|
+
raise ArgumentError, "limit should be bigger than zero" unless limit.positive?
|
8
|
+
|
9
|
+
relation = Alert.includes(:tags, :artifacts)
|
10
|
+
relation = relation.where(title: title) if title
|
11
|
+
relation = relation.where(source: source) if source
|
12
|
+
relation = relation.where(tags: { name: tag } ) if tag
|
13
|
+
|
14
|
+
alerts = relation.limit(limit).order(id: :desc)
|
15
|
+
alerts.map do |alert|
|
16
|
+
json = AlertSerializer.new(alert).as_json
|
17
|
+
json[:artifacts] = (json.dig(:artifacts) || []).map { |artifact_| artifact_.dig(:data) }
|
18
|
+
json[:tags] = (json.dig(:tags) || []).map { |tag_| tag_.dig(:name) }
|
19
|
+
json
|
20
|
+
end
|
39
21
|
end
|
40
22
|
end
|
41
23
|
end
|