synapse 2.153.0__py311-none-any.whl → 2.154.0__py311-none-any.whl

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.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

Files changed (69) hide show
  1. synapse/cortex.py +4 -4
  2. synapse/lib/ast.py +10 -5
  3. synapse/lib/autodoc.py +2 -2
  4. synapse/lib/cache.py +16 -1
  5. synapse/lib/layer.py +2 -1
  6. synapse/lib/modelrev.py +36 -3
  7. synapse/lib/node.py +2 -5
  8. synapse/lib/snap.py +10 -0
  9. synapse/lib/storm.py +80 -0
  10. synapse/lib/stormhttp.py +3 -0
  11. synapse/lib/stormlib/backup.py +1 -0
  12. synapse/lib/stormlib/basex.py +2 -0
  13. synapse/lib/stormlib/cell.py +7 -0
  14. synapse/lib/stormlib/compression.py +3 -0
  15. synapse/lib/stormlib/ethereum.py +1 -0
  16. synapse/lib/stormlib/graph.py +2 -0
  17. synapse/lib/stormlib/hashes.py +5 -0
  18. synapse/lib/stormlib/hex.py +6 -0
  19. synapse/lib/stormlib/infosec.py +4 -0
  20. synapse/lib/stormlib/ipv6.py +1 -0
  21. synapse/lib/stormlib/iters.py +2 -0
  22. synapse/lib/stormlib/json.py +5 -0
  23. synapse/lib/stormlib/mime.py +1 -0
  24. synapse/lib/stormlib/model.py +19 -3
  25. synapse/lib/stormlib/modelext.py +1 -0
  26. synapse/lib/stormlib/notifications.py +2 -0
  27. synapse/lib/stormlib/pack.py +2 -0
  28. synapse/lib/stormlib/random.py +1 -0
  29. synapse/lib/stormlib/smtp.py +0 -7
  30. synapse/lib/stormlib/stats.py +4 -0
  31. synapse/lib/stormlib/stix.py +8 -0
  32. synapse/lib/stormlib/storm.py +1 -0
  33. synapse/lib/stormlib/version.py +3 -0
  34. synapse/lib/stormlib/xml.py +3 -0
  35. synapse/lib/stormlib/yaml.py +2 -0
  36. synapse/lib/stormtypes.py +201 -29
  37. synapse/lib/trigger.py +180 -4
  38. synapse/lib/types.py +1 -1
  39. synapse/lib/version.py +2 -2
  40. synapse/lib/view.py +55 -6
  41. synapse/models/inet.py +16 -4
  42. synapse/models/orgs.py +47 -2
  43. synapse/models/risk.py +126 -2
  44. synapse/models/syn.py +6 -0
  45. synapse/tests/files/stormpkg/badapidef.yaml +13 -0
  46. synapse/tests/files/stormpkg/storm/modules/apimod +10 -0
  47. synapse/tests/files/stormpkg/testpkg.yaml +23 -0
  48. synapse/tests/test_cortex.py +50 -34
  49. synapse/tests/test_lib_ast.py +7 -0
  50. synapse/tests/test_lib_autodoc.py +1 -1
  51. synapse/tests/test_lib_modelrev.py +9 -0
  52. synapse/tests/test_lib_node.py +55 -0
  53. synapse/tests/test_lib_storm.py +13 -0
  54. synapse/tests/test_lib_stormlib_storm.py +8 -0
  55. synapse/tests/test_lib_stormsvc.py +24 -1
  56. synapse/tests/test_lib_stormtypes.py +105 -1
  57. synapse/tests/test_lib_trigger.py +315 -0
  58. synapse/tests/test_lib_view.py +1 -2
  59. synapse/tests/test_model_inet.py +22 -0
  60. synapse/tests/test_model_orgs.py +28 -0
  61. synapse/tests/test_model_risk.py +73 -0
  62. synapse/tests/test_tools_autodoc.py +25 -0
  63. synapse/tests/test_tools_genpkg.py +9 -3
  64. synapse/tools/autodoc.py +42 -2
  65. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/METADATA +1 -1
  66. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/RECORD +69 -67
  67. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/WHEEL +1 -1
  68. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/LICENSE +0 -0
  69. {synapse-2.153.0.dist-info → synapse-2.154.0.dist-info}/top_level.txt +0 -0
synapse/lib/trigger.py CHANGED
@@ -21,6 +21,8 @@ Conditions = set((
21
21
  'node:add',
22
22
  'node:del',
23
23
  'prop:set',
24
+ 'edge:add',
25
+ 'edge:del'
24
26
  ))
