PyPanther Detections Style Guide
Last updated
Was this helpful?
Last updated
Was this helpful?
In your code repository where your PyPanther Detections are stored, it's recommended to:
Maintain a top-level module, content
, in which all of your custom Python code is stored (except for the main.py
file).
The top-level directory we are calling content/
can be named anything except src/
, which is a reserved repository name in Panther.
Within this folder, it's recommended to:
Store custom rule definitions in a rules
directory.
Store logic that makes overrides on Panther-managed rules in an overrides
directory.
Define an in each file in overrides
.
Store custom helpers in a helpers
directory.
main.py
content recommendationsIt's recommended for your main.py
file to:
Import Panther-managed rules (which you do or don't want to make overrides on) using get_panther_rules
Import custom rules using get_rules
apply_overrides()
The pypanther
apply_overrides()
convenience function lets you, in main.py
, efficiently apply all detection overrides made in a separate file or folder.
In the following example, the apply_overrides()
functions in general.py
and aws_cloudtrail.py
are applied when apply_overrides(overrides, all_rules)
is called in main.py
:
rule()
upgrade()
or downgrade()
in severity()
While each PyPanther rule must define a default_severity
, it can also define a severity()
function, whose value overrides default_severity
to set the severity level of resulting alerts.
Within severity()
, it's common practice to dynamically set the severity based on an event field value. One way to do this is to add some condition, which, if satisfied, returns a hard-coded Severity
value. In the example below, if the actor associated to the event has an admin role, Severity.MEDIUM
is returned, regardless of the value of self.default_severity
:
In the example above, if MyRule
's default_severity
was ever changed from Severity.LOW
to, say, Severity.MEDIUM
, you would also need to remember to update the Severity.MEDIUM
in severity()
(presumably to Severity.HIGH
), to preserve the escalation.
upgrade()
instead of hard-coding a SeverityInstead of setting the return value of severity()
as a hard-coded Severity
value (as is shown in the example above), it's recommended to call the Severity
class's upgrade()
and downgrade()
functions on self.default_severity
.
In this model, if your detection's default_severity
value ever changes, you won't also need to make changes in the severity()
function. In the example below, default_severity
has been changed to Severity.MEDIUM
, and the return value within the condition automatically adjusts to returning Severity.HIGH
because it uses upgrade()
:
There may be rare instances when using a static value within severity()
is preferable—for example, you may always want to return Severity.INFO
when an event originates in a dev
account, even if the default_severity
later changes. This might look like:
The pypanther
base Rule
class is typed, so when you create a custom detection (i.e., inherit from Rule
), explicit typing is optional. Still, if you prefer to use explicit types, you can do so.
If you defined apply_overrides
functions, call pypanther
's apply_overrides()
to apply all of your changes. Learn more in , below.
apply_overrides()
takes an imported package (folder) or module (file) name and an optional list of rules, and runs all functions named apply_overrides()
from that package or module against the list of rules. (This is similar to how works.) It's recommended to name this package or module overrides
.
If you would like to alter the logic of a Panther-managed PyPanther Detection, it's recommended to use instead of the rule's rule()
function. Filters are designed for this purpose—to be applied on top of existing rule logic. They are executed against each incoming event before the rule()
logic, in order to determine if the rule should indeed process the event.
If you are significantly altering the rule logic, you might also consider instead.
In order to use self.default_severity.upgrade()
or self.default_severity.downgrade()
, the detection's default_severity
value must be a object, not a string literal.