-
Notifications
You must be signed in to change notification settings - Fork 212
Add feature to define findings manually in the model, similar to over… #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 6 commits
82bc106
9e0fce5
1eaeded
1640899
389ee11
05f6dfd
34959b6
f577e66
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,12 +59,15 @@ def __set__(self, instance, value): | |
| # called when x.d = val | ||
| # instance = x | ||
| # value = val | ||
|
|
||
| if instance in self.data: | ||
| raise ValueError( | ||
| "cannot overwrite {}.{} value with {}, already set to {}".format( | ||
| instance, self.__class__.__name__, value, self.data[instance] | ||
| if (not isinstance(instance, Finding)): | ||
| raise ValueError( | ||
| "cannot overwrite {}.{} value with {}, already set to {}".format( | ||
| instance, self.__class__.__name__, value, self.data[instance] | ||
| ) | ||
| ) | ||
| ) | ||
|
|
||
| self.data[instance] = value | ||
| if self.onSet is not None: | ||
| self.onSet(instance, value) | ||
|
|
@@ -603,17 +606,17 @@ class Finding: | |
| """Represents a Finding - the element in question | ||
| and a description of the finding""" | ||
|
|
||
| element = varElement(None, required=True, doc="Element this finding applies to") | ||
| element = varElement(None, required=False, doc="Element this finding applies to") | ||
| target = varString("", doc="Name of the element this finding applies to") | ||
| description = varString("", required=True, doc="Threat description") | ||
| details = varString("", required=True, doc="Threat details") | ||
| severity = varString("", required=True, doc="Threat severity") | ||
| mitigations = varString("", required=True, doc="Threat mitigations") | ||
| example = varString("", required=True, doc="Threat example") | ||
| severity = varString("", required=False, doc="Threat severity") | ||
| mitigations = varString("", required=False, doc="Threat mitigations") | ||
| example = varString("", required=False, doc="Threat example") | ||
| id = varString("", required=True, doc="Finding ID") | ||
| threat_id = varString("", required=True, doc="Threat ID") | ||
| references = varString("", required=True, doc="Threat references") | ||
| condition = varString("", required=True, doc="Threat condition") | ||
| threat_id = varString("", required=False, doc="Threat ID") | ||
| references = varString("", required=False, doc="Threat references") | ||
| condition = varString("", required=False, doc="Threat condition") | ||
| response = varString( | ||
| "", | ||
| required=False, | ||
|
|
@@ -626,6 +629,7 @@ class Finding: | |
| """, | ||
| ) | ||
| cvss = varString("", required=False, doc="The CVSS score and/or vector") | ||
| source = varString("manual", required=False, doc="The source of the Finding.") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. I'll add some better text. I could remove the logic around source altogether and we just add it to every element in the threatlib.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's fine to have that logic, it's better to have this field only in |
||
|
|
||
| def __init__( | ||
| self, | ||
|
|
@@ -635,10 +639,12 @@ def __init__( | |
| if args: | ||
| element = args[0] | ||
| else: | ||
| element = kwargs.pop("element", Element("invalid")) | ||
| element = kwargs.pop("element", None) | ||
|
|
||
| if (element): | ||
| self.target = element.name | ||
| self.element = element | ||
|
|
||
| self.target = element.name | ||
| self.element = element | ||
| attrs = [ | ||
| "description", | ||
| "details", | ||
|
|
@@ -656,25 +662,30 @@ def __init__( | |
| kwargs[a] = getattr(threat, a) | ||
|
|
||
| threat_id = kwargs.get("threat_id", None) | ||
| for f in element.overrides: | ||
| if f.threat_id != threat_id: | ||
| continue | ||
| for i in dir(f.__class__): | ||
| attr = getattr(f.__class__, i) | ||
| if ( | ||
| i in ("element", "target") | ||
| or i.startswith("_") | ||
| or callable(attr) | ||
| or not isinstance(attr, var) | ||
| ): | ||
|
|
||
| if (element): | ||
| for f in element.overrides: | ||
| if f.threat_id != threat_id: | ||
| continue | ||
| if f in attr.data: | ||
| kwargs[i] = attr.data[f] | ||
| break | ||
|
|
||
| for i in dir(f.__class__): | ||
| attr = getattr(f.__class__, i) | ||
| if ( | ||
| i in ("element", "target") | ||
| or i.startswith("_") | ||
| or callable(attr) | ||
| or not isinstance(attr, var) | ||
| ): | ||
| continue | ||
| if f in attr.data: | ||
| kwargs[i] = attr.data[f] | ||
| break | ||
|
|
||
| for k, v in kwargs.items(): | ||
| setattr(self, k, v) | ||
|
|
||
| TM._findings.append(self) | ||
|
|
||
|
|
||
| def _safeset(self, attr, value): | ||
| try: | ||
| setattr(self, attr, value) | ||
|
|
@@ -704,6 +715,7 @@ class TM: | |
| _threatsExcluded = [] | ||
| _sf = None | ||
| _duplicate_ignored_attrs = "name", "note", "order", "response", "responseTo" | ||
| _findings = [] | ||
| name = varString("", required=True, doc="Model name") | ||
| description = varString("", required=True, doc="Model description") | ||
| threatsFile = varString( | ||
|
|
@@ -739,6 +751,7 @@ def reset(cls): | |
| cls._threats = [] | ||
| cls._boundaries = [] | ||
| cls._data = [] | ||
| cls._findings = [] | ||
|
|
||
| def _init_threats(self): | ||
| TM._threats = [] | ||
|
|
@@ -755,10 +768,30 @@ def resolve(self): | |
| finding_count = 0 | ||
| findings = [] | ||
| elements = defaultdict(list) | ||
|
|
||
| #Manually added findings with element as arg to Finding object | ||
| for f in TM._findings: | ||
| if (f.element): | ||
| finding_count += 1 | ||
| f._safeset("id", str(finding_count)) | ||
| findings.append(f) | ||
| elements[f.element].append(f) | ||
|
|
||
| for e in TM._elements: | ||
| if not e.inScope: | ||
| continue | ||
|
|
||
| #Manually added findings, added to an element's finding attribute | ||
| if (len(e.findings) > 0): | ||
|
|
||
| for f in e.findings: | ||
| finding_count += 1 | ||
| f._safeset("id", str(finding_count)) | ||
| f._safeset("element", e) | ||
| f._safeset("target", e.name) | ||
| findings.append(f) | ||
| elements[e].append(f) | ||
|
|
||
| override_ids = set(f.threat_id for f in e.overrides) | ||
| # if element is a dataflow filter out overrides from source and sink | ||
| # because they will be always applied there anyway | ||
|
|
@@ -769,18 +802,21 @@ def resolve(self): | |
| except AttributeError: | ||
| pass | ||
|
|
||
| #Findings added by pytm using threatlib | ||
| for t in TM._threats: | ||
| if not t.apply(e) and t.id not in override_ids: | ||
| continue | ||
|
|
||
| finding_count += 1 | ||
| f = Finding(e, id=str(finding_count), threat=t) | ||
| f = Finding(e, id=str(finding_count), threat=t, source="pytm") | ||
| logger.debug(f"new finding: {f}") | ||
| findings.append(f) | ||
| elements[e].append(f) | ||
|
|
||
| self.findings = findings | ||
|
|
||
| for e, findings in elements.items(): | ||
| e.findings = findings | ||
| e._safeset("findings", findings) | ||
|
|
||
| def check(self): | ||
| if self.description is None: | ||
|
|
@@ -1854,6 +1890,7 @@ def encode_threat_data(obj): | |
| "threat_id", | ||
| "references", | ||
| "condition", | ||
| "source", | ||
| ] | ||
|
|
||
| if type(obj) is Finding or (len(obj) != 0 and type(obj[0]) is Finding): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -801,4 +801,4 @@ | |
| "onDuplicates": "Action.NO_ACTION", | ||
| "threatsExcluded": [], | ||
| "threatsFile": "pytm/threatlib/threats.json" | ||
| } | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't remove end of line at end of file. Configure your text editor not to do that automatically.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I replaced the json using the produced json during the tests. I'm editing in vi so nothing is removed automatically on my end. If the space is important we could look to add it to the generated file. |
||
Uh oh!
There was an error while loading. Please reload this page.