25
27
 
26
28
  RecursionDepth = contextvars.ContextVar('RecursionDepth', default=0)
@@ -37,11 +39,13 @@ TrigSchema = {
37
39
  'user': {'type': 'string', 'pattern': s_config.re_iden},
38
40
  'view': {'type': 'string', 'pattern': s_config.re_iden},
39
41
  'form': {'type': 'string', 'pattern': _formre},
42
+ 'n2form': {'type': 'string', 'pattern': _formre},
40
43
  'tag': {'type': 'string', 'pattern': _tagre},
41
44
  'prop': {'type': 'string', 'pattern': _propre},
45
+ 'verb': {'type': 'string', },
42
46
  'name': {'type': 'string', },
43
47
  'doc': {'type': 'string', },
44
- 'cond': {'enum': ['node:add', 'node:del', 'tag:add', 'tag:del', 'prop:set']},
48
+ 'cond': {'enum': ['node:add', 'node:del', 'tag:add', 'tag:del', 'prop:set', 'edge:add', 'edge:del']},
45
49
  'storm': {'type': 'string'},
46
50
  'async': {'type': 'boolean'},
47
51
  'enabled': {'type': 'boolean'},
@@ -69,6 +73,14 @@ TrigSchema = {
69
73
  'if': {'properties': {'cond': {'const': 'prop:set'}}},
70
74
  'then': {'required': ['prop']},
71
75
  },
76
+ {
77
+ 'if': {'properties': {'cond': {'const': 'edge:add'}}},
78
+ 'then': {'required': ['verb']},
79
+ },
80
+ {
81
+ 'if': {'properties': {'cond': {'const': 'edge:del'}}},
82
+ 'then': {'required': ['verb']},
83
+ },
72
84
  ],
73
85
  }
74
86
  TrigSchemaValidator = s_config.getJsValidator(TrigSchema)
@@ -89,9 +101,9 @@ class Triggers:
89
101
  self.view = view
90
102
  self.triggers = {}
91
103
 
92
- self.tagadd = collections.defaultdict(list) # (form, tag): [ Triger ... ]
93
- self.tagset = collections.defaultdict(list) # (form, tag): [ Triger ... ]
94
- self.tagdel = collections.defaultdict(list) # (form, tag): [ Triger ... ]
104
+ self.tagadd = collections.defaultdict(list) # (form, tag): [ Trigger ... ]
105
+ self.tagset = collections.defaultdict(list) # (form, tag): [ Trigger ... ]
106
+ self.tagdel = collections.defaultdict(list) # (form, tag): [ Trigger ... ]
95
107
 
96
108
  self.tagaddglobs = collections.defaultdict(s_cache.TagGlobs) # form: TagGlobs
97
109
  self.tagsetglobs = collections.defaultdict(s_cache.TagGlobs) # form: TagGlobs
@@ -101,6 +113,15 @@ class Triggers:
101
113
  self.nodedel = collections.defaultdict(list) # form: [ Trigger ... ]
102
114
  self.propset = collections.defaultdict(list) # prop: [ Trigger ... ]
103
115
 
116
+ self.edgeadd = collections.defaultdict(list) # (n1form, verb, n2form: [ Trigger ... ]
117
+ self.edgedel = collections.defaultdict(list) # (n1form, verb, n2form: [ Trigger ... ]
118
+
119
+ self.edgeaddglobs = collections.defaultdict(s_cache.EdgeGlobs) # (n1form, n2form: [ EdgeGlobs ... ]
120
+ self.edgedelglobs = collections.defaultdict(s_cache.EdgeGlobs) # (n1form, n2form: [ EdgeGlobs ... ]
121
+
122
+ self.edgeaddcache = s_cache.LruDict()
123
+ self.edgedelcache = s_cache.LruDict()
124
+
104
125
  @contextlib.contextmanager
105
126
  def _recursion_check(self):
106
127
 
@@ -182,6 +203,106 @@ class Triggers:
182
203
  for _, trig in globs.get(tag):
183
204
  await trig.execute(node, vars=vars, view=view)
184
205
 
