Creating Rule Transformations

Rule transformations are implemented by defined a class which inherits from the Transformation class. A transformation defines two methods for making modifications to the rule as a whole and/or individual condition expressions.

A single transformation can be used to transform both rules and their internal conditional expressions.

Configuration

Transformations can define custom configuration which must be provided when instantiating the transformation class. This normally looks like key-value pairs alongside the transformation type in the serializer configuration:

Example serializer configuration
transforms:
  - type: package.module:CustomTransform
    title_format: "{} - MODIFIED"
    extra_tags:
      - custom_tag

The exact format and types for this configuration is defined by the Schema class within your transformation. The below example shows how to setup a configuration schema which matches the above YAML.

Custom transformation with required configuration
from typing import List

from sigma.transform import Transformation
from sigma.schema import RuleTag
from sigma.utils import CopyableSchema

class CustomTransform(Transformation):

    class Schema(Transformation.Schema):
        title_format: str
        extra_tags: List[RuleTag]

        class Config(CopyableSchema):
            schema_extra = Transformation.Schema.Config.copy_schema(example_extra={
                "title_format": "My Title Format: {}",
                "extra_tags": [ "attack.t12345", "custom tag" ]
            })

The Schema class must inherit from Transformation.Schema which is a pydantic model.

Note

Defining Config.schema_extra is not required, but doing so allows users to utilize the sigma schema transform --examples command/options to view example configuration for your custom transformation.

Note

Using the CopyableSchema base class allows future users to who may extend your transform to easily extend your own example schemas as well. The Transformation.Schema.Config <sigma.transform.Transformation.Schema.Config class is an example which provides the copy_schema method to duplicate the schema_extra field with extra example data easily.

Note

Defining a custom Schema class is not required. You can omit this class if your transformation does not require any special configuration.

You can access the configuration values at runtime via the Transformation.schema property (i.e. in either transform_expression or transform_rule, you can use self.schema which will be an instance of your custom Schema class)

Rule Transformation

Modifying high-level rule properties is relatively straightforward. You define the transform_rule() method, which takes a Rule object, and return a modified version of the the rule. This modified version can either be a reference to the same rule with modified properties or an entirely different rule.

A transformation which modifies the rule title and tags
from sigma.transform import Transformation
from sigma.schema import Rule

class CustomTransform(Transformation):

    def transform_rule(rule: Rule) -> Rule:
        """ Modify the rule by changing the title and adding a new tag """

        rule.title = f"MODIFIED: {rule.title}"
        rule.tags.append(RuleTag("attack.t12345"))

        return rule

Expression Transformation

To modify the conditional expression, you must define the transform_expression() method. This method takes a reference to the rule being modified as well as the specific expression. For a given rule, this method is called for each expression recursively. See sigma.grammar for a list of possible grammar classes and their meaning. As with the rule transformation, you must return a reference to a modified expression. This returned expression can be the same expression object with modified properties, or a completely new expression.

A transformation which swaps all AND/OR statements (don’t do this)
from sigma.transform import Transformation
from sigma.schema import Rule
from sigma.grammar import Expression, LogicalOr, LogicalAnd

class CustomTransform(Transformation):

    def transform_expression(rule: Rule, expression: Expression) -> Expression:
        """ Modify the rule by changing the title and adding a new tag """

        if isinstance(expression, LogicalOr):
            return LogicalAnd(args=expression.args)
        elif isinstance(expression, LogicalAnd):
            return LogicalOr(args=expression.args)
        else:
            # Don't modify non AND/OR expressions
            return expression