@cyvest/cyvest-js 2.0.1
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.
- package/README.md +51 -0
- package/dist/index.d.mts +1210 -0
- package/dist/index.d.ts +1210 -0
- package/dist/index.js +1768 -0
- package/dist/index.mjs +1632 -0
- package/package.json +27 -0
- package/src/finders.ts +712 -0
- package/src/getters.ts +409 -0
- package/src/graph.ts +601 -0
- package/src/helpers.ts +31 -0
- package/src/index.ts +28 -0
- package/src/keys.ts +286 -0
- package/src/levels.ts +262 -0
- package/src/types.generated.ts +176 -0
- package/tests/getters-finders.test.ts +461 -0
- package/tests/graph.test.ts +398 -0
- package/tests/keys-levels.test.ts +298 -0
- package/tsconfig.json +4 -0
package/src/keys.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key generation utilities for Cyvest objects.
|
|
3
|
+
*
|
|
4
|
+
* Provides deterministic, unique key generation for all object types.
|
|
5
|
+
* Keys are used for object identification, retrieval, and merging.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Key type prefixes used in Cyvest.
|
|
10
|
+
*/
|
|
11
|
+
export type KeyType = "obs" | "chk" | "ti" | "enr" | "ctr";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Normalize a string value for consistent key generation.
|
|
15
|
+
*/
|
|
16
|
+
function normalizeValue(value: string): string {
|
|
17
|
+
return value.trim().toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a deterministic hash from a string using a simple hash algorithm.
|
|
22
|
+
* Uses a subset of characters for shorter keys.
|
|
23
|
+
*/
|
|
24
|
+
function hashString(content: string, length: number = 16): string {
|
|
25
|
+
// Simple hash implementation (similar to Java's hashCode)
|
|
26
|
+
// For production, consider using crypto.subtle or a library
|
|
27
|
+
let hash = 0;
|
|
28
|
+
for (let i = 0; i < content.length; i++) {
|
|
29
|
+
const char = content.charCodeAt(i);
|
|
30
|
+
hash = ((hash << 5) - hash + char) | 0;
|
|
31
|
+
}
|
|
32
|
+
// Convert to hex and pad/truncate
|
|
33
|
+
const hex = Math.abs(hash).toString(16).padStart(8, "0");
|
|
34
|
+
return hex.slice(0, length);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generate a unique key for an observable.
|
|
39
|
+
*
|
|
40
|
+
* Format: obs:{type}:{normalized_value}
|
|
41
|
+
*
|
|
42
|
+
* @param obsType - Type of observable (ip, url, domain, hash, etc.)
|
|
43
|
+
* @param value - Value of the observable
|
|
44
|
+
* @returns Unique observable key
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* generateObservableKey("ipv4-addr", "192.168.1.1")
|
|
49
|
+
* // => "obs:ipv4-addr:192.168.1.1"
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function generateObservableKey(obsType: string, value: string): string {
|
|
53
|
+
const normalizedType = normalizeValue(obsType);
|
|
54
|
+
const normalizedValue = normalizeValue(value);
|
|
55
|
+
return `obs:${normalizedType}:${normalizedValue}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate a unique key for a check.
|
|
60
|
+
*
|
|
61
|
+
* Format: chk:{check_id}:{scope}
|
|
62
|
+
*
|
|
63
|
+
* @param checkId - Identifier of the check
|
|
64
|
+
* @param scope - Scope of the check
|
|
65
|
+
* @returns Unique check key
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* generateCheckKey("sender_verification", "email_headers")
|
|
70
|
+
* // => "chk:sender_verification:email_headers"
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function generateCheckKey(checkId: string, scope: string): string {
|
|
74
|
+
const normalizedId = normalizeValue(checkId);
|
|
75
|
+
const normalizedScope = normalizeValue(scope);
|
|
76
|
+
return `chk:${normalizedId}:${normalizedScope}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate a unique key for threat intelligence.
|
|
81
|
+
*
|
|
82
|
+
* Format: ti:{normalized_source}:{observable_key}
|
|
83
|
+
*
|
|
84
|
+
* @param source - Name of the threat intel source
|
|
85
|
+
* @param observableKey - Key of the related observable
|
|
86
|
+
* @returns Unique threat intel key
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* generateThreatIntelKey("virustotal", "obs:ipv4-addr:192.168.1.1")
|
|
91
|
+
* // => "ti:virustotal:obs:ipv4-addr:192.168.1.1"
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export function generateThreatIntelKey(
|
|
95
|
+
source: string,
|
|
96
|
+
observableKey: string
|
|
97
|
+
): string {
|
|
98
|
+
const normalizedSource = normalizeValue(source);
|
|
99
|
+
return `ti:${normalizedSource}:${observableKey}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Generate a unique key for an enrichment.
|
|
104
|
+
*
|
|
105
|
+
* Format: enr:{name} or enr:{name}:{context_hash}
|
|
106
|
+
*
|
|
107
|
+
* @param name - Name of the enrichment
|
|
108
|
+
* @param context - Optional context string for disambiguation
|
|
109
|
+
* @returns Unique enrichment key
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* generateEnrichmentKey("whois_data")
|
|
114
|
+
* // => "enr:whois_data"
|
|
115
|
+
*
|
|
116
|
+
* generateEnrichmentKey("whois_data", "domain:example.com")
|
|
117
|
+
* // => "enr:whois_data:a1b2c3d4"
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export function generateEnrichmentKey(name: string, context?: string): string {
|
|
121
|
+
const normalizedName = normalizeValue(name);
|
|
122
|
+
if (context) {
|
|
123
|
+
const contextHash = hashString(context, 8);
|
|
124
|
+
return `enr:${normalizedName}:${contextHash}`;
|
|
125
|
+
}
|
|
126
|
+
return `enr:${normalizedName}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate a unique key for a container.
|
|
131
|
+
*
|
|
132
|
+
* Format: ctr:{normalized_path}
|
|
133
|
+
*
|
|
134
|
+
* @param path - Path of the container (can use / or . as separator)
|
|
135
|
+
* @returns Unique container key
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* generateContainerKey("email/headers")
|
|
140
|
+
* // => "ctr:email/headers"
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function generateContainerKey(path: string): string {
|
|
144
|
+
// Normalize path separators
|
|
145
|
+
let normalizedPath = path.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
146
|
+
normalizedPath = normalizeValue(normalizedPath);
|
|
147
|
+
return `ctr:${normalizedPath}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Extract the type prefix from a key.
|
|
152
|
+
*
|
|
153
|
+
* @param key - The key to parse
|
|
154
|
+
* @returns Type prefix (obs, chk, ti, enr, ctr) or null if invalid
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* parseKeyType("obs:ipv4-addr:192.168.1.1") // => "obs"
|
|
159
|
+
* parseKeyType("invalid") // => null
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export function parseKeyType(key: string): KeyType | null {
|
|
163
|
+
if (key.includes(":")) {
|
|
164
|
+
const prefix = key.split(":", 1)[0] as KeyType;
|
|
165
|
+
if (["obs", "chk", "ti", "enr", "ctr"].includes(prefix)) {
|
|
166
|
+
return prefix;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validate a key format and optionally check its type.
|
|
174
|
+
*
|
|
175
|
+
* @param key - The key to validate
|
|
176
|
+
* @param expectedType - Optional expected type prefix
|
|
177
|
+
* @returns True if valid, false otherwise
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* validateKey("obs:ipv4-addr:192.168.1.1") // => true
|
|
182
|
+
* validateKey("obs:ipv4-addr:192.168.1.1", "obs") // => true
|
|
183
|
+
* validateKey("obs:ipv4-addr:192.168.1.1", "chk") // => false
|
|
184
|
+
* validateKey("invalid") // => false
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
export function validateKey(key: string, expectedType?: KeyType): boolean {
|
|
188
|
+
if (!key || !key.includes(":")) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const keyType = parseKeyType(key);
|
|
193
|
+
if (!keyType) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (expectedType && keyType !== expectedType) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Extract components from an observable key.
|
|
206
|
+
*
|
|
207
|
+
* @param key - Observable key to parse
|
|
208
|
+
* @returns Object with type and value, or null if invalid
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* parseObservableKey("obs:ipv4-addr:192.168.1.1")
|
|
213
|
+
* // => { type: "ipv4-addr", value: "192.168.1.1" }
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
export function parseObservableKey(
|
|
217
|
+
key: string
|
|
218
|
+
): { type: string; value: string } | null {
|
|
219
|
+
if (!validateKey(key, "obs")) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const parts = key.split(":");
|
|
223
|
+
if (parts.length >= 3) {
|
|
224
|
+
return {
|
|
225
|
+
type: parts[1],
|
|
226
|
+
value: parts.slice(2).join(":"), // Handle values with colons
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Extract components from a check key.
|
|
234
|
+
*
|
|
235
|
+
* @param key - Check key to parse
|
|
236
|
+
* @returns Object with checkId and scope, or null if invalid
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* parseCheckKey("chk:sender_verification:email_headers")
|
|
241
|
+
* // => { checkId: "sender_verification", scope: "email_headers" }
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
export function parseCheckKey(
|
|
245
|
+
key: string
|
|
246
|
+
): { checkId: string; scope: string } | null {
|
|
247
|
+
if (!validateKey(key, "chk")) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
const parts = key.split(":");
|
|
251
|
+
if (parts.length >= 3) {
|
|
252
|
+
return {
|
|
253
|
+
checkId: parts[1],
|
|
254
|
+
scope: parts.slice(2).join(":"),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Extract components from a threat intel key.
|
|
262
|
+
*
|
|
263
|
+
* @param key - Threat intel key to parse
|
|
264
|
+
* @returns Object with source and observableKey, or null if invalid
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```ts
|
|
268
|
+
* parseThreatIntelKey("ti:virustotal:obs:ipv4-addr:192.168.1.1")
|
|
269
|
+
* // => { source: "virustotal", observableKey: "obs:ipv4-addr:192.168.1.1" }
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
export function parseThreatIntelKey(
|
|
273
|
+
key: string
|
|
274
|
+
): { source: string; observableKey: string } | null {
|
|
275
|
+
if (!validateKey(key, "ti")) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
const parts = key.split(":");
|
|
279
|
+
if (parts.length >= 3) {
|
|
280
|
+
return {
|
|
281
|
+
source: parts[1],
|
|
282
|
+
observableKey: parts.slice(2).join(":"),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
package/src/levels.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Level enumeration and scoring logic for Cyvest.
|
|
3
|
+
*
|
|
4
|
+
* This module defines the security level classification system and the algorithm
|
|
5
|
+
* for determining levels from scores.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Observable, Check, ThreatIntel, Container, Level } from "./types.generated";
|
|
9
|
+
|
|
10
|
+
// Re-export Level type from types.generated for convenience
|
|
11
|
+
export type { Level } from "./types.generated";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ordered array of levels from lowest to highest severity.
|
|
15
|
+
*/
|
|
16
|
+
export const LEVEL_ORDER: readonly Level[] = [
|
|
17
|
+
"NONE",
|
|
18
|
+
"TRUSTED",
|
|
19
|
+
"INFO",
|
|
20
|
+
"SAFE",
|
|
21
|
+
"NOTABLE",
|
|
22
|
+
"SUSPICIOUS",
|
|
23
|
+
"MALICIOUS",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Numeric values for each level (for comparison purposes).
|
|
28
|
+
*/
|
|
29
|
+
export const LEVEL_VALUES: Record<Level, number> = {
|
|
30
|
+
NONE: 0,
|
|
31
|
+
TRUSTED: 1,
|
|
32
|
+
INFO: 2,
|
|
33
|
+
SAFE: 3,
|
|
34
|
+
NOTABLE: 4,
|
|
35
|
+
SUSPICIOUS: 5,
|
|
36
|
+
MALICIOUS: 6,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Color mapping for display purposes.
|
|
41
|
+
*/
|
|
42
|
+
export const LEVEL_COLORS: Record<Level, string> = {
|
|
43
|
+
NONE: "#808080", // gray
|
|
44
|
+
TRUSTED: "#22c55e", // green
|
|
45
|
+
INFO: "#06b6d4", // cyan
|
|
46
|
+
SAFE: "#4ade80", // bright green
|
|
47
|
+
NOTABLE: "#eab308", // yellow
|
|
48
|
+
SUSPICIOUS: "#f97316", // orange
|
|
49
|
+
MALICIOUS: "#ef4444", // red
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Normalize a level input to the Level type.
|
|
54
|
+
*
|
|
55
|
+
* Accepts a case-insensitive string and returns the normalized Level.
|
|
56
|
+
*
|
|
57
|
+
* @param level - Level string (e.g., "malicious", "MALICIOUS")
|
|
58
|
+
* @returns The normalized Level
|
|
59
|
+
* @throws Error if the string does not match a valid Level
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* normalizeLevel("malicious") // => "MALICIOUS"
|
|
64
|
+
* normalizeLevel("TRUSTED") // => "TRUSTED"
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function normalizeLevel(level: string): Level {
|
|
68
|
+
const upper = level.toUpperCase();
|
|
69
|
+
if (LEVEL_ORDER.includes(upper as Level)) {
|
|
70
|
+
return upper as Level;
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Invalid level name: ${level}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if a string is a valid Level.
|
|
77
|
+
*
|
|
78
|
+
* @param level - String to check
|
|
79
|
+
* @returns True if valid Level
|
|
80
|
+
*/
|
|
81
|
+
export function isValidLevel(level: string): level is Level {
|
|
82
|
+
return LEVEL_ORDER.includes(level.toUpperCase() as Level);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Calculate the security level from a numeric score.
|
|
87
|
+
*
|
|
88
|
+
* Algorithm:
|
|
89
|
+
* - score < 0.0 -> TRUSTED
|
|
90
|
+
* - score === 0.0 -> INFO
|
|
91
|
+
* - score < 3.0 -> NOTABLE
|
|
92
|
+
* - score < 5.0 -> SUSPICIOUS
|
|
93
|
+
* - score >= 5.0 -> MALICIOUS
|
|
94
|
+
*
|
|
95
|
+
* @param score - The numeric score to evaluate
|
|
96
|
+
* @returns The appropriate Level based on the score
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* getLevelFromScore(-1) // => "TRUSTED"
|
|
101
|
+
* getLevelFromScore(0) // => "INFO"
|
|
102
|
+
* getLevelFromScore(2.5) // => "NOTABLE"
|
|
103
|
+
* getLevelFromScore(4) // => "SUSPICIOUS"
|
|
104
|
+
* getLevelFromScore(5) // => "MALICIOUS"
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export function getLevelFromScore(score: number): Level {
|
|
108
|
+
if (score < 0) {
|
|
109
|
+
return "TRUSTED";
|
|
110
|
+
}
|
|
111
|
+
if (score === 0) {
|
|
112
|
+
return "INFO";
|
|
113
|
+
}
|
|
114
|
+
if (score < 3) {
|
|
115
|
+
return "NOTABLE";
|
|
116
|
+
}
|
|
117
|
+
if (score < 5) {
|
|
118
|
+
return "SUSPICIOUS";
|
|
119
|
+
}
|
|
120
|
+
return "MALICIOUS";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Compare two levels.
|
|
125
|
+
*
|
|
126
|
+
* @param a - First level
|
|
127
|
+
* @param b - Second level
|
|
128
|
+
* @returns -1 if a < b, 0 if a === b, 1 if a > b
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* compareLevels("INFO", "MALICIOUS") // => -1
|
|
133
|
+
* compareLevels("MALICIOUS", "INFO") // => 1
|
|
134
|
+
* compareLevels("INFO", "INFO") // => 0
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export function compareLevels(a: Level, b: Level): -1 | 0 | 1 {
|
|
138
|
+
const valueA = LEVEL_VALUES[a];
|
|
139
|
+
const valueB = LEVEL_VALUES[b];
|
|
140
|
+
if (valueA < valueB) return -1;
|
|
141
|
+
if (valueA > valueB) return 1;
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if level a is higher (more severe) than level b.
|
|
147
|
+
*
|
|
148
|
+
* @param a - First level
|
|
149
|
+
* @param b - Second level
|
|
150
|
+
* @returns True if a is higher than b
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* isLevelHigherThan("MALICIOUS", "SUSPICIOUS") // => true
|
|
155
|
+
* isLevelHigherThan("INFO", "MALICIOUS") // => false
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export function isLevelHigherThan(a: Level, b: Level): boolean {
|
|
159
|
+
return LEVEL_VALUES[a] > LEVEL_VALUES[b];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Check if level a is lower (less severe) than level b.
|
|
164
|
+
*
|
|
165
|
+
* @param a - First level
|
|
166
|
+
* @param b - Second level
|
|
167
|
+
* @returns True if a is lower than b
|
|
168
|
+
*/
|
|
169
|
+
export function isLevelLowerThan(a: Level, b: Level): boolean {
|
|
170
|
+
return LEVEL_VALUES[a] < LEVEL_VALUES[b];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if level a is at least as severe as level b.
|
|
175
|
+
*
|
|
176
|
+
* @param a - Level to check
|
|
177
|
+
* @param minLevel - Minimum required level
|
|
178
|
+
* @returns True if a is at least minLevel
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* isLevelAtLeast("MALICIOUS", "SUSPICIOUS") // => true
|
|
183
|
+
* isLevelAtLeast("SUSPICIOUS", "SUSPICIOUS") // => true
|
|
184
|
+
* isLevelAtLeast("INFO", "SUSPICIOUS") // => false
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
export function isLevelAtLeast(a: Level, minLevel: Level): boolean {
|
|
188
|
+
return LEVEL_VALUES[a] >= LEVEL_VALUES[minLevel];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get the maximum (most severe) level from an array of levels.
|
|
193
|
+
*
|
|
194
|
+
* @param levels - Array of levels
|
|
195
|
+
* @returns The most severe level, or "NONE" if array is empty
|
|
196
|
+
*/
|
|
197
|
+
export function maxLevel(levels: Level[]): Level {
|
|
198
|
+
if (levels.length === 0) return "NONE";
|
|
199
|
+
return levels.reduce((max, level) =>
|
|
200
|
+
isLevelHigherThan(level, max) ? level : max
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get the minimum (least severe) level from an array of levels.
|
|
206
|
+
*
|
|
207
|
+
* @param levels - Array of levels
|
|
208
|
+
* @returns The least severe level, or "MALICIOUS" if array is empty
|
|
209
|
+
*/
|
|
210
|
+
export function minLevel(levels: Level[]): Level {
|
|
211
|
+
if (levels.length === 0) return "MALICIOUS";
|
|
212
|
+
return levels.reduce((min, level) =>
|
|
213
|
+
isLevelLowerThan(level, min) ? level : min
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get the color associated with a level for display purposes.
|
|
219
|
+
*
|
|
220
|
+
* @param level - Level to get color for
|
|
221
|
+
* @returns Hex color string
|
|
222
|
+
*/
|
|
223
|
+
export function getColorForLevel(level: Level): string {
|
|
224
|
+
return LEVEL_COLORS[level];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get the color associated with a score for display purposes.
|
|
229
|
+
*
|
|
230
|
+
* @param score - Score to get color for
|
|
231
|
+
* @returns Hex color string
|
|
232
|
+
*/
|
|
233
|
+
export function getColorForScore(score: number): string {
|
|
234
|
+
return getColorForLevel(getLevelFromScore(score));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Type guard to check if an object has a level property.
|
|
239
|
+
*/
|
|
240
|
+
export function hasLevel(
|
|
241
|
+
obj: unknown
|
|
242
|
+
): obj is { level: Level } {
|
|
243
|
+
return (
|
|
244
|
+
typeof obj === "object" &&
|
|
245
|
+
obj !== null &&
|
|
246
|
+
"level" in obj &&
|
|
247
|
+
typeof (obj as { level: unknown }).level === "string" &&
|
|
248
|
+
isValidLevel((obj as { level: string }).level)
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Extract level from an entity (Observable, Check, ThreatIntel, Container).
|
|
254
|
+
*/
|
|
255
|
+
export function getEntityLevel(
|
|
256
|
+
entity: Observable | Check | ThreatIntel | Container
|
|
257
|
+
): Level {
|
|
258
|
+
if ("aggregated_level" in entity) {
|
|
259
|
+
return entity.aggregated_level;
|
|
260
|
+
}
|
|
261
|
+
return entity.level;
|
|
262
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// AUTO-GENERATED FROM cyvest.schema.json — DO NOT EDIT
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Security level classification from NONE (lowest) to MALICIOUS (highest).
|
|
5
|
+
*/
|
|
6
|
+
export type Level =
|
|
7
|
+
| "NONE"
|
|
8
|
+
| "TRUSTED"
|
|
9
|
+
| "INFO"
|
|
10
|
+
| "SAFE"
|
|
11
|
+
| "NOTABLE"
|
|
12
|
+
| "SUSPICIOUS"
|
|
13
|
+
| "MALICIOUS";
|
|
14
|
+
/**
|
|
15
|
+
* Direction of a relationship between observables.
|
|
16
|
+
*/
|
|
17
|
+
export type RelationshipDirection = "outbound" | "inbound" | "bidirectional";
|
|
18
|
+
/**
|
|
19
|
+
* Score computation policy: 'auto' calculates from level, 'manual' uses explicit score.
|
|
20
|
+
*/
|
|
21
|
+
export type ScorePolicy = "auto" | "manual";
|
|
22
|
+
/**
|
|
23
|
+
* Score aggregation mode: 'max' takes highest score, 'sum' adds all scores.
|
|
24
|
+
*/
|
|
25
|
+
export type ScoreMode = "max" | "sum";
|
|
26
|
+
|
|
27
|
+
export interface CyvestInvestigation {
|
|
28
|
+
score: number;
|
|
29
|
+
level: Level;
|
|
30
|
+
whitelisted: boolean;
|
|
31
|
+
whitelists: Whitelist[];
|
|
32
|
+
observables: {
|
|
33
|
+
[k: string]: Observable;
|
|
34
|
+
};
|
|
35
|
+
checks: {
|
|
36
|
+
[k: string]: Check[];
|
|
37
|
+
};
|
|
38
|
+
checks_by_level: {
|
|
39
|
+
[k: string]: string[];
|
|
40
|
+
};
|
|
41
|
+
threat_intels: {
|
|
42
|
+
[k: string]: ThreatIntel;
|
|
43
|
+
};
|
|
44
|
+
enrichments: {
|
|
45
|
+
[k: string]: Enrichment;
|
|
46
|
+
};
|
|
47
|
+
containers: {
|
|
48
|
+
[k: string]: Container;
|
|
49
|
+
};
|
|
50
|
+
stats: Statistics;
|
|
51
|
+
stats_checks: StatsChecks;
|
|
52
|
+
data_extraction: DataExtraction;
|
|
53
|
+
}
|
|
54
|
+
export interface Whitelist {
|
|
55
|
+
identifier: string;
|
|
56
|
+
name: string;
|
|
57
|
+
justification?: string | null;
|
|
58
|
+
}
|
|
59
|
+
export interface Observable {
|
|
60
|
+
key: string;
|
|
61
|
+
/**
|
|
62
|
+
* Observable type (e.g., ipv4-addr, url). Custom values are allowed.
|
|
63
|
+
*/
|
|
64
|
+
type: string;
|
|
65
|
+
value: string;
|
|
66
|
+
internal: boolean;
|
|
67
|
+
whitelisted: boolean;
|
|
68
|
+
comment: string;
|
|
69
|
+
extra: {
|
|
70
|
+
[k: string]: unknown;
|
|
71
|
+
} | null;
|
|
72
|
+
score: number;
|
|
73
|
+
level: Level;
|
|
74
|
+
relationships: Relationship[];
|
|
75
|
+
threat_intels: string[];
|
|
76
|
+
generated_by_checks: string[];
|
|
77
|
+
}
|
|
78
|
+
export interface Relationship {
|
|
79
|
+
target_key: string;
|
|
80
|
+
/**
|
|
81
|
+
* Relationship label; defaults to related-to.
|
|
82
|
+
*/
|
|
83
|
+
relationship_type: string;
|
|
84
|
+
direction: RelationshipDirection;
|
|
85
|
+
}
|
|
86
|
+
export interface Check {
|
|
87
|
+
key: string;
|
|
88
|
+
check_id: string;
|
|
89
|
+
scope: string;
|
|
90
|
+
description: string;
|
|
91
|
+
comment: string;
|
|
92
|
+
extra: {
|
|
93
|
+
[k: string]: unknown;
|
|
94
|
+
} | null;
|
|
95
|
+
score: number;
|
|
96
|
+
level: Level;
|
|
97
|
+
score_policy: ScorePolicy;
|
|
98
|
+
observables: string[];
|
|
99
|
+
}
|
|
100
|
+
export interface ThreatIntel {
|
|
101
|
+
key: string;
|
|
102
|
+
source: string;
|
|
103
|
+
observable_key: string;
|
|
104
|
+
comment: string;
|
|
105
|
+
extra: {
|
|
106
|
+
[k: string]: unknown;
|
|
107
|
+
} | null;
|
|
108
|
+
score: number;
|
|
109
|
+
level: Level;
|
|
110
|
+
taxonomies: {
|
|
111
|
+
[k: string]: unknown;
|
|
112
|
+
}[];
|
|
113
|
+
}
|
|
114
|
+
export interface Enrichment {
|
|
115
|
+
key: string;
|
|
116
|
+
name: string;
|
|
117
|
+
data: {
|
|
118
|
+
[k: string]: unknown;
|
|
119
|
+
};
|
|
120
|
+
context: string;
|
|
121
|
+
}
|
|
122
|
+
export interface Container {
|
|
123
|
+
key: string;
|
|
124
|
+
path: string;
|
|
125
|
+
description: string;
|
|
126
|
+
checks: string[];
|
|
127
|
+
sub_containers: {
|
|
128
|
+
[k: string]: Container;
|
|
129
|
+
};
|
|
130
|
+
aggregated_score: number;
|
|
131
|
+
aggregated_level: Level;
|
|
132
|
+
}
|
|
133
|
+
export interface Statistics {
|
|
134
|
+
total_observables: number;
|
|
135
|
+
internal_observables: number;
|
|
136
|
+
external_observables: number;
|
|
137
|
+
whitelisted_observables: number;
|
|
138
|
+
observables_by_type: {
|
|
139
|
+
[k: string]: number;
|
|
140
|
+
};
|
|
141
|
+
observables_by_level: {
|
|
142
|
+
[k: string]: number;
|
|
143
|
+
};
|
|
144
|
+
observables_by_type_and_level: {
|
|
145
|
+
[k: string]: {
|
|
146
|
+
[k: string]: number;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
total_checks: number;
|
|
150
|
+
applied_checks: number;
|
|
151
|
+
checks_by_scope: {
|
|
152
|
+
[k: string]: number;
|
|
153
|
+
};
|
|
154
|
+
checks_by_level: {
|
|
155
|
+
[k: string]: number;
|
|
156
|
+
};
|
|
157
|
+
total_threat_intel: number;
|
|
158
|
+
threat_intel_by_source: {
|
|
159
|
+
[k: string]: number;
|
|
160
|
+
};
|
|
161
|
+
threat_intel_by_level: {
|
|
162
|
+
[k: string]: number;
|
|
163
|
+
};
|
|
164
|
+
total_containers: number;
|
|
165
|
+
}
|
|
166
|
+
export interface StatsChecks {
|
|
167
|
+
checks: number;
|
|
168
|
+
applied: number;
|
|
169
|
+
}
|
|
170
|
+
export interface DataExtraction {
|
|
171
|
+
/**
|
|
172
|
+
* Root observable type used during data extraction.
|
|
173
|
+
*/
|
|
174
|
+
root_type: string | null;
|
|
175
|
+
score_mode: ScoreMode;
|
|
176
|
+
}
|