Skip to content

Commit

Permalink
Move some configuration into the docs themselves (#655)
Browse files Browse the repository at this point in the history
For convenience and for more clarity to end users, having the
configuration in the docs themselves can aid how each rule is
configured.

Later, when precli handles a configuration file mechanism, a user can
customize rules based on this config noted in the docs in each rule.

Signed-off-by: Eric Brown <eric.brown@securesauce.dev>
  • Loading branch information
ericwb authored Oct 24, 2024
1 parent 9f2bdc0 commit 2a70a6c
Show file tree
Hide file tree
Showing 119 changed files with 702 additions and 277 deletions.
17 changes: 17 additions & 0 deletions precli/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ def __init__(
enabled: bool = True,
level: Level = Level.WARNING,
rank: float = -1.0,
parameters: dict = None,
):
self._enabled = enabled
self._level = level
self._rank = rank
self._parameters = parameters

@property
def enabled(self) -> bool:
Expand All @@ -29,7 +31,22 @@ def level(self) -> Level:
"""The default severity level."""
return self._level

@level.setter
def level(self, level: Level):
"""Set the default severity level."""
self._level = level

@property
def rank(self) -> float:
"""The default rank for the rule."""
return self._rank

@property
def parameters(self) -> dict:
"""A dictionary of default parameters."""
return self._parameters

@parameters.setter
def parameters(self, parameters: dict):
"""Set the dictionary of default parameters."""
self._parameters = parameters
2 changes: 1 addition & 1 deletion precli/core/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(
self._artifact = artifact
self._kind = kind
rule = Rule.get_by_id(self._rule_id)
default_config = rule.default_config if rule else None
default_config = rule.config if rule else None
self._rank = default_config.rank if default_config else -1.0
if level:
self._level = level
Expand Down
6 changes: 3 additions & 3 deletions precli/renderers/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ def create_rule_if_needed(
text=rule.short_description
),
default_configuration=sarif_om.ReportingConfiguration(
enabled=rule.default_config.enabled,
level=rule.default_config.level.name.lower(),
enabled=rule.config.enabled,
level=rule.config.level.name.lower(),
),
help=sarif_om.MultiformatMessageString(
text=rule.full_description, markdown=rule.full_description
Expand All @@ -87,7 +87,7 @@ def create_rule_if_needed(
"security",
f"external/cwe/cwe-{rule.cwe.id}",
],
"security-severity": (rule.default_config.level.to_severity()),
"security-severity": (rule.config.level.to_severity()),
},
)

Expand Down
26 changes: 24 additions & 2 deletions precli/rules/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# Copyright 2024 Secure Sauce LLC
import re
import sys
from abc import ABC
from typing import Optional

if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib

from precli.core.config import Config
from precli.core.cwe import Cwe
from precli.core.fix import Fix
from precli.core.level import Level
from precli.core.location import Location


Expand Down Expand Up @@ -41,7 +49,21 @@ def __init__(
self._cwe = Cwe(cwe_id)
self._message = message
self._wildcards = wildcards if wildcards else {}
self._config = Config() if not config else config

match = re.search(r"```toml(.*?)```", description, re.DOTALL)
if match:
toml_content = match.group(1).strip()
try:
metadata = tomllib.loads(toml_content)
self._config = Config()
self._config.enabled = metadata.get("enabled")
self._config.level = Level(metadata.get("level"))
self._config.parameters = metadata.get("parameters")
except tomllib.TOMLDecodeError as err:
print(err)
print("Invalid config in documentation")
else:
self._config = Config() if not config else config
self._enabled = self._config.enabled
self._help_url = f"https://docs.securesauce.dev/rules/{id}"
Rule._rules[id] = self
Expand Down Expand Up @@ -89,7 +111,7 @@ def help_url(self) -> str:
return self._help_url

@property
def default_config(self) -> Config:
def config(self) -> Config:
"""Default configuration for this rule."""
return self._config

Expand Down
10 changes: 7 additions & 3 deletions precli/rules/go/stdlib/crypto_weak_cipher.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@
}
```
# Default Configuration
```toml
enabled = true
level = "error"
```
# See also
!!! info
Expand All @@ -125,8 +132,6 @@
from typing import Optional

from precli.core.call import Call
from precli.core.config import Config
from precli.core.level import Level
from precli.core.location import Location
from precli.core.result import Result
from precli.rules import Rule
Expand All @@ -141,7 +146,6 @@ def __init__(self, id: str):
cwe_id=327,
message="Weak ciphers like '{0}' should be avoided due to their "
"known vulnerabilities and weaknesses.",
config=Config(level=Level.ERROR),
)

def analyze_call_expression(
Expand Down
10 changes: 7 additions & 3 deletions precli/rules/go/stdlib/crypto_weak_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@
}
```
# Default Configuration
```toml
enabled = true
level = "error"
```
# See also
!!! info
Expand All @@ -76,8 +83,6 @@
from typing import Optional

from precli.core.call import Call
from precli.core.config import Config
from precli.core.level import Level
from precli.core.location import Location
from precli.core.result import Result
from precli.rules import Rule
Expand All @@ -92,7 +97,6 @@ def __init__(self, id: str):
cwe_id=328,
message="Use of weak hash function '{0}' does not meet security "
"expectations.",
config=Config(level=Level.ERROR),
)