206
+ async def runEdgeAdd(self, n1, verb, n2, view=None):
207
+ n1form = n1.form.name if n1 else None
208
+ n2form = n2.form.name if n2 else None
209
+ n2iden = n2.iden() if n2 else None
210
+ varz = {'auto': {'opts': {'verb': verb, 'n2iden': n2iden}}}
211
+ with self._recursion_check():
212
+ cachekey = (n1form, verb, n2form)
213
+ cached = self.edgeaddcache.get(cachekey)
214
+ if cached is None:
215
+ cached = []
216
+ for trig in self.edgeadd.get((None, verb, None), ()):
217
+ cached.append(trig)
218
+
219
+ globs = self.edgeaddglobs.get((None, None))
220
+ if globs:
221
+ for _, trig in globs.get(verb):
222
+ cached.append(trig)
223
+
224
+ if n1:
225
+ for trig in self.edgeadd.get((n1form, verb, None), ()):
226
+ cached.append(trig)
227
+
228
+ globs = self.edgeaddglobs.get((n1form, None))
229
+ if globs:
230
+ for _, trig in globs.get(verb):
231
+ cached.append(trig)
232
+
233
+ if n2:
234
+ for trig in self.edgeadd.get((None, verb, n2form), ()):
235
+ cached.append(trig)
236
+
237
+ globs = self.edgeaddglobs.get((None, n2form))
238
+ if globs:
239
+ for _, trig in globs.get(verb):
240
+ cached.append(trig)
241
+
242
+ if n1 and n2:
243
+ for trig in self.edgeadd.get((n1form, verb, n2form), ()):
244
+ cached.append(trig)
245
+
246
+ globs = self.edgeaddglobs.get((n1form, n2form))
247
+ if globs:
248
+ for _, trig in globs.get(verb):
249
+ cached.append(trig)
250
+
251
+ self.edgeaddcache[cachekey] = cached
252
+
253
+ for trig in cached:
254
+ await trig.execute(n1, vars=varz, view=view)
255
+
256
+ async def runEdgeDel(self, n1, verb, n2, view=None):
257
+ n1form = n1.form.name if n1 else None
258
+ n2form = n2.form.name if n2 else None
259
+ n2iden = n2.iden() if n2 else None
260
+ varz = {'auto': {'opts': {'verb': verb, 'n2iden': n2iden}}}
261
+ with self._recursion_check():
262
+ cachekey = (n1form, verb, n2form)
263
+ cached = self.edgedelcache.get(cachekey)
264
+ if cached is None:
265
+ cached = []
266
+ for trig in self.edgedel.get((None, verb, None), ()):
267
+ cached.append(trig)
268
+
269
+ globs = self.edgedelglobs.get((None, None))
270
+ if globs:
271
+ for _, trig in globs.get(verb):
272
+ cached.append(trig)
273
+
274
+ if n1:
275
+ for trig in self.edgedel.get((n1form, verb, None), ()):
276
+ cached.append(trig)
277
+
278
+ globs = self.edgedelglobs.get((n1form, None))
279
+ if globs:
280
+ for _, trig in globs.get(verb):
281
+ cached.append(trig)
282
+
283
+ if n2:
284
+ for trig in self.edgedel.get((None, verb, n2form), ()):
285
+ cached.append(trig)
286
+
287
+ globs = self.edgedelglobs.get((None, n2form))
288
+ if globs:
289
+ for _, trig in globs.get(verb):
290
+ cached.append(trig)
291
+
292
+ if n1 and n2:
293
+ for trig in self.edgedel.get((n1form, verb, n2form), ()):
294
+ cached.append(trig)
295
+
296
+ globs = self.edgedelglobs.get((n1form, n2form))
297
+ if globs:
298
+ for _, trig in globs.get(verb):
299
+ cached.append(trig)
300
+
301
+ self.edgedelcache[cachekey] = cached
302
+
303
+ for trig in cached:
304
+ await trig.execute(n1, vars=varz, view=view)
305
+
185
306
  async def load(self, tdef):
186
307
 
187
308
  trig = Trigger(self.view, tdef)
@@ -194,6 +315,8 @@ class Triggers:
194
315
  tag = trig.tdef.get('tag')
195
316
  form = trig.tdef.get('form')
196
317
  prop = trig.tdef.get('prop')
318
+ verb = trig.tdef.get('verb')
319
+ n2form = trig.tdef.get('n2form')
197
320
 
198
321
  if cond not in Conditions:
199
322
  raise s_exc.NoSuchCond(name=cond)
@@ -206,6 +329,9 @@ class Triggers:
206
329
  if tag is None:
207
330
  raise s_exc.BadOptValu(mesg='missing tag')
208
331
  s_chop.validateTagMatch(tag)
