mihari 5.2.2 → 5.2.4

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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/build_frontend.sh +1 -9
  3. data/frontend/.browserslistrc +3 -0
  4. data/frontend/.eslintrc.js +33 -0
  5. data/frontend/.gitignore +25 -0
  6. data/frontend/README.md +3 -0
  7. data/frontend/babel.config.js +3 -0
  8. data/frontend/index.html +21 -0
  9. data/frontend/jest.config.js +9 -0
  10. data/frontend/package-lock.json +13216 -0
  11. data/frontend/package.json +54 -0
  12. data/frontend/public/favicon.ico +0 -0
  13. data/frontend/scripts/swagger_doc_to_yaml.rb +23 -0
  14. data/frontend/src/App.vue +27 -0
  15. data/frontend/src/api-helper.ts +113 -0
  16. data/frontend/src/api.ts +105 -0
  17. data/frontend/src/components/ErrorMessage.vue +32 -0
  18. data/frontend/src/components/Loading.vue +15 -0
  19. data/frontend/src/components/Navbar.vue +59 -0
  20. data/frontend/src/components/Pagination.vue +126 -0
  21. data/frontend/src/components/alert/Alert.vue +92 -0
  22. data/frontend/src/components/alert/Alerts.vue +66 -0
  23. data/frontend/src/components/alert/AlertsWithPagination.vue +91 -0
  24. data/frontend/src/components/alert/AlertsWrapper.vue +141 -0
  25. data/frontend/src/components/alert/Form.vue +185 -0
  26. data/frontend/src/components/artifact/AS.vue +29 -0
  27. data/frontend/src/components/artifact/Artifact.vue +321 -0
  28. data/frontend/src/components/artifact/ArtifactTag.vue +70 -0
  29. data/frontend/src/components/artifact/ArtifactTags.vue +29 -0
  30. data/frontend/src/components/artifact/ArtifactWrapper.vue +62 -0
  31. data/frontend/src/components/artifact/CPEs.vue +23 -0
  32. data/frontend/src/components/artifact/DnsRecords.vue +38 -0
  33. data/frontend/src/components/artifact/Ports.vue +23 -0
  34. data/frontend/src/components/artifact/ReverseDnsNames.vue +31 -0
  35. data/frontend/src/components/artifact/Tags.vue +29 -0
  36. data/frontend/src/components/artifact/WhoisRecord.vue +49 -0
  37. data/frontend/src/components/config/Configs.vue +68 -0
  38. data/frontend/src/components/config/ConfigsWrapper.vue +40 -0
  39. data/frontend/src/components/link/Link.vue +32 -0
  40. data/frontend/src/components/link/Links.vue +47 -0
  41. data/frontend/src/components/rule/EditRule.vue +74 -0
  42. data/frontend/src/components/rule/EditRuleWrapper.vue +56 -0
  43. data/frontend/src/components/rule/Form.vue +160 -0
  44. data/frontend/src/components/rule/InputForm.vue +80 -0
  45. data/frontend/src/components/rule/NewRule.vue +60 -0
  46. data/frontend/src/components/rule/Rule.vue +108 -0
  47. data/frontend/src/components/rule/RuleWrapper.vue +62 -0
  48. data/frontend/src/components/rule/Rules.vue +88 -0
  49. data/frontend/src/components/rule/RulesWrapper.vue +130 -0
  50. data/frontend/src/components/rule/YAML.vue +47 -0
  51. data/frontend/src/components/tag/Tag.vue +73 -0
  52. data/frontend/src/components/tag/Tags.vue +37 -0
  53. data/frontend/src/countries.ts +350 -0
  54. data/frontend/src/index.ts +23 -0
  55. data/frontend/src/links/anyrun.ts +19 -0
  56. data/frontend/src/links/base.ts +14 -0
  57. data/frontend/src/links/censys.ts +20 -0
  58. data/frontend/src/links/crtsh.ts +20 -0
  59. data/frontend/src/links/dnslytics.ts +38 -0
  60. data/frontend/src/links/greynoise.ts +20 -0
  61. data/frontend/src/links/index.ts +40 -0
  62. data/frontend/src/links/intezer.ts +20 -0
  63. data/frontend/src/links/otx.ts +33 -0
  64. data/frontend/src/links/securitytrails.ts +38 -0
  65. data/frontend/src/links/shodan.ts +20 -0
  66. data/frontend/src/links/urlscan.ts +50 -0
  67. data/frontend/src/links/virustotal.ts +72 -0
  68. data/frontend/src/main.ts +11 -0
  69. data/frontend/src/router/index.ts +57 -0
  70. data/frontend/src/rule.ts +14 -0
  71. data/frontend/src/shims-vue.d.ts +6 -0
  72. data/frontend/src/swagger.yaml +737 -0
  73. data/frontend/src/types.ts +188 -0
  74. data/frontend/src/utils.ts +60 -0
  75. data/frontend/src/views/Alerts.vue +20 -0
  76. data/frontend/src/views/Artifact.vue +44 -0
  77. data/frontend/src/views/Configs.vue +20 -0
  78. data/frontend/src/views/EditRule.vue +44 -0
  79. data/frontend/src/views/NewRule.vue +26 -0
  80. data/frontend/src/views/Rule.vue +44 -0
  81. data/frontend/src/views/Rules.vue +20 -0
  82. data/frontend/tests/unit/utils.spec.ts +7 -0
  83. data/frontend/tsconfig.json +40 -0
  84. data/frontend/vite.config.js +24 -0
  85. data/lefthook.yml +10 -0
  86. data/lib/mihari/analyzers/base.rb +22 -5
  87. data/lib/mihari/analyzers/binaryedge.rb +0 -1
  88. data/lib/mihari/analyzers/censys.rb +7 -2
  89. data/lib/mihari/analyzers/circl.rb +1 -1
  90. data/lib/mihari/analyzers/passivetotal.rb +1 -1
  91. data/lib/mihari/analyzers/rule.rb +43 -73
  92. data/lib/mihari/analyzers/virustotal_intelligence.rb +1 -2
  93. data/lib/mihari/clients/base.rb +1 -1
  94. data/lib/mihari/commands/database.rb +12 -11
  95. data/lib/mihari/commands/rule.rb +47 -45
  96. data/lib/mihari/commands/search.rb +73 -45
  97. data/lib/mihari/commands/version.rb +8 -6
  98. data/lib/mihari/commands/web.rb +26 -23
  99. data/lib/mihari/emitters/base.rb +14 -1
  100. data/lib/mihari/emitters/database.rb +3 -10
  101. data/lib/mihari/emitters/misp.rb +16 -5
  102. data/lib/mihari/emitters/slack.rb +13 -15
  103. data/lib/mihari/emitters/the_hive.rb +17 -19
  104. data/lib/mihari/emitters/webhook.rb +23 -23
  105. data/lib/mihari/enrichers/whois.rb +1 -0
  106. data/lib/mihari/entities/rule.rb +1 -1
  107. data/lib/mihari/entities/tag.rb +1 -1
  108. data/lib/mihari/feed/parser.rb +1 -0
  109. data/lib/mihari/feed/reader.rb +29 -14
  110. data/lib/mihari/mixins/configurable.rb +13 -4
  111. data/lib/mihari/schemas/analyzer.rb +2 -7
  112. data/lib/mihari/schemas/rule.rb +1 -1
  113. data/lib/mihari/structs/censys.rb +96 -82
  114. data/lib/mihari/structs/config.rb +46 -21
  115. data/lib/mihari/structs/google_public_dns.rb +27 -23
  116. data/lib/mihari/structs/greynoise.rb +44 -38
  117. data/lib/mihari/structs/onyphe.rb +34 -30
  118. data/lib/mihari/structs/rule.rb +1 -1
  119. data/lib/mihari/structs/shodan.rb +77 -69
  120. data/lib/mihari/structs/urlscan.rb +42 -36
  121. data/lib/mihari/structs/virustotal_intelligence.rb +57 -49
  122. data/lib/mihari/type_checker.rb +10 -8
  123. data/lib/mihari/version.rb +1 -1
  124. data/lib/mihari/web/public/assets/index-ac4e5ffa.js +50 -0
  125. data/lib/mihari/web/public/index.html +1 -1
  126. data/mihari.gemspec +8 -8
  127. metadata +103 -22
  128. data/.gitmodules +0 -0
  129. data/.overcommit.yml +0 -12
  130. data/lib/mihari/web/public/assets/index-cbe1734c.js +0 -50
