Skip to content

Commit

Permalink
Make the artifact argument of a Result to be lazy set (#271)
Browse files Browse the repository at this point in the history
In order to simplify the rules, this change reduces the need to pass the
file artifact when initializing the Result. This amounts to less code in
each Rule. The artifact still gets set, but later in the parser. As a
result, the snippet and artifact URI are also set later.

Signed-off-by: Eric Brown <[email protected]>
  • Loading branch information
ericwb authored Feb 7, 2024
1 parent c234148 commit 8d9a9be
Show file tree
Hide file tree
Showing 24 changed files with 37 additions and 47 deletions.
40 changes: 30 additions & 10 deletions precli/core/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ class Result:
def __init__(
self,
rule_id: str,
location: Location,
artifact: Artifact = None,
kind: Kind = Kind.FAIL,
level: Level = None,
location: Location = None,
message: str = None,
fixes: list[Fix] = None,
suppression: Suppression = None,
Expand All @@ -38,25 +38,34 @@ def __init__(
self._message = Rule.get_by_id(self._rule_id).message
self._fixes = fixes if fixes is not None else []
self._suppression = suppression

if snippet is not None:
self._snippet = snippet
else:
self._init_snippet()
self._init_uri()

def _init_snippet(self):
if self._artifact is not None and self._location is not None:
linecache = LineCache(
artifact.file_name,
artifact.contents.decode(),
self._artifact.file_name,
self._artifact.contents.decode(),
)
self._snippet = ""
for i in range(location.start_line - 1, location.end_line + 2):
for i in range(
self._location.start_line - 1, self._location.end_line + 2
):
self._snippet += linecache.getline(i)

def _init_uri(self):
# Append location info to the URI
if artifact.uri is not None:
if location.start_line != location.end_line:
lines = f"L{location.start_line}-L{location.end_line}"
if self._artifact is not None and self._artifact.uri is not None:
if self._location.start_line != self._location.end_line:
lines = (
f"L{self._location.start_line}-L{self._location.end_line}"
)
else:
lines = f"L{location.start_line}"
artifact.uri = f"{artifact.uri}#{lines}"
lines = f"L{self._location.start_line}"
self._artifact.uri = f"{self._artifact.uri}#{lines}"

@property
def rule_id(self) -> str:
Expand All @@ -81,6 +90,17 @@ def artifact(self) -> Artifact:
"""
return self._artifact

@artifact.setter
def artifact(self, artifact):
"""
Set the file artifact.
:param Artifact artifact: file artifact
"""
self._artifact = artifact
self._init_snippet()
self._init_uri()

@property
def location(self) -> Location:
"""
Expand Down
4 changes: 3 additions & 1 deletion precli/parsers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,16 @@ def parse(self, artifact: Artifact) -> list[Result]:
:rtype: list
"""
self.results = []
self.context = {"artifact": artifact}
self.context = {}
if artifact.contents is None:
with open(artifact.file_name, "rb") as fdata:
artifact.contents = fdata.read()
tree = self.tree_sitter_parser.parse(artifact.contents)
self.visit([tree.root_node])

for result in self.results:
result.artifact = artifact

suppression = self.suppressions.get(result.location.start_line)
if suppression and result.rule_id in suppression.rules:
result.suppression = suppression
Expand Down
1 change: 0 additions & 1 deletion precli/rules/go/stdlib/crypto_weak_cipher.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(call.name),
Expand Down
2 changes: 0 additions & 2 deletions precli/rules/go/stdlib/crypto_weak_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(call.name_qualified),
Expand All @@ -124,7 +123,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(call.name_qualified),
Expand Down
2 changes: 0 additions & 2 deletions precli/rules/go/stdlib/crypto_weak_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=argument.identifier_node),
level=Level.ERROR,
message=self.message.format("DSA", 2048),
Expand All @@ -145,7 +144,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=argument.node),
level=Level.ERROR if bits <= 1024 else Level.WARNING,
message=self.message.format("RSA", 2048),
Expand Down
4 changes: 1 addition & 3 deletions precli/rules/python/stdlib/crypt_weak_hash.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Secure Saurce LLC
# Copyright 2024 Secure Saurce LLC
r"""
=======================================
Reversible One Way Hash in Crypt Module
Expand Down Expand Up @@ -134,7 +134,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if isinstance(name, str) and name in WEAK_CRYPT_HASHES:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(name),
)
Expand All @@ -144,7 +143,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if isinstance(name, str) and name in WEAK_CRYPT_HASHES:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(name),
)
3 changes: 0 additions & 3 deletions precli/rules/python/stdlib/ftplib_cleartext.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if call.get_argument(position=2, name="passwd").value is not None:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=f"The '{call.name_qualified}' module will "
Expand All @@ -164,7 +163,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
else:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(call.name_qualified),
fixes=fixes,
Expand All @@ -176,7 +174,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if call.get_argument(position=1, name="passwd").value is not None:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.identifier_node),
level=Level.ERROR,
message=f"The '{call.name_qualified}' function will "
Expand Down
3 changes: 0 additions & 3 deletions precli/rules/python/stdlib/hashlib_weak_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if used_for_security is True:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(call.name_qualified),
Expand All @@ -157,7 +156,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if isinstance(hash_name, str) and hash_name.lower() in WEAK_HASHES:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(hash_name),
Expand All @@ -176,7 +174,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if used_for_security is True:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(name),
Expand Down
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/hmac_timing_attack.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=comparison.operator_node),
level=Level.ERROR,
fixes=fixes,
Expand Down
2 changes: 0 additions & 2 deletions precli/rules/python/stdlib/hmac_weak_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
) or digestmod in HASHLIB_WEAK_HASHES:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=argument.node),
level=Level.ERROR,
message=self.message.format(digestmod),
Expand All @@ -140,7 +139,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
) or digest in HASHLIB_WEAK_HASHES:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=argument.node),
level=Level.ERROR,
message=self.message.format(digest),
Expand Down
3 changes: 1 addition & 2 deletions precli/rules/python/stdlib/imaplib_cleartext.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Secure Saurce LLC
# Copyright 2024 Secure Saurce LLC
r"""
=====================================================================
Cleartext Transmission of Sensitive Information in the Imaplib Module
Expand Down Expand Up @@ -116,7 +116,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.identifier_node),
level=Level.ERROR,
message=f"The '{call.name_qualified}' function will "
Expand Down
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/json_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
"""
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(call.name_qualified),
)
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if call.get_argument(position=1, name="verify").value is None:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(call.name_qualified),
)
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/marshal_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
"""
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(call.name_qualified),
)
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/nntplib_cleartext.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.identifier_node),
level=Level.ERROR,
message=f"The '{call.name_qualified}' function will "
Expand Down
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/pickle_load.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
]:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(call.name_qualified),
)
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/poplib_cleartext.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.identifier_node),
level=Level.ERROR,
message=f"The '{call.name_qualified}' function will "
Expand Down
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/shelve_open.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if call.name_qualified in ["shelve.open", "shelve.DbfilenameShelf"]:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(call.name_qualified),
)
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/smtplib_cleartext.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.identifier_node),
level=Level.ERROR,
message=f"The '{call.name_qualified}' function will "
Expand Down
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/ssl_context_weak_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=arg.node),
level=Level.ERROR if key_size < 160 else Level.WARNING,
message=self.message.format("EC", 224),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(call.name_qualified),
fixes=fixes,
Expand Down
5 changes: 1 addition & 4 deletions precli/rules/python/stdlib/ssl_insecure_tls_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Secure Saurce LLC
# Copyright 2024 Secure Saurce LLC
r"""
=======================================================
Inadequate Encryption Strength Using Weak SSL Protocols
Expand Down Expand Up @@ -124,7 +124,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=argument.identifier_node),
level=Level.ERROR,
message=self.message.format(version),
Expand Down Expand Up @@ -165,7 +164,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=argument.identifier_node),
level=Level.ERROR,
message=self.message.format(version),
Expand Down Expand Up @@ -193,7 +191,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
)
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=argument.identifier_node),
level=Level.ERROR,
message=self.message.format(protocol),
Expand Down
1 change: 0 additions & 1 deletion precli/rules/python/stdlib/telnetlib_cleartext.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:
if call.name_qualified in ["telnetlib.Telnet"]:
return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
level=Level.ERROR,
message=self.message.format(call.name_qualified),
Expand Down
3 changes: 1 addition & 2 deletions precli/rules/python/stdlib/tempfile_mktemp_race_condition.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2023 Secure Saurce LLC
# Copyright 2024 Secure Saurce LLC
r"""
==============================================
Insecure Temporary File in the Tempfile Module
Expand Down Expand Up @@ -178,7 +178,6 @@ def analyze(self, context: dict, **kwargs: dict) -> Result:

return Result(
rule_id=self.id,
artifact=context["artifact"],
location=Location(node=call.function_node),
message=self.message.format(file_arg.value),
fixes=fixes,
Expand Down

0 comments on commit 8d9a9be

Please sign in to comment.