@cyvest/cyvest-js 4.4.1 → 5.0.2
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 +5 -4
- package/dist/index.cjs +248 -333
- package/dist/index.d.cts +255 -121
- package/dist/index.d.ts +255 -121
- package/dist/index.js +219 -312
- package/package.json +1 -1
- package/src/finders.ts +101 -186
- package/src/getters.ts +176 -104
- package/src/graph.ts +4 -4
- package/src/keys.ts +84 -30
- package/src/levels.ts +7 -7
- package/src/types.generated.ts +25 -24
- package/tests/getters-finders.test.ts +225 -126
- package/tests/graph.test.ts +6 -7
- package/tests/keys-levels.test.ts +14 -15
package/dist/index.cjs
CHANGED
|
@@ -36,85 +36,93 @@ __export(index_exports, {
|
|
|
36
36
|
areConnected: () => areConnected,
|
|
37
37
|
compareLevels: () => compareLevels,
|
|
38
38
|
countRelationshipsByType: () => countRelationshipsByType,
|
|
39
|
+
findCheckByName: () => findCheckByName,
|
|
39
40
|
findChecksAtLeast: () => findChecksAtLeast,
|
|
40
|
-
findChecksByCheckId: () => findChecksByCheckId,
|
|
41
41
|
findChecksByLevel: () => findChecksByLevel,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
findContainersByLevel: () => findContainersByLevel,
|
|
42
|
+
findChecksForObservable: () => findChecksForObservable,
|
|
43
|
+
findChecksForTag: () => findChecksForTag,
|
|
45
44
|
findExternalObservables: () => findExternalObservables,
|
|
45
|
+
findHighestScoringChecks: () => findHighestScoringChecks,
|
|
46
|
+
findHighestScoringObservables: () => findHighestScoringObservables,
|
|
46
47
|
findInternalObservables: () => findInternalObservables,
|
|
47
48
|
findLeafObservables: () => findLeafObservables,
|
|
49
|
+
findMaliciousChecks: () => findMaliciousChecks,
|
|
50
|
+
findMaliciousObservables: () => findMaliciousObservables,
|
|
48
51
|
findObservablesAtLeast: () => findObservablesAtLeast,
|
|
49
52
|
findObservablesByLevel: () => findObservablesByLevel,
|
|
50
53
|
findObservablesByType: () => findObservablesByType,
|
|
51
54
|
findObservablesByValue: () => findObservablesByValue,
|
|
52
55
|
findObservablesContaining: () => findObservablesContaining,
|
|
56
|
+
findObservablesForCheck: () => findObservablesForCheck,
|
|
53
57
|
findObservablesMatching: () => findObservablesMatching,
|
|
54
58
|
findObservablesWithThreatIntel: () => findObservablesWithThreatIntel,
|
|
55
59
|
findOrphanObservables: () => findOrphanObservables,
|
|
56
60
|
findPath: () => findPath,
|
|
57
|
-
|
|
61
|
+
findSourceObservables: () => findSourceObservables,
|
|
62
|
+
findSuspiciousChecks: () => findSuspiciousChecks,
|
|
63
|
+
findSuspiciousObservables: () => findSuspiciousObservables,
|
|
64
|
+
findTagsAtLeast: () => findTagsAtLeast,
|
|
65
|
+
findTagsByLevel: () => findTagsByLevel,
|
|
66
|
+
findTagsByNamePattern: () => findTagsByNamePattern,
|
|
58
67
|
findThreatIntelAtLeast: () => findThreatIntelAtLeast,
|
|
59
68
|
findThreatIntelByLevel: () => findThreatIntelByLevel,
|
|
60
69
|
findThreatIntelBySource: () => findThreatIntelBySource,
|
|
70
|
+
findThreatIntelsForObservable: () => findThreatIntelsForObservable,
|
|
61
71
|
findWhitelistedObservables: () => findWhitelistedObservables,
|
|
62
72
|
generateCheckKey: () => generateCheckKey,
|
|
63
|
-
generateContainerKey: () => generateContainerKey,
|
|
64
73
|
generateEnrichmentKey: () => generateEnrichmentKey,
|
|
65
74
|
generateObservableKey: () => generateObservableKey,
|
|
75
|
+
generateTagKey: () => generateTagKey,
|
|
66
76
|
generateThreatIntelKey: () => generateThreatIntelKey,
|
|
77
|
+
getAllCheckKeys: () => getAllCheckKeys,
|
|
67
78
|
getAllChecks: () => getAllChecks,
|
|
68
|
-
getAllContainers: () => getAllContainers,
|
|
69
79
|
getAllEnrichments: () => getAllEnrichments,
|
|
70
80
|
getAllObservableTypes: () => getAllObservableTypes,
|
|
71
81
|
getAllObservables: () => getAllObservables,
|
|
72
82
|
getAllRelationshipTypes: () => getAllRelationshipTypes,
|
|
73
|
-
|
|
83
|
+
getAllTags: () => getAllTags,
|
|
74
84
|
getAllThreatIntelSources: () => getAllThreatIntelSources,
|
|
75
85
|
getAllThreatIntels: () => getAllThreatIntels,
|
|
76
86
|
getCheck: () => getCheck,
|
|
77
|
-
|
|
78
|
-
getChecksForContainer: () => getChecksForContainer,
|
|
79
|
-
getChecksForObservable: () => getChecksForObservable,
|
|
87
|
+
getCheckByName: () => getCheckByName,
|
|
80
88
|
getColorForLevel: () => getColorForLevel,
|
|
81
89
|
getColorForScore: () => getColorForScore,
|
|
82
|
-
getContainer: () => getContainer,
|
|
83
|
-
getContainerByPath: () => getContainerByPath,
|
|
84
90
|
getCounts: () => getCounts,
|
|
85
91
|
getDataExtraction: () => getDataExtraction,
|
|
86
92
|
getEnrichment: () => getEnrichment,
|
|
87
93
|
getEnrichmentByName: () => getEnrichmentByName,
|
|
88
94
|
getEntityLevel: () => getEntityLevel,
|
|
89
|
-
getHighestScoringChecks: () => getHighestScoringChecks,
|
|
90
|
-
getHighestScoringObservables: () => getHighestScoringObservables,
|
|
91
95
|
getLevelFromScore: () => getLevelFromScore,
|
|
92
|
-
getMaliciousChecks: () => getMaliciousChecks,
|
|
93
|
-
getMaliciousObservables: () => getMaliciousObservables,
|
|
94
96
|
getObservable: () => getObservable,
|
|
95
97
|
getObservableByTypeValue: () => getObservableByTypeValue,
|
|
96
98
|
getObservableChildren: () => getObservableChildren,
|
|
97
99
|
getObservableGraph: () => getObservableGraph,
|
|
98
100
|
getObservableParents: () => getObservableParents,
|
|
99
|
-
getObservablesForCheck: () => getObservablesForCheck,
|
|
100
101
|
getReachableObservables: () => getReachableObservables,
|
|
101
102
|
getRelatedObservables: () => getRelatedObservables,
|
|
102
103
|
getRelatedObservablesByDirection: () => getRelatedObservablesByDirection,
|
|
103
104
|
getRelatedObservablesByType: () => getRelatedObservablesByType,
|
|
104
105
|
getRelationshipsForObservable: () => getRelationshipsForObservable,
|
|
106
|
+
getRootObservable: () => getRootObservable,
|
|
105
107
|
getStartedAt: () => getStartedAt,
|
|
106
108
|
getStats: () => getStats,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
getTag: () => getTag,
|
|
110
|
+
getTagAggregatedLevel: () => getTagAggregatedLevel,
|
|
111
|
+
getTagAggregatedScore: () => getTagAggregatedScore,
|
|
112
|
+
getTagAncestors: () => getTagAncestors,
|
|
113
|
+
getTagByName: () => getTagByName,
|
|
114
|
+
getTagChildren: () => getTagChildren,
|
|
115
|
+
getTagDescendants: () => getTagDescendants,
|
|
109
116
|
getThreatIntel: () => getThreatIntel,
|
|
110
117
|
getThreatIntelBySourceObservable: () => getThreatIntelBySourceObservable,
|
|
111
|
-
getThreatIntelsForObservable: () => getThreatIntelsForObservable,
|
|
112
118
|
getWhitelists: () => getWhitelists,
|
|
113
119
|
hasLevel: () => hasLevel,
|
|
114
120
|
isCyvest: () => isCyvest,
|
|
115
121
|
isLevelAtLeast: () => isLevelAtLeast,
|
|
116
122
|
isLevelHigherThan: () => isLevelHigherThan,
|
|
117
123
|
isLevelLowerThan: () => isLevelLowerThan,
|
|
124
|
+
isTagChildOf: () => isTagChildOf,
|
|
125
|
+
isTagDescendantOf: () => isTagDescendantOf,
|
|
118
126
|
isValidLevel: () => isValidLevel,
|
|
119
127
|
maxLevel: () => maxLevel,
|
|
120
128
|
minLevel: () => minLevel,
|
|
@@ -233,12 +241,8 @@ var cyvest_schema_default = {
|
|
|
233
241
|
Check: {
|
|
234
242
|
description: "Represents a verification step in the investigation.\n\nA check validates a specific aspect of the data under investigation\nand contributes to the overall investigation score.",
|
|
235
243
|
properties: {
|
|
236
|
-
|
|
237
|
-
title: "Check
|
|
238
|
-
type: "string"
|
|
239
|
-
},
|
|
240
|
-
scope: {
|
|
241
|
-
title: "Scope",
|
|
244
|
+
check_name: {
|
|
245
|
+
title: "Check Name",
|
|
242
246
|
type: "string"
|
|
243
247
|
},
|
|
244
248
|
description: {
|
|
@@ -283,8 +287,7 @@ var cyvest_schema_default = {
|
|
|
283
287
|
}
|
|
284
288
|
},
|
|
285
289
|
required: [
|
|
286
|
-
"
|
|
287
|
-
"scope",
|
|
290
|
+
"check_name",
|
|
288
291
|
"description",
|
|
289
292
|
"comment",
|
|
290
293
|
"extra",
|
|
@@ -298,59 +301,6 @@ var cyvest_schema_default = {
|
|
|
298
301
|
title: "Check",
|
|
299
302
|
type: "object"
|
|
300
303
|
},
|
|
301
|
-
Container: {
|
|
302
|
-
additionalProperties: false,
|
|
303
|
-
description: "Groups checks and sub-containers for hierarchical organization.\n\nContainers allow structuring the investigation into logical sections\nwith aggregated scores and levels.",
|
|
304
|
-
properties: {
|
|
305
|
-
path: {
|
|
306
|
-
title: "Path",
|
|
307
|
-
type: "string"
|
|
308
|
-
},
|
|
309
|
-
description: {
|
|
310
|
-
default: "",
|
|
311
|
-
title: "Description",
|
|
312
|
-
type: "string"
|
|
313
|
-
},
|
|
314
|
-
checks: {
|
|
315
|
-
items: {
|
|
316
|
-
type: "string"
|
|
317
|
-
},
|
|
318
|
-
title: "Checks",
|
|
319
|
-
type: "array"
|
|
320
|
-
},
|
|
321
|
-
sub_containers: {
|
|
322
|
-
additionalProperties: {
|
|
323
|
-
$ref: "#/$defs/Container"
|
|
324
|
-
},
|
|
325
|
-
title: "Sub Containers",
|
|
326
|
-
type: "object"
|
|
327
|
-
},
|
|
328
|
-
key: {
|
|
329
|
-
title: "Key",
|
|
330
|
-
type: "string"
|
|
331
|
-
},
|
|
332
|
-
aggregated_score: {
|
|
333
|
-
readOnly: true,
|
|
334
|
-
title: "Aggregated Score",
|
|
335
|
-
type: "number"
|
|
336
|
-
},
|
|
337
|
-
aggregated_level: {
|
|
338
|
-
$ref: "#/$defs/Level",
|
|
339
|
-
description: "Calculate the aggregated level from the aggregated score.\n\nReturns:\n Level based on aggregated score",
|
|
340
|
-
readOnly: true
|
|
341
|
-
}
|
|
342
|
-
},
|
|
343
|
-
required: [
|
|
344
|
-
"path",
|
|
345
|
-
"checks",
|
|
346
|
-
"sub_containers",
|
|
347
|
-
"key",
|
|
348
|
-
"aggregated_score",
|
|
349
|
-
"aggregated_level"
|
|
350
|
-
],
|
|
351
|
-
title: "Container",
|
|
352
|
-
type: "object"
|
|
353
|
-
},
|
|
354
304
|
DataExtractionSchema: {
|
|
355
305
|
additionalProperties: false,
|
|
356
306
|
description: "Schema for data extraction metadata.",
|
|
@@ -675,16 +625,6 @@ var cyvest_schema_default = {
|
|
|
675
625
|
title: "Applied Checks",
|
|
676
626
|
type: "integer"
|
|
677
627
|
},
|
|
678
|
-
checks_by_scope: {
|
|
679
|
-
additionalProperties: {
|
|
680
|
-
items: {
|
|
681
|
-
type: "string"
|
|
682
|
-
},
|
|
683
|
-
type: "array"
|
|
684
|
-
},
|
|
685
|
-
title: "Checks By Scope",
|
|
686
|
-
type: "object"
|
|
687
|
-
},
|
|
688
628
|
checks_by_level: {
|
|
689
629
|
additionalProperties: {
|
|
690
630
|
items: {
|
|
@@ -716,9 +656,9 @@ var cyvest_schema_default = {
|
|
|
716
656
|
title: "Threat Intel By Level",
|
|
717
657
|
type: "object"
|
|
718
658
|
},
|
|
719
|
-
|
|
659
|
+
total_tags: {
|
|
720
660
|
minimum: 0,
|
|
721
|
-
title: "Total
|
|
661
|
+
title: "Total Tags",
|
|
722
662
|
type: "integer"
|
|
723
663
|
}
|
|
724
664
|
},
|
|
@@ -730,11 +670,57 @@ var cyvest_schema_default = {
|
|
|
730
670
|
"total_checks",
|
|
731
671
|
"applied_checks",
|
|
732
672
|
"total_threat_intel",
|
|
733
|
-
"
|
|
673
|
+
"total_tags"
|
|
734
674
|
],
|
|
735
675
|
title: "StatisticsSchema",
|
|
736
676
|
type: "object"
|
|
737
677
|
},
|
|
678
|
+
Tag: {
|
|
679
|
+
additionalProperties: false,
|
|
680
|
+
description: 'Groups checks for categorical organization.\n\nTags allow structuring the investigation into logical sections\nwith aggregated scores and levels. Hierarchy is automatic based on\nthe ":" delimiter in tag names (e.g., "header:auth:dkim").',
|
|
681
|
+
properties: {
|
|
682
|
+
name: {
|
|
683
|
+
title: "Name",
|
|
684
|
+
type: "string"
|
|
685
|
+
},
|
|
686
|
+
description: {
|
|
687
|
+
default: "",
|
|
688
|
+
title: "Description",
|
|
689
|
+
type: "string"
|
|
690
|
+
},
|
|
691
|
+
checks: {
|
|
692
|
+
items: {
|
|
693
|
+
type: "string"
|
|
694
|
+
},
|
|
695
|
+
title: "Checks",
|
|
696
|
+
type: "array"
|
|
697
|
+
},
|
|
698
|
+
key: {
|
|
699
|
+
title: "Key",
|
|
700
|
+
type: "string"
|
|
701
|
+
},
|
|
702
|
+
direct_score: {
|
|
703
|
+
description: "Calculate the score from direct checks only (no hierarchy).\n\nFor hierarchical aggregation (including descendant tags), use\nInvestigation.get_tag_aggregated_score() or TagProxy.get_aggregated_score().\n\nReturns:\n Total score from direct checks",
|
|
704
|
+
readOnly: true,
|
|
705
|
+
title: "Direct Score",
|
|
706
|
+
type: "number"
|
|
707
|
+
},
|
|
708
|
+
direct_level: {
|
|
709
|
+
$ref: "#/$defs/Level",
|
|
710
|
+
description: "Calculate the level from direct checks only (no hierarchy).\n\nFor hierarchical aggregation (including descendant tags), use\nInvestigation.get_tag_aggregated_level() or TagProxy.get_aggregated_level().\n\nReturns:\n Level based on direct score",
|
|
711
|
+
readOnly: true
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
required: [
|
|
715
|
+
"name",
|
|
716
|
+
"checks",
|
|
717
|
+
"key",
|
|
718
|
+
"direct_score",
|
|
719
|
+
"direct_level"
|
|
720
|
+
],
|
|
721
|
+
title: "Tag",
|
|
722
|
+
type: "object"
|
|
723
|
+
},
|
|
738
724
|
Taxonomy: {
|
|
739
725
|
additionalProperties: false,
|
|
740
726
|
description: "Represents a structured taxonomy entry for threat intelligence.",
|
|
@@ -888,12 +874,9 @@ var cyvest_schema_default = {
|
|
|
888
874
|
},
|
|
889
875
|
checks: {
|
|
890
876
|
additionalProperties: {
|
|
891
|
-
|
|
892
|
-
$ref: "#/$defs/Check"
|
|
893
|
-
},
|
|
894
|
-
type: "array"
|
|
877
|
+
$ref: "#/$defs/Check"
|
|
895
878
|
},
|
|
896
|
-
description: "Checks
|
|
879
|
+
description: "Checks keyed by their unique key.",
|
|
897
880
|
title: "Checks",
|
|
898
881
|
type: "object"
|
|
899
882
|
},
|
|
@@ -913,12 +896,12 @@ var cyvest_schema_default = {
|
|
|
913
896
|
title: "Enrichments",
|
|
914
897
|
type: "object"
|
|
915
898
|
},
|
|
916
|
-
|
|
899
|
+
tags: {
|
|
917
900
|
additionalProperties: {
|
|
918
|
-
$ref: "#/$defs/
|
|
901
|
+
$ref: "#/$defs/Tag"
|
|
919
902
|
},
|
|
920
|
-
description: "
|
|
921
|
-
title: "
|
|
903
|
+
description: "Tags keyed by their unique key.",
|
|
904
|
+
title: "Tags",
|
|
922
905
|
type: "object"
|
|
923
906
|
},
|
|
924
907
|
stats: {
|
|
@@ -946,7 +929,7 @@ var cyvest_schema_default = {
|
|
|
946
929
|
"checks",
|
|
947
930
|
"threat_intels",
|
|
948
931
|
"enrichments",
|
|
949
|
-
"
|
|
932
|
+
"tags",
|
|
950
933
|
"stats",
|
|
951
934
|
"data_extraction",
|
|
952
935
|
"score_display"
|
|
@@ -996,10 +979,9 @@ function generateObservableKey(obsType, value) {
|
|
|
996
979
|
const normalizedValue = normalizeValue(value);
|
|
997
980
|
return `obs:${normalizedType}:${normalizedValue}`;
|
|
998
981
|
}
|
|
999
|
-
function generateCheckKey(
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
return `chk:${normalizedId}:${normalizedScope}`;
|
|
982
|
+
function generateCheckKey(checkName) {
|
|
983
|
+
const normalizedName = normalizeValue(checkName);
|
|
984
|
+
return `chk:${normalizedName}`;
|
|
1003
985
|
}
|
|
1004
986
|
function generateThreatIntelKey(source, observableKey) {
|
|
1005
987
|
const normalizedSource = normalizeValue(source);
|
|
@@ -1013,15 +995,32 @@ function generateEnrichmentKey(name, context) {
|
|
|
1013
995
|
}
|
|
1014
996
|
return `enr:${normalizedName}`;
|
|
1015
997
|
}
|
|
1016
|
-
function
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
998
|
+
function generateTagKey(name) {
|
|
999
|
+
const normalizedName = normalizeValue(name);
|
|
1000
|
+
return `tag:${normalizedName}`;
|
|
1001
|
+
}
|
|
1002
|
+
function getTagAncestors(name) {
|
|
1003
|
+
const parts = name.split(":");
|
|
1004
|
+
const ancestors = [];
|
|
1005
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1006
|
+
ancestors.push(parts.slice(0, i + 1).join(":"));
|
|
1007
|
+
}
|
|
1008
|
+
return ancestors;
|
|
1009
|
+
}
|
|
1010
|
+
function isTagChildOf(childName, parentName) {
|
|
1011
|
+
if (!childName.startsWith(parentName + ":")) {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
const remaining = childName.slice(parentName.length + 1);
|
|
1015
|
+
return !remaining.includes(":");
|
|
1016
|
+
}
|
|
1017
|
+
function isTagDescendantOf(descendantName, ancestorName) {
|
|
1018
|
+
return descendantName.startsWith(ancestorName + ":");
|
|
1020
1019
|
}
|
|
1021
1020
|
function parseKeyType(key) {
|
|
1022
1021
|
if (key.includes(":")) {
|
|
1023
1022
|
const prefix = key.split(":", 1)[0];
|
|
1024
|
-
if (["obs", "chk", "ti", "enr", "
|
|
1023
|
+
if (["obs", "chk", "ti", "enr", "tag"].includes(prefix)) {
|
|
1025
1024
|
return prefix;
|
|
1026
1025
|
}
|
|
1027
1026
|
}
|
|
@@ -1059,10 +1058,9 @@ function parseCheckKey(key) {
|
|
|
1059
1058
|
return null;
|
|
1060
1059
|
}
|
|
1061
1060
|
const parts = key.split(":");
|
|
1062
|
-
if (parts.length >=
|
|
1061
|
+
if (parts.length >= 2) {
|
|
1063
1062
|
return {
|
|
1064
|
-
|
|
1065
|
-
scope: parts.slice(2).join(":")
|
|
1063
|
+
checkName: parts.slice(1).join(":")
|
|
1066
1064
|
};
|
|
1067
1065
|
}
|
|
1068
1066
|
return null;
|
|
@@ -1179,10 +1177,10 @@ function hasLevel(obj) {
|
|
|
1179
1177
|
return typeof obj === "object" && obj !== null && "level" in obj && typeof obj.level === "string" && isValidLevel(obj.level);
|
|
1180
1178
|
}
|
|
1181
1179
|
function getEntityLevel(entity) {
|
|
1182
|
-
if ("
|
|
1183
|
-
const
|
|
1184
|
-
if (typeof
|
|
1185
|
-
return
|
|
1180
|
+
if ("direct_level" in entity) {
|
|
1181
|
+
const directLevel = entity.direct_level;
|
|
1182
|
+
if (typeof directLevel === "string" && isValidLevel(directLevel)) {
|
|
1183
|
+
return directLevel;
|
|
1186
1184
|
}
|
|
1187
1185
|
}
|
|
1188
1186
|
if ("level" in entity && isValidLevel(entity.level)) {
|
|
@@ -1205,40 +1203,24 @@ function getObservableByTypeValue(inv, type, value) {
|
|
|
1205
1203
|
}
|
|
1206
1204
|
return void 0;
|
|
1207
1205
|
}
|
|
1208
|
-
function
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
return check;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1206
|
+
function getRootObservable(inv) {
|
|
1207
|
+
const rootType = inv.data_extraction.root_type;
|
|
1208
|
+
if (!rootType) {
|
|
1209
|
+
return void 0;
|
|
1215
1210
|
}
|
|
1216
|
-
|
|
1211
|
+
const rootKey = generateObservableKey(rootType, "root");
|
|
1212
|
+
return inv.observables[rootKey];
|
|
1217
1213
|
}
|
|
1218
|
-
function
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
);
|
|
1226
|
-
}
|
|
1227
|
-
for (const checks of Object.values(inv.checks)) {
|
|
1228
|
-
for (const check of checks) {
|
|
1229
|
-
if (check.check_id.toLowerCase() === normalizedId && check.scope.toLowerCase() === normalizedScope) {
|
|
1230
|
-
return check;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
return void 0;
|
|
1214
|
+
function getCheck(inv, key) {
|
|
1215
|
+
return inv.checks[key];
|
|
1216
|
+
}
|
|
1217
|
+
function getCheckByName(inv, checkName) {
|
|
1218
|
+
const normalizedName = checkName.trim().toLowerCase();
|
|
1219
|
+
const key = `chk:${normalizedName}`;
|
|
1220
|
+
return inv.checks[key];
|
|
1235
1221
|
}
|
|
1236
1222
|
function getAllChecks(inv) {
|
|
1237
|
-
|
|
1238
|
-
for (const checks of Object.values(inv.checks)) {
|
|
1239
|
-
result.push(...checks);
|
|
1240
|
-
}
|
|
1241
|
-
return result;
|
|
1223
|
+
return Object.values(inv.checks);
|
|
1242
1224
|
}
|
|
1243
1225
|
function getThreatIntel(inv, key) {
|
|
1244
1226
|
return inv.threat_intels[key];
|
|
@@ -1270,46 +1252,16 @@ function getEnrichmentByName(inv, name) {
|
|
|
1270
1252
|
function getAllEnrichments(inv) {
|
|
1271
1253
|
return Object.values(inv.enrichments);
|
|
1272
1254
|
}
|
|
1273
|
-
function
|
|
1274
|
-
|
|
1275
|
-
return inv.containers[key];
|
|
1276
|
-
}
|
|
1277
|
-
function searchSubContainers(containers) {
|
|
1278
|
-
for (const container of Object.values(containers)) {
|
|
1279
|
-
if (container.key === key) {
|
|
1280
|
-
return container;
|
|
1281
|
-
}
|
|
1282
|
-
const found = searchSubContainers(container.sub_containers);
|
|
1283
|
-
if (found) return found;
|
|
1284
|
-
}
|
|
1285
|
-
return void 0;
|
|
1286
|
-
}
|
|
1287
|
-
return searchSubContainers(inv.containers);
|
|
1288
|
-
}
|
|
1289
|
-
function getContainerByPath(inv, path) {
|
|
1290
|
-
const normalizedPath = path.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
1291
|
-
function searchContainers(containers) {
|
|
1292
|
-
for (const container of Object.values(containers)) {
|
|
1293
|
-
if (container.path.toLowerCase() === normalizedPath) {
|
|
1294
|
-
return container;
|
|
1295
|
-
}
|
|
1296
|
-
const found = searchContainers(container.sub_containers);
|
|
1297
|
-
if (found) return found;
|
|
1298
|
-
}
|
|
1299
|
-
return void 0;
|
|
1300
|
-
}
|
|
1301
|
-
return searchContainers(inv.containers);
|
|
1255
|
+
function getTag(inv, key) {
|
|
1256
|
+
return inv.tags[key];
|
|
1302
1257
|
}
|
|
1303
|
-
function
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
1311
|
-
collectContainers(inv.containers);
|
|
1312
|
-
return result;
|
|
1258
|
+
function getTagByName(inv, name) {
|
|
1259
|
+
const normalizedName = name.trim().toLowerCase();
|
|
1260
|
+
const key = `tag:${normalizedName}`;
|
|
1261
|
+
return inv.tags[key];
|
|
1262
|
+
}
|
|
1263
|
+
function getAllTags(inv) {
|
|
1264
|
+
return Object.values(inv.tags);
|
|
1313
1265
|
}
|
|
1314
1266
|
function getAllObservables(inv) {
|
|
1315
1267
|
return Object.values(inv.observables);
|
|
@@ -1329,7 +1281,7 @@ function getCounts(inv) {
|
|
|
1329
1281
|
checks: getAllChecks(inv).length,
|
|
1330
1282
|
threatIntels: Object.keys(inv.threat_intels).length,
|
|
1331
1283
|
enrichments: Object.keys(inv.enrichments).length,
|
|
1332
|
-
|
|
1284
|
+
tags: getAllTags(inv).length,
|
|
1333
1285
|
whitelists: inv.whitelists.length
|
|
1334
1286
|
};
|
|
1335
1287
|
}
|
|
@@ -1339,6 +1291,28 @@ function getStartedAt(inv) {
|
|
|
1339
1291
|
);
|
|
1340
1292
|
return event?.timestamp;
|
|
1341
1293
|
}
|
|
1294
|
+
function getTagChildren(inv, tagName) {
|
|
1295
|
+
return Object.values(inv.tags).filter((tag) => isTagChildOf(tag.name, tagName));
|
|
1296
|
+
}
|
|
1297
|
+
function getTagDescendants(inv, tagName) {
|
|
1298
|
+
const prefix = tagName + ":";
|
|
1299
|
+
return Object.values(inv.tags).filter((tag) => tag.name.startsWith(prefix));
|
|
1300
|
+
}
|
|
1301
|
+
function getTagAggregatedScore(inv, tagName) {
|
|
1302
|
+
const tag = getTagByName(inv, tagName);
|
|
1303
|
+
if (!tag) {
|
|
1304
|
+
return 0;
|
|
1305
|
+
}
|
|
1306
|
+
let total = tag.direct_score;
|
|
1307
|
+
const children = getTagChildren(inv, tagName);
|
|
1308
|
+
for (const child of children) {
|
|
1309
|
+
total += getTagAggregatedScore(inv, child.name);
|
|
1310
|
+
}
|
|
1311
|
+
return total;
|
|
1312
|
+
}
|
|
1313
|
+
function getTagAggregatedLevel(inv, tagName) {
|
|
1314
|
+
return getLevelFromScore(getTagAggregatedScore(inv, tagName));
|
|
1315
|
+
}
|
|
1342
1316
|
|
|
1343
1317
|
// src/finders.ts
|
|
1344
1318
|
function findObservablesByType(inv, type) {
|
|
@@ -1386,51 +1360,19 @@ function findObservablesWithThreatIntel(inv) {
|
|
|
1386
1360
|
(obs) => obs.threat_intels.length > 0
|
|
1387
1361
|
);
|
|
1388
1362
|
}
|
|
1389
|
-
function findChecksByScope(inv, scope) {
|
|
1390
|
-
const normalizedScope = scope.trim().toLowerCase();
|
|
1391
|
-
if (inv.checks[scope]) {
|
|
1392
|
-
return inv.checks[scope];
|
|
1393
|
-
}
|
|
1394
|
-
for (const [key, checks] of Object.entries(inv.checks)) {
|
|
1395
|
-
if (key.toLowerCase() === normalizedScope) {
|
|
1396
|
-
return checks;
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
return [];
|
|
1400
|
-
}
|
|
1401
1363
|
function findChecksByLevel(inv, level) {
|
|
1402
|
-
|
|
1403
|
-
for (const checks of Object.values(inv.checks)) {
|
|
1404
|
-
for (const check of checks) {
|
|
1405
|
-
if (check.level === level) {
|
|
1406
|
-
result.push(check);
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
return result;
|
|
1364
|
+
return Object.values(inv.checks).filter((check) => check.level === level);
|
|
1411
1365
|
}
|
|
1412
1366
|
function findChecksAtLeast(inv, minLevel2) {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
if (isLevelAtLeast(check.level, minLevel2)) {
|
|
1417
|
-
result.push(check);
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
return result;
|
|
1367
|
+
return Object.values(inv.checks).filter(
|
|
1368
|
+
(check) => isLevelAtLeast(check.level, minLevel2)
|
|
1369
|
+
);
|
|
1422
1370
|
}
|
|
1423
|
-
function
|
|
1424
|
-
const
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
if (check.check_id.toLowerCase() === normalizedId) {
|
|
1429
|
-
result.push(check);
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
return result;
|
|
1371
|
+
function findCheckByName(inv, checkName) {
|
|
1372
|
+
const normalizedName = checkName.trim().toLowerCase();
|
|
1373
|
+
return Object.values(inv.checks).find(
|
|
1374
|
+
(check) => check.check_name.toLowerCase() === normalizedName
|
|
1375
|
+
);
|
|
1434
1376
|
}
|
|
1435
1377
|
function findThreatIntelBySource(inv, source) {
|
|
1436
1378
|
const normalizedSource = source.trim().toLowerCase();
|
|
@@ -1446,52 +1388,31 @@ function findThreatIntelAtLeast(inv, minLevel2) {
|
|
|
1446
1388
|
(ti) => isLevelAtLeast(ti.level, minLevel2)
|
|
1447
1389
|
);
|
|
1448
1390
|
}
|
|
1449
|
-
function
|
|
1450
|
-
|
|
1451
|
-
function searchContainers(containers) {
|
|
1452
|
-
for (const container of Object.values(containers)) {
|
|
1453
|
-
if (container.aggregated_level === level) {
|
|
1454
|
-
result.push(container);
|
|
1455
|
-
}
|
|
1456
|
-
searchContainers(container.sub_containers);
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
searchContainers(inv.containers);
|
|
1460
|
-
return result;
|
|
1391
|
+
function findTagsByLevel(inv, level) {
|
|
1392
|
+
return Object.values(inv.tags).filter((tag) => tag.direct_level === level);
|
|
1461
1393
|
}
|
|
1462
|
-
function
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
if (isLevelAtLeast(container.aggregated_level, minLevel2)) {
|
|
1467
|
-
result.push(container);
|
|
1468
|
-
}
|
|
1469
|
-
searchContainers(container.sub_containers);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
searchContainers(inv.containers);
|
|
1473
|
-
return result;
|
|
1394
|
+
function findTagsAtLeast(inv, minLevel2) {
|
|
1395
|
+
return Object.values(inv.tags).filter(
|
|
1396
|
+
(tag) => isLevelAtLeast(tag.direct_level, minLevel2)
|
|
1397
|
+
);
|
|
1474
1398
|
}
|
|
1475
|
-
function
|
|
1399
|
+
function findTagsByNamePattern(inv, pattern) {
|
|
1400
|
+
return Object.values(inv.tags).filter((tag) => pattern.test(tag.name));
|
|
1401
|
+
}
|
|
1402
|
+
function findChecksForObservable(inv, observableKey) {
|
|
1476
1403
|
const result = [];
|
|
1477
1404
|
const seen = /* @__PURE__ */ new Set();
|
|
1478
|
-
const checkLookup = /* @__PURE__ */ new Map();
|
|
1479
|
-
for (const checks of Object.values(inv.checks)) {
|
|
1480
|
-
for (const check of checks) {
|
|
1481
|
-
checkLookup.set(check.key, check);
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
1405
|
const observable = inv.observables[observableKey];
|
|
1485
1406
|
if (observable) {
|
|
1486
1407
|
for (const checkKey of observable.check_links) {
|
|
1487
|
-
const check =
|
|
1408
|
+
const check = inv.checks[checkKey];
|
|
1488
1409
|
if (check && !seen.has(check.key)) {
|
|
1489
1410
|
result.push(check);
|
|
1490
1411
|
seen.add(check.key);
|
|
1491
1412
|
}
|
|
1492
1413
|
}
|
|
1493
1414
|
}
|
|
1494
|
-
for (const check of
|
|
1415
|
+
for (const check of Object.values(inv.checks)) {
|
|
1495
1416
|
if (seen.has(check.key)) {
|
|
1496
1417
|
continue;
|
|
1497
1418
|
}
|
|
@@ -1502,7 +1423,7 @@ function getChecksForObservable(inv, observableKey) {
|
|
|
1502
1423
|
}
|
|
1503
1424
|
return result;
|
|
1504
1425
|
}
|
|
1505
|
-
function
|
|
1426
|
+
function findThreatIntelsForObservable(inv, observableKey) {
|
|
1506
1427
|
const observable = inv.observables[observableKey];
|
|
1507
1428
|
if (observable) {
|
|
1508
1429
|
return observable.threat_intels.map((tiKey) => inv.threat_intels[tiKey]).filter((ti) => ti !== void 0);
|
|
@@ -1511,51 +1432,41 @@ function getThreatIntelsForObservable(inv, observableKey) {
|
|
|
1511
1432
|
(ti) => ti.observable_key === observableKey
|
|
1512
1433
|
);
|
|
1513
1434
|
}
|
|
1514
|
-
function
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
keys.add(link.observable_key);
|
|
1521
|
-
}
|
|
1522
|
-
return Array.from(keys).map((obsKey) => inv.observables[obsKey]).filter((obs) => obs !== void 0);
|
|
1523
|
-
}
|
|
1435
|
+
function findObservablesForCheck(inv, checkKey) {
|
|
1436
|
+
const check = inv.checks[checkKey];
|
|
1437
|
+
if (check) {
|
|
1438
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1439
|
+
for (const link of check.observable_links) {
|
|
1440
|
+
keys.add(link.observable_key);
|
|
1524
1441
|
}
|
|
1442
|
+
return Array.from(keys).map((obsKey) => inv.observables[obsKey]).filter((obs) => obs !== void 0);
|
|
1525
1443
|
}
|
|
1526
1444
|
return [];
|
|
1527
1445
|
}
|
|
1528
|
-
function
|
|
1446
|
+
function findChecksForTag(inv, tagKey, recursive = false) {
|
|
1529
1447
|
const result = [];
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1448
|
+
const tag = inv.tags[tagKey];
|
|
1449
|
+
if (!tag) {
|
|
1450
|
+
return result;
|
|
1451
|
+
}
|
|
1452
|
+
for (const checkKey of tag.checks) {
|
|
1453
|
+
const check = inv.checks[checkKey];
|
|
1454
|
+
if (check) {
|
|
1455
|
+
result.push(check);
|
|
1537
1456
|
}
|
|
1538
|
-
return void 0;
|
|
1539
1457
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1458
|
+
if (recursive) {
|
|
1459
|
+
const prefix = tag.name + ":";
|
|
1460
|
+
for (const otherTag of Object.values(inv.tags)) {
|
|
1461
|
+
if (otherTag.name.startsWith(prefix)) {
|
|
1462
|
+
for (const checkKey of otherTag.checks) {
|
|
1463
|
+
const check = inv.checks[checkKey];
|
|
1464
|
+
if (check) {
|
|
1545
1465
|
result.push(check);
|
|
1546
1466
|
}
|
|
1547
1467
|
}
|
|
1548
1468
|
}
|
|
1549
1469
|
}
|
|
1550
|
-
if (recursive) {
|
|
1551
|
-
for (const subContainer of Object.values(container2.sub_containers)) {
|
|
1552
|
-
collectChecks(subContainer);
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
const container = findContainer(inv.containers);
|
|
1557
|
-
if (container) {
|
|
1558
|
-
collectChecks(container);
|
|
1559
1470
|
}
|
|
1560
1471
|
return result;
|
|
1561
1472
|
}
|
|
@@ -1575,29 +1486,25 @@ function sortChecksByLevel(checks) {
|
|
|
1575
1486
|
(a, b) => LEVEL_VALUES[b.level] - LEVEL_VALUES[a.level]
|
|
1576
1487
|
);
|
|
1577
1488
|
}
|
|
1578
|
-
function
|
|
1489
|
+
function findHighestScoringObservables(inv, n = 10) {
|
|
1579
1490
|
return sortObservablesByScore(Object.values(inv.observables)).slice(0, n);
|
|
1580
1491
|
}
|
|
1581
|
-
function
|
|
1582
|
-
|
|
1583
|
-
for (const checks of Object.values(inv.checks)) {
|
|
1584
|
-
allChecks.push(...checks);
|
|
1585
|
-
}
|
|
1586
|
-
return sortChecksByScore(allChecks).slice(0, n);
|
|
1492
|
+
function findHighestScoringChecks(inv, n = 10) {
|
|
1493
|
+
return sortChecksByScore(Object.values(inv.checks)).slice(0, n);
|
|
1587
1494
|
}
|
|
1588
|
-
function
|
|
1495
|
+
function findMaliciousObservables(inv) {
|
|
1589
1496
|
return findObservablesByLevel(inv, "MALICIOUS");
|
|
1590
1497
|
}
|
|
1591
|
-
function
|
|
1498
|
+
function findSuspiciousObservables(inv) {
|
|
1592
1499
|
return findObservablesByLevel(inv, "SUSPICIOUS");
|
|
1593
1500
|
}
|
|
1594
|
-
function
|
|
1501
|
+
function findMaliciousChecks(inv) {
|
|
1595
1502
|
return findChecksByLevel(inv, "MALICIOUS");
|
|
1596
1503
|
}
|
|
1597
|
-
function
|
|
1504
|
+
function findSuspiciousChecks(inv) {
|
|
1598
1505
|
return findChecksByLevel(inv, "SUSPICIOUS");
|
|
1599
1506
|
}
|
|
1600
|
-
function
|
|
1507
|
+
function getAllCheckKeys(inv) {
|
|
1601
1508
|
return Object.keys(inv.checks);
|
|
1602
1509
|
}
|
|
1603
1510
|
function getAllObservableTypes(inv) {
|
|
@@ -1734,7 +1641,7 @@ function getObservableGraph(inv) {
|
|
|
1734
1641
|
}
|
|
1735
1642
|
return { nodes, edges };
|
|
1736
1643
|
}
|
|
1737
|
-
function
|
|
1644
|
+
function findSourceObservables(inv) {
|
|
1738
1645
|
const targetKeys = /* @__PURE__ */ new Set();
|
|
1739
1646
|
for (const obs of Object.values(inv.observables)) {
|
|
1740
1647
|
for (const rel of obs.relationships) {
|
|
@@ -1883,85 +1790,93 @@ function getRelationshipsForObservable(inv, observableKey) {
|
|
|
1883
1790
|
areConnected,
|
|
1884
1791
|
compareLevels,
|
|
1885
1792
|
countRelationshipsByType,
|
|
1793
|
+
findCheckByName,
|
|
1886
1794
|
findChecksAtLeast,
|
|
1887
|
-
findChecksByCheckId,
|
|
1888
1795
|
findChecksByLevel,
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
findContainersByLevel,
|
|
1796
|
+
findChecksForObservable,
|
|
1797
|
+
findChecksForTag,
|
|
1892
1798
|
findExternalObservables,
|
|
1799
|
+
findHighestScoringChecks,
|
|
1800
|
+
findHighestScoringObservables,
|
|
1893
1801
|
findInternalObservables,
|
|
1894
1802
|
findLeafObservables,
|
|
1803
|
+
findMaliciousChecks,
|
|
1804
|
+
findMaliciousObservables,
|
|
1895
1805
|
findObservablesAtLeast,
|
|
1896
1806
|
findObservablesByLevel,
|
|
1897
1807
|
findObservablesByType,
|
|
1898
1808
|
findObservablesByValue,
|
|
1899
1809
|
findObservablesContaining,
|
|
1810
|
+
findObservablesForCheck,
|
|
1900
1811
|
findObservablesMatching,
|
|
1901
1812
|
findObservablesWithThreatIntel,
|
|
1902
1813
|
findOrphanObservables,
|
|
1903
1814
|
findPath,
|
|
1904
|
-
|
|
1815
|
+
findSourceObservables,
|
|
1816
|
+
findSuspiciousChecks,
|
|
1817
|
+
findSuspiciousObservables,
|
|
1818
|
+
findTagsAtLeast,
|
|
1819
|
+
findTagsByLevel,
|
|
1820
|
+
findTagsByNamePattern,
|
|
1905
1821
|
findThreatIntelAtLeast,
|
|
1906
1822
|
findThreatIntelByLevel,
|
|
1907
1823
|
findThreatIntelBySource,
|
|
1824
|
+
findThreatIntelsForObservable,
|
|
1908
1825
|
findWhitelistedObservables,
|
|
1909
1826
|
generateCheckKey,
|
|
1910
|
-
generateContainerKey,
|
|
1911
1827
|
generateEnrichmentKey,
|
|
1912
1828
|
generateObservableKey,
|
|
1829
|
+
generateTagKey,
|
|
1913
1830
|
generateThreatIntelKey,
|
|
1831
|
+
getAllCheckKeys,
|
|
1914
1832
|
getAllChecks,
|
|
1915
|
-
getAllContainers,
|
|
1916
1833
|
getAllEnrichments,
|
|
1917
1834
|
getAllObservableTypes,
|
|
1918
1835
|
getAllObservables,
|
|
1919
1836
|
getAllRelationshipTypes,
|
|
1920
|
-
|
|
1837
|
+
getAllTags,
|
|
1921
1838
|
getAllThreatIntelSources,
|
|
1922
1839
|
getAllThreatIntels,
|
|
1923
1840
|
getCheck,
|
|
1924
|
-
|
|
1925
|
-
getChecksForContainer,
|
|
1926
|
-
getChecksForObservable,
|
|
1841
|
+
getCheckByName,
|
|
1927
1842
|
getColorForLevel,
|
|
1928
1843
|
getColorForScore,
|
|
1929
|
-
getContainer,
|
|
1930
|
-
getContainerByPath,
|
|
1931
1844
|
getCounts,
|
|
1932
1845
|
getDataExtraction,
|
|
1933
1846
|
getEnrichment,
|
|
1934
1847
|
getEnrichmentByName,
|
|
1935
1848
|
getEntityLevel,
|
|
1936
|
-
getHighestScoringChecks,
|
|
1937
|
-
getHighestScoringObservables,
|
|
1938
1849
|
getLevelFromScore,
|
|
1939
|
-
getMaliciousChecks,
|
|
1940
|
-
getMaliciousObservables,
|
|
1941
1850
|
getObservable,
|
|
1942
1851
|
getObservableByTypeValue,
|
|
1943
1852
|
getObservableChildren,
|
|
1944
1853
|
getObservableGraph,
|
|
1945
1854
|
getObservableParents,
|
|
1946
|
-
getObservablesForCheck,
|
|
1947
1855
|
getReachableObservables,
|
|
1948
1856
|
getRelatedObservables,
|
|
1949
1857
|
getRelatedObservablesByDirection,
|
|
1950
1858
|
getRelatedObservablesByType,
|
|
1951
1859
|
getRelationshipsForObservable,
|
|
1860
|
+
getRootObservable,
|
|
1952
1861
|
getStartedAt,
|
|
1953
1862
|
getStats,
|
|
1954
|
-
|
|
1955
|
-
|
|
1863
|
+
getTag,
|
|
1864
|
+
getTagAggregatedLevel,
|
|
1865
|
+
getTagAggregatedScore,
|
|
1866
|
+
getTagAncestors,
|
|
1867
|
+
getTagByName,
|
|
1868
|
+
getTagChildren,
|
|
1869
|
+
getTagDescendants,
|
|
1956
1870
|
getThreatIntel,
|
|
1957
1871
|
getThreatIntelBySourceObservable,
|
|
1958
|
-
getThreatIntelsForObservable,
|
|
1959
1872
|
getWhitelists,
|
|
1960
1873
|
hasLevel,
|
|
1961
1874
|
isCyvest,
|
|
1962
1875
|
isLevelAtLeast,
|
|
1963
1876
|
isLevelHigherThan,
|
|
1964
1877
|
isLevelLowerThan,
|
|
1878
|
+
isTagChildOf,
|
|
1879
|
+
isTagDescendantOf,
|
|
1965
1880
|
isValidLevel,
|
|
1966
1881
|
maxLevel,
|
|
1967
1882
|
minLevel,
|