@@ -0,0 +1,188 @@
1
+ export interface Pagination {
2
+ total: number;
3
+ currentPage: number;
4
+ pageSize: number;
5
+ }
6
+
7
+ export interface ConfigValue {
8
+ key: string;
9
+ value: string | null;
10
+ }
11
+
12
+ export interface Config {
13
+ name: string;
14
+ isConfigured: boolean;
15
+ values: ConfigValue[];
16
+ type: string;
17
+ }
18
+
19
+ export interface Tag {
20
+ name: string;
21
+ }
22
+
23
+ export interface Tags {
24
+ tags: string[];
25
+ }
26
+
27
+ export interface RuleSet {
28
+ ruleIds: string[];
29
+ }
30
+
31
+ export interface DnsRecord {
32
+ resource: string;
33
+ value: string;
34
+ }
35
+
36
+ export interface Contact {
37
+ name: string | null;
38
+ organization: string | null;
39
+ }
40
+
41
+ export interface Registrar {
42
+ name: string | null;
43
+ organization: string | null;
44
+ }
45
+
46
+ export interface WhoisRecord {
47
+ createdOn: Date | null;
48
+ updatedOn: Date | null;
49
+ expiresOn: Date | null;
50
+ registrar: Registrar | null;
51
+ contacts: Contact[];
52
+ }
53
+
54
+ export interface AutonomousSystem {
55
+ asn: number;
56
+ }
57
+
58
+ export interface Geolocation {
59
+ country: string;
60
+ countryCode: string;
61
+ }
62
+
63
+ export interface ReverseDnsName {
64
+ name: string;
65
+ }
66
+
67
+ export interface CPE {
68
+ cpe: string;
69
+ }
70
+
71
+ export interface Port {
72
+ port: string;
73
+ }
74
+
75
+ export interface Artifact {
76
+ id: string;
77
+ data: string;
78
+ dataType: string;
79
+ source: string;
80
+ metadata: unknown | null;
81
+ createdAt: string;
82
+
83
+ autonomousSystem: AutonomousSystem | null;
84
+ whoisRecord: WhoisRecord | null;
85
+ geolocation: Geolocation | null;
86
+
87
+ dnsRecords: DnsRecord[] | null;
88
+ reverseDnsNames: ReverseDnsName[] | null;
89
+ cpes: CPE[] | null;
90
+ ports: Port[] | null;
91
+ }
92
+
93
+ export interface ArtifactWithTags extends Artifact {
94
+ tags: string[];
95
+ }
96
+
97
+ export interface Alert {
98
+ id: string;
99
+ ruleId: string;
100
+ createdAt: string;
101
+
102
+ tags: Tag[];
103
+ artifacts: Artifact[];
104
+ }
105
+
106
+ export interface Alerts extends Pagination {
107
+ alerts: Alert[];
108
+ }
109
+
110
+ export interface PaginationParams {
111
+ page: number | undefined;
112
+ }
113
+
114
+ export interface AlertSearchParams extends PaginationParams {
115
+ artifact: string | undefined;
116
+ ruleId: string | undefined;
117
+ tag: string | undefined;
118
+ fromAt: string | undefined;
119
+ toAt: string | undefined;
120
+ }
121
+
122
+ export interface IPInfo {
123
+ ip: string;
124
+ hostname: string | null;
125
+ loc: string;
126
+ countryCode: string;
127
+ asn: string;
128
+ }
129
+
130
+ export interface GCS {
131
+ lat: number;
132
+ long: number;
133
+ }
134
+
135
+ export interface Country {
136
+ name: string;
137
+ code: string;
138
+ lat: number;
139
+ long: number;
140
+ }
141
+
142
+ export type LinkType = "ip" | "domain" | "url" | "hash";
143
+
144
+ export interface Link {
145
+ name: string;
146
+ type: string;
147
+ baseURL: string;
148
+ // eslint-disable-next-line no-unused-vars
149
+ href(data: string): string;
150
+ favicon(): string;
151
+ }
152
+
153
+ export interface Rule {
154
+ id: string;
155
+ title: string;
156
+ description: string;
157
+ yaml: string;
158
+ createdAt: string;
159
+ updatedAt: string;
160
+ tags: Tag[];
161
+ }
162
+
163
+ export interface CreateRule {
164
+ yaml: string;
165
+ }
166
+
167
+ export interface UpdateRule {
168
+ id: string;
169
+ yaml: string;
170
+ }
171
+
172
+ export interface Query {
173
+ analyzer: string;
174
+ query: string;
175
+ interval: null;
176
+ }
177
+
178
+ export interface Rules extends Pagination {
179
+ rules: Rule[];
180
+ }
181
+
182
+ export interface RuleSearchParams extends PaginationParams {
183
+ description: string | undefined;
184
+ tag: string | undefined;
185
+ title: string | undefined;
186
+ fromAt: string | undefined;
187
+ toAt: string | undefined;
188
+ }
@@ -0,0 +1,60 @@
1
+ import dayjs from "dayjs";
2
+ import relativeTime from "dayjs/plugin/relativeTime";
3
+ import timezone from "dayjs/plugin/timezone";
4
+ import utc from "dayjs/plugin/utc";
5
+ import { LocationQueryValue } from "vue-router";
6
+
7
+ import { getCountryByCode } from "@/countries";
8
+ import { GCS, IPInfo } from "@/types";
9
+
10
+ dayjs.extend(relativeTime);
11
+ dayjs.extend(timezone);
12
+ dayjs.extend(utc);
13
+
14
+ export function getLocalDatetime(datetime: string): string {
15
+ return dayjs(datetime).local().format("YYYY-MM-DD HH:mm:ss");
16
+ }
17
+
18
+ export function getHumanizedRelativeTime(datetime: string): string {
19
+ return dayjs(datetime).local().fromNow();
20
+ }
21
+
22
+ export function getGCSByCountryCode(countryCode: string): GCS | undefined {
23
+ const country = getCountryByCode(countryCode);
24
+ if (country !== undefined) {
25
+ return { lat: country.lat, long: country.long };
26
+ }
27
+ }
28
+
29
+ export function getGCSByIPInfo(ipinfo: IPInfo): GCS | undefined {
30
+ if (ipinfo.loc !== undefined) {
31
+ const numbers = ipinfo.loc.split(",");
32
+ if (numbers.length === 2) {
33
+ const lat = numbers[0];
34
+ const long = numbers[1];
35
+
36
+ return { lat: parseFloat(lat), long: parseFloat(long) };
37
+ }
38
+ }
39
+ return getGCSByCountryCode(ipinfo.countryCode);
40
+ }
41
+
42
+ export function normalizeQueryParam(
43
+ param:
44
+ | undefined
45
+ | null
46
+ | string
47
+ | string[]
48
+ | LocationQueryValue
49
+ | LocationQueryValue[]
50
+ ): string | undefined {
51
+ if (param === undefined || param === null) {
52
+ return undefined;
53
+ }
54
+
55
+ if (typeof param === "string") {
56
+ return param;
57
+ }
58
+
59
+ return param.toString();
60
+ }
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <Alerts></Alerts>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent } from "vue";
8
+
9
+ import Alerts from "@/components/alert/AlertsWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "AlertsView",
13
+ components: {
14
+ Alerts,
15
+ },
16
+ setup() {
17
+ useTitle("Alerts - Mihari");
18
+ },
19
+ });
20
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <Artifact :id="artifactId"></Artifact>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent, onMounted, ref, watch } from "vue";
8
+
9
+ import Artifact from "@/components/artifact/ArtifactWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "ArtifactView",
13
+ components: {
14
+ Artifact,
15
+ },
16
+ props: {
17
+ id: {
18
+ type: String,
19
+ required: true,
20
+ },
21
+ },
22
+ setup(props) {
23
+ const artifactId = ref<string>(props.id);
24
+
25
+ const updateTitle = () => {
26
+ useTitle(`Artifact:${artifactId.value} - Mihari`);
27
+ };
28
+
29
+ onMounted(() => {
30
+ updateTitle();
31
+ });
32
+
33
+ watch(
34
+ () => props.id,
35
+ () => {
36
+ artifactId.value = props.id;
37
+ updateTitle();
38
+ }
39
+ );
40
+
41
+ return { artifactId };
42
+ },
43
+ });
44
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <Configs></Configs>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent } from "vue";
8
+
9
+ import Configs from "@/components/config/ConfigsWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "ConfigView",
13
+ components: {
14
+ Configs,
15
+ },
16
+ setup() {
17
+ useTitle("Config - Mihari");
18
+ },
19
+ });
20
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <EditRule :id="id"></EditRule>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent, onMounted, ref, watch } from "vue";
8
+
9
+ import EditRule from "@/components/rule/EditRuleWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "EditRuleView",
13
+ components: {
14
+ EditRule,
15
+ },
16
+ props: {
17
+ id: {
18
+ type: String,
19
+ required: true,
20
+ },
21
+ },
22
+ setup(props) {
23
+ const ruleId = ref<string>(props.id);
24
+
25
+ const updateTitle = () => {
26
+ useTitle(`Edit rule:${ruleId.value} - Mihari`);
27
+ };
28
+
29
+ onMounted(() => {
30
+ updateTitle();
31
+ });
32
+
33
+ watch(
34
+ () => props.id,
35
+ () => {
36
+ ruleId.value = props.id;
37
+ updateTitle();
38
+ }
39
+ );
40
+
41
+ return { ruleId };
42
+ },
43
+ });
44
+ </script>
@@ -0,0 +1,26 @@
1
+ <template>
2
+ <NewRule></NewRule>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent, onMounted } from "vue";
8
+
9
+ import NewRule from "@/components/rule/NewRule.vue";
10
+
11
+ export default defineComponent({
12
+ name: "NewRuleView",
13
+ components: {
14
+ NewRule,
15
+ },
16
+ setup() {
17
+ const updateTitle = () => {
18
+ useTitle(`New rule - Mihari`);
19
+ };
20
+
21
+ onMounted(() => {
22
+ updateTitle();
23
+ });
24
+ },
25
+ });
26
+ </script>
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <Rule :id="ruleId"></Rule>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent, onMounted, ref, watch } from "vue";
8
+
9
+ import Rule from "@/components/rule/RuleWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "RuleView",
13
+ components: {
14
+ Rule,
15
+ },
16
+ props: {
17
+ id: {
18
+ type: String,
19
+ required: true,
20
+ },
21
+ },
22
+ setup(props) {
23
+ const ruleId = ref<string>(props.id);
24
+
25
+ const updateTitle = () => {
26
+ useTitle(`Rule:${ruleId.value} - Mihari`);
27
+ };
28
+
29
+ onMounted(() => {
30
+ updateTitle();
31
+ });
32
+
33
+ watch(
34
+ () => props.id,
35
+ () => {
36
+ ruleId.value = props.id;
37
+ updateTitle();
38
+ }
39
+ );
40
+
41
+ return { ruleId };
42
+ },
43
+ });
44
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <Rules></Rules>
3
+ </template>
4
+
5
+ <script lang="ts">
6
+ import { useTitle } from "@vueuse/core";
7
+ import { defineComponent } from "vue";
8
+
9
+ import Rules from "@/components/rule/RulesWrapper.vue";
10
+
11
+ export default defineComponent({
12
+ name: "RulesView",
13
+ components: {
14
+ Rules,
15
+ },
16
+ setup() {
17
+ useTitle("Rules - Mihari");
18
+ },
19
+ });
20
+ </script>
@@ -0,0 +1,7 @@
1
+ import { getHumanizedRelativeTime } from "@/utils";
2
+
3
+ describe("getHumanizedRelativeTime", () => {
4
+ it("returns a relative time in humanized format", () => {
5
+ expect(getHumanizedRelativeTime("1970-01-01 00:00:00")).toContain("years");
6
+ });
7
+ });
@@ -0,0 +1,40 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "esnext",
4
+ "module": "esnext",
5
+ "strict": true,
6
+ "jsx": "preserve",
7
+ "importHelpers": true,
8
+ "moduleResolution": "node",
9
+ "skipLibCheck": true,
10
+ "esModuleInterop": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "sourceMap": true,
13
+ "baseUrl": ".",
14
+ "types": [
15
+ "webpack-env",
16
+ "jest"
17
+ ],
18
+ "paths": {
19
+ "@/*": [
20
+ "src/*"
21
+ ]
22
+ },
23
+ "lib": [
24
+ "esnext",
25
+ "dom",
26
+ "dom.iterable",
27
+ "scripthost"
28
+ ]
29
+ },
30
+ "include": [
31
+ "src/**/*.ts",
32
+ "src/**/*.tsx",
33
+ "src/**/*.vue",
34
+ "tests/**/*.ts",
35
+ "tests/**/*.tsx"
36
+ ],
37
+ "exclude": [
38
+ "node_modules"
39
+ ]
40
+ }
@@ -0,0 +1,24 @@
1
+ import vue from "@vitejs/plugin-vue";
2
+ import path from "path";
3
+ import { defineConfig, loadEnv } from "vite";
4
+
5
+ export default defineConfig(({ _, mode }) => {
6
+ const env = loadEnv(mode, process.cwd(), "");
7
+ const target = env.BACKEND_URL || "http://localhost:9292/";
8
+ const port = env.port || 8080;
9
+
10
+ return {
11
+ plugins: [vue()],
12
+ server: {
13
+ port,
14
+ proxy: {
15
+ "/api": target,
16
+ },
17
+ },
18
+ resolve: {
19
+ alias: {
20
+ "@": path.resolve(__dirname, "./src"),
21
+ },
22
+ },
23
+ };
24
+ });
data/lefthook.yml ADDED
@@ -0,0 +1,10 @@
1
+ pre-commit:
2
+ parallel: true
3
+ commands:
4
+ standard:
5
+ glob: "*.rb"
6
+ run: bundle exec standardrb --fix {staged_files} && git add {staged_files}
7
+ eslint:
8
+ root: "frontend/"
9
+ glob: "*.{js,ts,vue}"
10
+ run: npx eslint --fix {staged_files} && git add {staged_files}
@@ -35,17 +35,34 @@ module Mihari
35
35
  end