332
+ if cond in ('edge:add', 'edge:del') and verb is None:
333
+ raise s_exc.BadOptValu(mesg='verb must be present for edge:add or edge:del')
334
+
209
335
  if prop is not None and cond != 'prop:set':
210
336
  raise s_exc.BadOptValu(mesg='prop parameter invalid')
211
337
 
@@ -237,6 +363,20 @@ class Triggers:
237
363
  else:
238
364
  self.tagdelglobs[form].add(tag, trig)
239
365
 
366
+ elif cond == 'edge:add':
367
+ self.edgeaddcache.clear()
368
+ if '*' not in verb:
369
+ self.edgeadd[(form, verb, n2form)].append(trig)
370
+ else:
371
+ self.edgeaddglobs[(form, n2form)].add(verb, trig)
372
+
373
+ elif cond == 'edge:del':
374
+ self.edgedelcache.clear()
375
+ if '*' not in verb:
376
+ self.edgedel[(form, verb, n2form)].append(trig)
377
+ else:
378
+ self.edgedelglobs[(form, n2form)].add(verb, trig)
379
+
240
380
  self.triggers[trig.iden] = trig
241
381
  return trig
242
382
 
@@ -292,6 +432,34 @@ class Triggers:
292
432
  globs.rem(tag, trig)
293
433
  return trig
294
434
 
435
+ if cond == 'edge:add':
436
+ verb = trig.tdef['verb']
437
+ form = trig.tdef.get('form')
438
+ n2form = trig.tdef.get('n2form')
439
+
440
+ self.edgeaddcache.clear()
441
+ if '*' not in verb:
442
+ self.edgeadd[(form, verb, n2form)].remove(trig)
443
+ return trig
444
+
445
+ globs = self.edgeaddglobs.get((form, n2form))
446
+ globs.rem(verb, trig)
447
+ return trig
448
+
449
+ if cond == 'edge:del':
450
+ verb = trig.tdef['verb']
451
+ form = trig.tdef.get('form')
452
+ n2form = trig.tdef.get('n2form')
453
+
454
+ self.edgedelcache.clear()
455
+ if '*' not in verb:
456
+ self.edgedel[(form, verb, n2form)].remove(trig)
457
+ return trig
458
+
459
+ globs = self.edgedelglobs.get((form, n2form))
460
+ globs.rem(verb, trig)
461
+ return trig
462
+
295
463
  raise AssertionError('trigger has invalid condition')
296
464
 
297
465
  def get(self, iden):
@@ -440,6 +608,14 @@ class Trigger:
440
608
  if prop is not None:
441
609
  props['prop'] = prop
442
610
 
611
+ verb = self.tdef.get('verb')
612
+ if verb is not None:
613
+ props['verb'] = verb
614
+
615
+ n2form = self.tdef.get('n2form')
616
+ if n2form is not None:
617
+ props['n2form'] = n2form
618
+
443
619
  pnorms = {}
444
620
  for prop, valu in props.items():
445
621
  formprop = form.props.get(prop)
synapse/lib/types.py CHANGED
@@ -239,7 +239,7 @@ class Type:
239
239
  return cmpr
240
240
 
241
241
  def _ctorCmprRe(self, text):
242
- regx = regex.compile(text)
242
+ regx = regex.compile(text, flags=regex.I)
243
243
 
244
244
  def cmpr(valu):
245
245
  vtxt = self.repr(valu)