def analyze_call_expression(
Expand Down
28 changes: 21 additions & 7 deletions precli/rules/go/stdlib/crypto_weak_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@
}
```
# Default Configuration
```toml
enabled = true
level = "warning"
parameters.warning_key_size = 2048
parameters.error_key_size = 1024
```
# See also
!!! info
Expand Down Expand Up @@ -152,6 +161,9 @@ def __init__(self, id: str):
def analyze_call_expression(
self, context: dict, call: Call
) -> Optional[Result]:
WARN_SIZE = self.config.parameters.get("warning_key_size")
ERR_SIZE = self.config.parameters.get("error_key_size")

if call.name_qualified in ["crypto/dsa.GenerateParameters"]:
argument = call.get_argument(position=2)
sizes = argument.value
Expand All @@ -160,33 +172,35 @@ def analyze_call_expression(
fixes = Rule.get_fixes(
context=context,
deleted_location=Location(node=argument.identifier_node),
description="Use a minimum key size of 2048 for DSA keys.",
description=f"Use a minimum key size of {WARN_SIZE} for "
"DSA keys.",
inserted_content="L2048N224",
)

return Result(
rule_id=self.id,
location=Location(node=argument.identifier_node),
level=Level.ERROR,
message=self.message.format("DSA", 2048),
message=self.message.format("DSA", WARN_SIZE),
fixes=fixes,
)
elif call.name_qualified in ["crypto/rsa.GenerateKey"]:
argument = call.get_argument(position=1)
bits = argument.value

if isinstance(bits, int) and bits < 2048:
if isinstance(bits, int) and bits < WARN_SIZE:
fixes = Rule.get_fixes(
context=context,
deleted_location=Location(node=argument.node),
description="Use a minimum key size of 2048 for RSA keys.",
inserted_content="2048",
description=f"Use a minimum key size of {WARN_SIZE} for "
"RSA keys.",
inserted_content=f"{WARN_SIZE}",
)

return Result(
rule_id=self.id,
location=Location(node=argument.node),
level=Level.ERROR if bits <= 1024 else Level.WARNING,
message=self.message.format("RSA", 2048),
level=Level.ERROR if bits <= ERR_SIZE else Level.WARNING,
message=self.message.format("RSA", WARN_SIZE),
fixes=fixes,
)
10 changes: 7 additions & 3 deletions precli/rules/go/stdlib/syscall_setuid_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@
}
```
# Default Configuration
```toml
enabled = true
level = "error"
```
## See also
!!! info
Expand All @@ -91,8 +98,6 @@
from typing import Optional

from precli.core.call import Call
from precli.core.config import Config
from precli.core.level import Level
from precli.core.location import Location
from precli.core.result import Result
from precli.rules import Rule
Expand All @@ -107,7 +112,6 @@ def __init__(self, id: str):
cwe_id=250,
message="The function '{0}(0)' escalates the process to run with "
"root (superuser) privileges.",
config=Config(level=Level.ERROR),
)

def analyze_call_expression(
Expand Down
7 changes: 7 additions & 0 deletions precli/rules/java/stdlib/java_net_insecure_cookie.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@
}
```
# Default Configuration
```toml
enabled = true
level = "warning"
```
# See also
!!! info
Expand Down
26 changes: 19 additions & 7 deletions precli/rules/java/stdlib/java_security_weak_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@
}
```
# Default Configuration
```toml
enabled = true
level = "error"
parameters.weak_hashes = [
"MD2",
"MD5",
"SHA",
"SHA1",
"SHA-1",
]
```
# See also
!!! info
Expand All @@ -71,16 +85,11 @@
from typing import Optional

from precli.core.call import Call
from precli.core.config import Config
from precli.core.level import Level
from precli.core.location import Location
from precli.core.result import Result
from precli.rules import Rule


WEAK_HASHES = ("MD2", "MD5", "SHA", "SHA1", "SHA-1")


class MessageDigestWeakHash(Rule):
def __init__(self, id: str):
super().__init__(
Expand All @@ -95,7 +104,6 @@ def __init__(self, id: str):
"MessageDigest",
],
},
config=Config(level=Level.ERROR),
)

def analyze_method_invocation(
Expand All @@ -109,7 +117,11 @@ def analyze_method_invocation(
argument = call.get_argument(position=0)
algorithm = argument.value_str

if algorithm is None or algorithm.upper() not in WEAK_HASHES:
if (
algorithm is None
or algorithm.upper()
not in self.config.parameters.get("weak_hashes")
):
return

fixes = Rule.get_fixes(
Expand Down
Loading

0 comments on commit 2a70a6c

Please sign in to comment.