36
36
  end
37
37
 
38
- # @return [String]
39
- def source
40
- self.class.to_s.split("::").last.to_s
41
- end
42
-
43
38
  # @return [String]
44
39
  def class_name
45
40
  self.class.to_s.split("::").last
46
41
  end
47
42
 
43
+ alias_method :source, :class_name
44
+
48
45
  class << self
46
+ #
47
+ # Initialize an analyzer by query params
48
+ #
49
+ # @param [Hash] params
50
+ #
51
+ # @return [Mihari::Analyzers::Base]
52
+ #
53
+ def from_query(params)
54
+ # get options and set default value as an empty hash
55
+ options = params[:options] || {}
56
+
57
+ # set interval in the top level
58
+ interval = options[:interval]
59
+ params[:interval] = interval if interval
60
+
61
+ query = params[:query]
62
+
63
+ new(query, **params)
64
+ end
65
+
49
66
  def inherited(child)
50
67
  super
51
68
  Mihari.analyzers << child
@@ -42,7 +42,6 @@ module Mihari
42
42
  #
43
43
  # Search with pagination
44
44
  #
45
- # @param [String] query
46
45
  # @param [Integer] page
47
46
  #
48
47
  # @return [Hash]
@@ -37,7 +37,12 @@ module Mihari
37
37
  response = client.search(query, cursor: cursor)