synapse/lib/version.py CHANGED
@@ -223,6 +223,6 @@ def reqVersion(valu, reqver,
223
223
  ##############################################################################
224
224
  # The following are touched during the release process by bumpversion.
225
225
  # Do not modify these directly.
226
- version = (2, 153, 0)
226
+ version = (2, 154, 0)
227
227
  verstring = '.'.join([str(x) for x in version])
228
- commit = '2c5fbd78dba1e0812c83cca220388488b7709932'
228
+ commit = '6c9600992a0ca2563239bc86b6631bd599707e3f'
synapse/lib/view.py CHANGED
@@ -746,6 +746,7 @@ class View(s_nexus.Pusher): # type: ignore
746
746
 
747
747
  await self.info.set('layers', [lyr.iden for lyr in self.layers])
748
748
  await self.core.feedBeholder('view:addlayer', {'iden': self.iden, 'layer': layriden, 'indx': indx}, gates=[self.iden, layriden])
749
+ self.core._calcViewsByLayer()
749
750
 
750
751
  @s_nexus.Pusher.onPushAuto('view:setlayers')
751
752
  async def setLayers(self, layers):
@@ -753,14 +754,11 @@ class View(s_nexus.Pusher): # type: ignore
753
754
  Set the view layers from a list of idens.
754
755
  NOTE: view layers are stored "top down" (the write layer is self.layers[0])
755
756
  '''
756
- for view in self.core.views.values():
757
- if view.parent is self:
758
- raise s_exc.ReadOnlyLayer(mesg='May not change layers that have been forked from')
757
+ layrs = []
759
758
 
760
759
  if self.parent is not None:
761
- raise s_exc.ReadOnlyLayer(mesg='May not change layers of forked view')
762
-
763
- layrs = []
760
+ mesg = 'You cannot set the layers of a forked view.'
761
+ raise s_exc.BadArg(mesg=mesg)
764
762
 
765
763
  for iden in layers:
766
764
  layr = self.core.layers.get(iden)
@@ -777,6 +775,39 @@ class View(s_nexus.Pusher): # type: ignore
777
775
  await self.info.set('layers', layers)
778
776
  await self.core.feedBeholder('view:setlayers', {'iden': self.iden, 'layers': layers}, gates=[self.iden, layers[0]])
779
777
 
778
+ await self._calcChildViews()
779
+ self.core._calcViewsByLayer()
780
+
781
+ async def _calcChildViews(self):
782
+
783
+ todo = collections.deque([self])
784
+
785
+ byparent = collections.defaultdict(list)
786
+ for view in self.core.views.values():
787
+ if view.parent is None:
788
+ continue
789
+
790
+ byparent[view.parent].append(view)
791
+
792
+ while todo:
793
+
794
+ view = todo.pop()
795
+
796
+ for child in byparent.get(view, ()):
797
+
798
+ layers = [child.layers[0]]
799
+ layers.extend(view.layers)
800
+
801
+ child.layers = layers
802
+
803
+ # convert layers to a list of idens...
804
+ lids = [layr.iden for layr in layers]
805
+ await child.info.set('layers', lids)
806
+
807
+ await self.core.feedBeholder('view:setlayers', {'iden': child.iden, 'layers': lids}, gates=[child.iden, lids[0]])
808
+
809
+ todo.append(child)
810
+
780
811
  async def fork(self, ldef=None, vdef=None):
781
812
  '''
782
813
  Make a new view inheriting from this view with the same layers and a new write layer on top
@@ -961,6 +992,24 @@ class View(s_nexus.Pusher): # type: ignore
961
992
 
962
993
  await self.triggers.runPropSet(node, prop, oldv, view=view)
963
994
 
995
+ async def runEdgeAdd(self, n1, edge, n2, view=None):
996
+ if not n1.snap.trigson:
997
+ return
998
+
999
+ if view is None:
1000
+ view = self.iden
1001
+
1002
+ await self.triggers.runEdgeAdd(n1, edge, n2, view=view)
1003
+
1004
+ async def runEdgeDel(self, n1, edge, n2, view=None):
1005
+ if not n1.snap.trigson:
1006
+ return
1007
+
1008
+ if view is None:
1009
+ view = self.iden
1010
+
1011
+ await self.triggers.runEdgeDel(n1, edge, n2, view=view)
1012
+
964
1013
  async def addTrigger(self, tdef):
965
1014
  '''
966
1015
  Adds a trigger to the view.
synapse/models/inet.py CHANGED
@@ -28,6 +28,8 @@ udots = regex.compile(r'[\u3002\uff0e\uff61]')
28
28
  cidrmasks = [((0xffffffff - (2 ** (32 - i) - 1)), (2 ** (32 - i))) for i in range(33)]
29
29
  ipv4max = 2 ** 32 - 1
30
30
 
31
+ rfc6598 = ipaddress.IPv4Network('100.64.0.0/10')
32
+
31
33
  def getAddrType(ip):
32
34
 
33
35
  if ip.is_multicast:
@@ -45,6 +47,9 @@ def getAddrType(ip):
45
47
  if ip.is_reserved:
46
48
  return 'reserved'
47
49
 
50
+ if ip in rfc6598:
51
+ return 'shared'
52
+
48
53
  return 'unicast'
49
54
 
50
55
  class Addr(s_types.Str):
@@ -924,6 +929,11 @@ class Url(s_types.Str):
924
929
  except Exception:
925
930
  pass
926
931
 
932
+ # allow MSFT specific wild card syntax
933
+ # https://learn.microsoft.com/en-us/windows/win32/http/urlprefix-strings
934
+ if host is None and part == '+':
935
+ host = '+'
936
+
927
937
  if host and local:
928
938
  raise s_exc.BadTypeValu(valu=orig, name=self.name,
929
939
  mesg='Host specified on local-only file URI') from None
@@ -950,10 +960,12 @@ class Url(s_types.Str):
950
960
  if port is not None:
951
961
  hostparts = f'{hostparts}:{port}'
952
962
 
953
- if proto != 'file':
954
- if (not subs.get('fqdn')) and (subs.get('ipv4') is None) and (subs.get('ipv6') is None):
955
- raise s_exc.BadTypeValu(valu=orig, name=self.name,
956
- mesg='Missing address/url') from None
963
+ if proto != 'file' and host is None:
964
+ raise s_exc.BadTypeValu(valu=orig, name=self.name, mesg='Missing address/url')
965
+
966
+ if not hostparts and not pathpart:
967
+ raise s_exc.BadTypeValu(valu=orig, name=self.name,
968
+ mesg='Missing address/url') from None
957
969
 
958
970
  base = f'{proto}://{hostparts}{pathpart}'
959
971
  subs['base'] = base
synapse/models/orgs.py CHANGED
@@ -202,6 +202,8 @@ class OuModule(s_module.CoreModule):
202
202
  ('ou:jobtitle', ('str', {'lower': True, 'onespace': True}), {
203
203
  'doc': 'A title for a position within an org.',
204
204
  }),
205
+ ('ou:requirement', ('guid', {}), {
206
+ 'doc': 'A specific requirement.'}),
205
207
  ),
206
208
  'edges': (
207
209
  (('ou:campaign', 'uses', 'ou:technique'), {
@@ -219,6 +221,8 @@ class OuModule(s_module.CoreModule):
219
221
  'doc': 'The campaign made use of the target node.'}),
220
222
  (('ou:contribution', 'includes', None), {
221
223
  'doc': 'The contribution includes the specific node.'}),
224
+ ((None, 'meets', 'ou:requirement'), {
225
+ 'doc': 'The requirement is met by the source node.'}),
222
226
  ),
223
227
  'forms': (
224
228
  ('ou:jobtype', {}, ()),
@@ -634,8 +638,10 @@ class OuModule(s_module.CoreModule):
634
638
  'doc': 'Deprecated for scalability. Please use -(uses)> ou:technique.',
635
639
  }),
636
640
  ('goals', ('array', {'type': 'ou:goal', 'sorted': True, 'uniq': True}), {
637
- 'doc': 'The assessed goals of the organization.'
638
- }),
641
+ 'doc': 'The assessed goals of the organization.'}),
642
+
643
+ ('tag', ('syn:tag', {}), {
644
+ 'doc': 'A base tag used to encode assessments made by the organization.'}),
639
645
  )),
640
646
  ('ou:team', {}, (
641
647
  ('org', ('ou:org', {}), {}),
@@ -1123,6 +1129,45 @@ class OuModule(s_module.CoreModule):
1123
1129
  }),
1124
1130
  # TODO duration ('duration'
1125
1131
  )),
1132
+ ('ou:requirement', {}, (
1133
+
1134
+ ('name', ('str', {'lower': True, 'onespace': True}), {
1135
+ 'doc': 'A name for the requirement.'}),
1136
+
1137
+ ('text', ('str', {}), {
1138
+ 'disp': {'hint': 'text'},
1139
+ 'doc': 'The text of the stated requirement.'}),
1140
+
1141
+ ('optional', ('bool', {}), {
1142
+ 'doc': 'Set to true if the requirement is optional.'}),
1143
+
1144
+ ('priority', ('meta:priority', {}), {
1145
+ 'doc': 'The priority of the requirement.'}),
1146
+
1147
+ ('goal', ('ou:goal', {}), {
1148
+ 'doc': 'The goal that the requirement is designed to achieve.'}),
1149
+
1150
+ ('active', ('bool', {}), {
1151
+ 'doc': 'Set to true if the requirement is currently active.'}),
1152
+
1153
+ ('issued', ('time', {}), {
1154
+ 'doc': 'The time that the requirement was first issued.'}),
1155
+
1156
+ ('period', ('ival', {}), {
1157
+ 'doc': 'The time window where the goal must be met. Can be ongoing.'}),
1158
+
1159
+ ('issuer', ('ps:contact', {}), {
1160
+ 'doc': 'The contact information of the entity which issued the requirement.'}),
1161
+
1162
+ ('assignee', ('ps:contact', {}), {
1163
+ 'doc': 'The contact information of the entity which is assigned to meet the requirement.'}),
1164
+
1165
+ ('deps', ('array', {'type': 'ou:requirement', 'sorted': True, 'uniq': True}), {
1166
+ 'doc': 'A list of sub-requirements which must be met to complete the requirement.'}),
1167
+
1168
+ ('deps:min', ('int', {'min': 0}), {
1169
+ 'doc': 'The minimum number dependant requirements which must be met. If unset, assume all must be met.'}),
1170
+ )),
1126
1171
  )
1127
1172
  }
1128
1173
 
synapse/models/risk.py CHANGED
@@ -99,9 +99,22 @@ class RiskModule(s_module.CoreModule):
99
99
  }),
100
100
 
101
101
  ('risk:threat:type:taxonomy', ('taxonomy', {}), {
102
- 'doc': 'A taxonomy of threat types.',
103
102
  'interfaces': ('taxonomy',),
104
- }),
103
+ 'doc': 'A taxonomy of threat types.'}),
104
+
105
+ ('risk:leak', ('guid', {}), {
106
+ 'doc': 'An event where information was disclosed without permission.'}),
107
+
108
+ ('risk:leak:type:taxonomy', ('taxonomy', {}), {
109
+ 'interfaces': ('taxonomy',),
110
+ 'doc': 'A taxonomy of leak event types.'}),
111
+
112
+ ('risk:extortion', ('guid', {}), {
113
+ 'doc': 'An event where an attacker attempted to extort a victim.'}),
114
+
115
+ ('risk:extortion:type:taxonomy', ('taxonomy', {}), {
116
+ 'interfaces': ('taxonomy',),
117
+ 'doc': 'A taxonomy of extortion event types.'}),
105
118
  ),
106
119
  'edges': (
107
120
  # some explicit examples...
@@ -141,6 +154,12 @@ class RiskModule(s_module.CoreModule):
141
154
  'doc': 'The target node was stolen or copied as a result of the compromise.'}),
142
155
  (('risk:mitigation', 'addresses', 'ou:technique'), {
143
156
  'doc': 'The mitigation addresses the technique.'}),
157
+
158
+ (('risk:leak', 'leaked', None), {
159
+ 'doc': 'The leak included the disclosure of the target node.'}),
160
+
161
+ (('risk:extortion', 'leveraged', None), {
162
+ 'doc': 'The extortion event was based on attacker access to the target node.'}),
144
163
  ),
145
164
  'forms': (
146
165
 
@@ -596,6 +615,9 @@ class RiskModule(s_module.CoreModule):
596
615
 
597
616
  ('ext:id', ('str', {}), {
598
617
  'doc': 'An external identifier for the alert.'}),
618
+
619
+ ('host', ('it:host', {}), {
620
+ 'doc': 'The host which generated the alert.'}),
599
621
  )),
600
622
  ('risk:compromisetype', {}, ()),
601
623
  ('risk:compromise', {}, (
@@ -612,6 +634,12 @@ class RiskModule(s_module.CoreModule):
612
634
  ('reporter:name', ('ou:name', {}), {
613
635
  'doc': 'The name of the organization reporting on the compromise.'}),
614
636
 
637
+ ('ext:id', ('str', {}), {
638
+ 'doc': 'An external unique ID for the compromise.'}),
639
+
640
+ ('url', ('inet:url', {}), {
641
+ 'doc': 'A URL which documents the compromise.'}),
642
+
615
643
  ('type', ('risk:compromisetype', {}), {
616
644
  'ex': 'cno.breach',
617
645
  'doc': 'A type for the compromise, as a taxonomy entry.'}),
@@ -815,6 +843,102 @@ class RiskModule(s_module.CoreModule):
815
843
  'doc': 'An external unique ID for the attack.'}),
816
844
 
817
845
  )),
846
+
847
+ ('risk:leak:type:taxonomy', {}, ()),
848
+ ('risk:leak', {}, (
849
+
850
+ ('name', ('str', {'lower': True, 'onespace': True}), {
851
+ 'doc': 'A simple name for the leak event.'}),
852
+
853
+ ('desc', ('str', {}), {
854
+ 'disp': {'hint': 'text'},
855
+ 'doc': 'A description of the leak event.'}),
856
+
857
+ ('reporter', ('ou:org', {}), {
858
+ 'doc': 'The organization reporting on the leak event.'}),
859
+
860
+ ('reporter:name', ('ou:name', {}), {
861
+ 'doc': 'The name of the organization reporting on the leak event.'}),
862
+
863
+ ('disclosed', ('time', {}), {
864
+ 'doc': 'The time the leaked information was disclosed.'}),
865
+
866
+ ('owner', ('ps:contact', {}), {
867
+ 'doc': 'The owner of the leaked information.'}),
868
+
869
+ ('leaker', ('ps:contact', {}), {
870
+ 'doc': 'The identity which leaked the information.'}),
871
+
872
+ ('type', ('risk:leak:type:taxonomy', {}), {
873
+ 'doc': 'A type taxonomy for the leak.'}),
874
+
875
+ ('goal', ('ou:goal', {}), {
876
+ 'doc': 'The goal of the leaker in disclosing the information.'}),
877
+
878
+ ('compromise', ('risk:compromise', {}), {
879
+ 'doc': 'The compromise which allowed the leaker access to the information.'}),
880
+
881
+ ('public', ('bool', {}), {
882
+ 'doc': 'Set to true if the leaked information was made publicly available.'}),
883
+
884
+ ('public:url', ('inet:url', {}), {
885
+ 'doc': 'The URL where the leaked information was made publicly available.'}),
886
+
887
+ )),
888
+
889
+ ('risk:extortion:type:taxonomy', {}, ()),
890
+ ('risk:extortion', {}, (
891
+
892
+ ('name', ('str', {'lower': True, 'onespace': True}), {
893
+ 'doc': 'A name for the extortion event.'}),
894
+
895
+ ('desc', ('str', {}), {
896
+ 'disp': {'hint': 'text'},
897
+ 'doc': 'A description of the extortion event.'}),
898
+
899
+ ('reporter', ('ou:org', {}), {
900
+ 'doc': 'The organization reporting on the extortion event.'}),
901
+
902
+ ('reporter:name', ('ou:name', {}), {
903
+ 'doc': 'The name of the organization reporting on the extortion event.'}),
904
+
905
+ ('demanded', ('time', {}), {
906
+ 'doc': 'The time that the attacker made their demands.'}),
907
+
908
+ ('goal', ('ou:goal', {}), {
909
+ 'doc': 'The goal of the attacker in extorting the victim.'}),
910
+
911
+ ('type', ('risk:extortion:type:taxonomy', {}), {
912
+ 'doc': 'A type taxonomy for the extortion event.'}),
913
+
914
+ ('attacker', ('ps:contact', {}), {
915
+ 'doc': 'The extortion attacker identity.'}),
916
+
917
+ ('target', ('ps:contact', {}), {
918
+ 'doc': 'The extortion target identity.'}),
919
+
920
+ ('success', ('bool', {}), {
921
+ 'doc': 'Set to true if the victim met the attackers demands.'}),
922
+
923
+ ('enacted', ('bool', {}), {
924
+ 'doc': 'Set to true if attacker carried out the threat.'}),
925
+
926
+ ('public', ('bool', {}), {
927
+ 'doc': 'Set to true if the attacker publicly announced the extortion.'}),
928
+
929
+ ('public:url', ('inet:url', {}), {
930
+ 'doc': 'The URL where the attacker publicly announced the extortion.'}),
931
+
932
+ ('compromise', ('risk:compromise', {}), {
933
+ 'doc': 'The compromise which allowed the attacker to extort the target.'}),
934
+
935
+ ('demanded:payment:price', ('econ:price', {}), {
936
+ 'doc': 'The payment price which was demanded.'}),
937
+
938
+ ('demanded:payment:currency', ('econ:currency', {}), {
939
+ 'doc': 'The currency in which payment was demanded.'}),
940
+
941
+ )),
818
942
  ),
819
943
  }
820
944
  name = 'risk'
synapse/models/syn.py CHANGED
@@ -236,6 +236,12 @@ class SynModule(s_module.CoreModule):
236
236
  ('form', ('str', {'lower': True, 'strip': True}), {
237
237
  'doc': 'Form the trigger is watching for.'
238
238
  }),
239
+ ('verb', ('str', {'lower': True, 'strip': True}), {
240
+ 'doc': 'Edge verb the trigger is watching for.'
241
+ }),
242
+ ('n2form', ('str', {'lower': True, 'strip': True}), {
243
+ 'doc': 'N2 form the trigger is watching for.'
244
+ }),
239
245
  ('prop', ('str', {'lower': True, 'strip': True}), {
240
246
  'doc': 'Property the trigger is watching for.'
241
247
  }),