38
38
  artifacts << response.result.to_artifacts
39
39
  cursor = response.result.links.next
40
- break if cursor.nil?
40
+ # NOTE: Censys's search API is unstable recently
41
+ # it may returns empty links or empty string cursors
42
+ # - Empty links: "links": {}
43
+ # - Empty cursors: "links": { "next": "", "prev": "" }
44
+ # So it needs to check both cases
45
+ break if cursor.nil? || cursor.empty?
41
46
 
42
47
  # sleep #{interval} seconds to avoid the rate limitation (if it is set)
43
48
  sleep interval
@@ -50,7 +55,7 @@ module Mihari
50
55
  # @return [Boolean]
51
56
  #
52
57
  def configured?
53
- configuration_keys.all? { |key| Mihari.config.send(key) } || (id? && secret?)
58
+ configuration_keys? || (id? && secret?)
54
59
  end
55
60
 
56
61
  private
@@ -41,7 +41,7 @@ module Mihari
41
41
  end
42
42
 
43
43
  def configured?
44
- configuration_keys.all? { |key| Mihari.config.send(key) } || (username? && password?)
44
+ configuration_keys? || (username? && password?)
45
45
  end
46
46
 
47
47
  private
@@ -43,7 +43,7 @@ module Mihari
43
43
  end
44
44
 
45
45
  def configured?
46
- configuration_keys.all? { |key| Mihari.config.send(key) } || (username? && api_key?)
46
+ configuration_keys? || (username? && api_key?)
47
47
  end
48
48
 
49
49
  private