Skip to content

ADT

psengine.classic_alerts.classic_alert

AlertRuleOut

Bases: RFBaseModel

Validate data received from v2/alert/rule.

created instance-attribute

created: datetime

enabled instance-attribute

enabled: bool

id_ class-attribute instance-attribute

id_: str = Field(alias='id')

intelligence_goals instance-attribute

intelligence_goals: list[IdName]

model_config class-attribute instance-attribute

model_config = ConfigDict(
    extra=get('RF_MODEL_EXTRA', 'ignore')
)

notification_settings instance-attribute

notification_settings: NotificationSettings

owner instance-attribute

owner: IdName

priority class-attribute instance-attribute

priority: bool = None

tags class-attribute instance-attribute

tags: list[IdNameTypeDescription] = None

title instance-attribute

title: str

json

json(
    by_alias: bool = True,
    exclude_none: bool = True,
    auto_exclude_unset: bool = True,
    **kwargs,
)

JSON representation of models. It is inherited by every model.

PARAMETER DESCRIPTION
by_alias

Alias flag:

  • If True, writes fields with their API alias (e.g., IpAddress)
  • If False uses the Python attribute name alias.

TYPE: bool DEFAULT: True

exclude_none

Whether to exclude fields equal to None.

TYPE: bool DEFAULT: True

auto_exclude_unset

Whether to auto exclude values not set.

  • If True, uses RF_EXTRA_MODEL config to decide inclusion of unmapped fields.
  • If False, you must specify exclude_unset manually.

TYPE: bool DEFAULT: True

Source code in psengine/common_models.py
def json(
    self,
    by_alias: Annotated[
        bool,
        Doc(
            """
            Alias flag:

            - If `True`, writes fields with their API alias (e.g., `IpAddress`)
            - If `False` uses the Python attribute name alias.
            """
        ),
    ] = True,
    exclude_none: Annotated[bool, Doc('Whether to exclude fields equal to None.')] = True,
    auto_exclude_unset: Annotated[
        bool,
        Doc("""
            Whether to auto exclude values not set.

            - If `True`, uses `RF_EXTRA_MODEL` config to decide inclusion of unmapped fields.
            - If `False`, you must specify `exclude_unset` manually.
            """),
    ] = True,
    **kwargs,
):
    """JSON representation of models. It is inherited by every model."""
    if not auto_exclude_unset and kwargs.get('exclude_unset') is None:
        raise ValueError('`auto_exclude_unset` is False, `exclude_unset has to be provided`')

    exclude_unset = (
        bool(self.model_config['extra'] != 'allow')
        if auto_exclude_unset
        else kwargs['exclude_unset']
    )
    kwargs['exclude_unset'] = exclude_unset
    return self.model_dump(mode='json', by_alias=by_alias, exclude_none=exclude_none, **kwargs)

ClassicAlert

Bases: RFBaseModel

Validate data received from the /v3/alerts/{id} endpoint.

This class supports hashing, equality comparison, string representation, and total ordering of ClassicAlert instances.

Hashing

Returns a hash value based on the id_.

Equality

Checks equality between two ClassicAlert instances based on their id_.

Greater-than Comparison

Defines a greater-than comparison between two ClassicAlert instances based on their log triggered timestamp.

String Representation

Returns a string representation of the ClassicAlert instance including the id_, triggered timestamp, title, and alerting rule name.

>>> print(alert_id_response)
Classic Alert ID: a123, Triggered: 2024-05-21 10:42:30AM, Title: Example Alert
Total ordering

The ordering of ClassicAlert instances is determined primarily by the log triggered timestamp. If two instances have the same triggered timestamp, their id_ is used as a secondary criterion.

ai_insights class-attribute instance-attribute

ai_insights: Optional[AlertAiInsight] = None

enriched_entities class-attribute instance-attribute

enriched_entities: Optional[list[EnrichedEntity]] = None

hits class-attribute instance-attribute

hits: Optional[list[ClassicAlertHit]] = None

id_ class-attribute instance-attribute

id_: str = Field(alias='id')

images property

images: dict

Return a dictionary of images if the alert has any.

Example:

1
2
3
4
{
    image_id: image_bytes,
    image_id: image_bytes
}

log instance-attribute

log: AlertLog

model_config class-attribute instance-attribute

model_config = ConfigDict(
    extra=get('RF_MODEL_EXTRA', 'ignore')
)

owner_organisation_details class-attribute instance-attribute

owner_organisation_details: Optional[
    OwnerOrganisationDetails
] = None

review class-attribute instance-attribute

review: Optional[AlertReview] = None

rule class-attribute instance-attribute

rule: Optional[AlertDeprecation] = None

title instance-attribute

title: str

triggered_by class-attribute instance-attribute

triggered_by: Optional[list[TriggeredBy]] = None

type_ class-attribute instance-attribute

type_: str = Field(alias='type', default=None)

url class-attribute instance-attribute

url: Optional[AlertURL] = None

__eq__

__eq__(other: ClassicAlert)
Source code in psengine/classic_alerts/classic_alert.py
def __eq__(self, other: 'ClassicAlert'):
    return self.id_ == other.id_

__gt__

__gt__(other: ClassicAlert)
Source code in psengine/classic_alerts/classic_alert.py
def __gt__(self, other: 'ClassicAlert'):
    return self.log.triggered > other.log.triggered

__hash__

__hash__()
Source code in psengine/classic_alerts/classic_alert.py
def __hash__(self):
    return hash(self.id_)

__str__

__str__()
Source code in psengine/classic_alerts/classic_alert.py
def __str__(self):
    return (
        f'Classic Alert ID: {self.id_}, '
        f'Triggered: {self.log.triggered.strftime(TIMESTAMP_STR)}, '
        f'Title: {self.title}, Alerting Rule: {self.rule.name}'
    )

json

json(
    by_alias: bool = True,
    exclude_none: bool = True,
    auto_exclude_unset: bool = True,
    **kwargs,
)

JSON representation of models. It is inherited by every model.

PARAMETER DESCRIPTION
by_alias

Alias flag:

  • If True, writes fields with their API alias (e.g., IpAddress)
  • If False uses the Python attribute name alias.

TYPE: bool DEFAULT: True

exclude_none

Whether to exclude fields equal to None.

TYPE: bool DEFAULT: True

auto_exclude_unset

Whether to auto exclude values not set.

  • If True, uses RF_EXTRA_MODEL config to decide inclusion of unmapped fields.
  • If False, you must specify exclude_unset manually.

TYPE: bool DEFAULT: True

Source code in psengine/common_models.py
def json(
    self,
    by_alias: Annotated[
        bool,
        Doc(
            """
            Alias flag:

            - If `True`, writes fields with their API alias (e.g., `IpAddress`)
            - If `False` uses the Python attribute name alias.
            """
        ),
    ] = True,
    exclude_none: Annotated[bool, Doc('Whether to exclude fields equal to None.')] = True,
    auto_exclude_unset: Annotated[
        bool,
        Doc("""
            Whether to auto exclude values not set.

            - If `True`, uses `RF_EXTRA_MODEL` config to decide inclusion of unmapped fields.
            - If `False`, you must specify `exclude_unset` manually.
            """),
    ] = True,
    **kwargs,
):
    """JSON representation of models. It is inherited by every model."""
    if not auto_exclude_unset and kwargs.get('exclude_unset') is None:
        raise ValueError('`auto_exclude_unset` is False, `exclude_unset has to be provided`')

    exclude_unset = (
        bool(self.model_config['extra'] != 'allow')
        if auto_exclude_unset
        else kwargs['exclude_unset']
    )
    kwargs['exclude_unset'] = exclude_unset
    return self.model_dump(mode='json', by_alias=by_alias, exclude_none=exclude_none, **kwargs)

markdown

markdown(
    owner_org: bool = False,
    ai_insights: bool = True,
    fragment_entities: bool = True,
    triggered_by: bool = True,
    html_tags: bool = False,
    character_limit: Optional[int] = None,
    defang_iocs: bool = False,
) -> str

Return a markdown string representation of the ClassicAlert instance.

PARAMETER DESCRIPTION
owner_org

Include owner org details.

TYPE: bool DEFAULT: False

ai_insights

Include AI insights.

TYPE: bool DEFAULT: True

fragment_entities

Include fragment entities.

TYPE: bool DEFAULT: True

triggered_by

Include triggered by.

TYPE: bool DEFAULT: True

html_tags

Include HTML tags in the markdown.

TYPE: bool DEFAULT: False

character_limit

Character limit for the markdown.

TYPE: Optional[int] DEFAULT: None

defang_iocs

Defang IOCs in hits.

TYPE: bool DEFAULT: False

Note

This function works on ClassicAlert instances returned by ClassicAlertMgr.fetch(). If you are passing the result of ClassicAlertMgr.search(), make sure the search method has been called with all the fields. Keep in mind that this will make the search slower.

RAISES DESCRIPTION
AlertMarkdownError

If fields are not available.

RETURNS DESCRIPTION
str

Markdown representation of the alert.

Source code in psengine/classic_alerts/classic_alert.py
def markdown(
    self,
    owner_org: Annotated[bool, Doc('Include owner org details.')] = False,
    ai_insights: Annotated[bool, Doc('Include AI insights.')] = True,
    fragment_entities: Annotated[bool, Doc('Include fragment entities.')] = True,
    triggered_by: Annotated[bool, Doc('Include triggered by.')] = True,
    html_tags: Annotated[bool, Doc('Include HTML tags in the markdown.')] = False,
    character_limit: Annotated[Optional[int], Doc('Character limit for the markdown.')] = None,
    defang_iocs: Annotated[bool, Doc('Defang IOCs in hits.')] = False,
) -> Annotated[str, Doc('Markdown representation of the alert.')]:
    """Return a markdown string representation of the `ClassicAlert` instance.

    Note:
        This function works on `ClassicAlert` instances returned by `ClassicAlertMgr.fetch()`.
        If you are passing the result of `ClassicAlertMgr.search()`, make sure the `search`
        method has been called with all the fields. Keep in mind that this will make the
        `search` slower.

    Raises:
        AlertMarkdownError: If fields are not available.
    """
    return _markdown_alert(
        self,
        owner_org=owner_org,
        ai_insights=ai_insights,
        fragment_entities=fragment_entities,
        triggered_by=triggered_by,
        html_tags=html_tags,
        character_limit=character_limit,
        defang_iocs=defang_iocs,
    )

parse_trigger_by classmethod

parse_trigger_by(data: list[dict]) -> list[dict]

Parse a list of data dictionaries to extract and format entity paths.

Each entity path is transformed into a formatted string where each entity is represented as EntityName (EntityType), joined by ->.

If an entity's type is MetaType, it is formatted as Any EntityName instead.

Example:

>>> print(parse_triggered_by([
    {
        'reference_id': '123',
        'entity_paths': [
            [
                {'entity': {'name': 'URL1', 'type': 'URL'}},
                {'entity': {'name': 'Domain1', 'type': 'InternetDomainName'}}
            ],
            [
                {'entity': {'name': 'URL1', 'type': 'URL'}},
                {'entity': {'name': 'Domain1', 'type': 'InternetDomainName'}}
            ]
        ]
    }
])
[
    {
        'reference_id': '123',
        'triggered_by_strings': [
            'URL1 (URL) -> Domain1 (InternetDomainName)'
        ]
    }
]

PARAMETER DESCRIPTION
data

List of dicts, each containing a reference_id and an entity_paths list.

TYPE: list[dict]

RETURNS DESCRIPTION
list[dict]

List of dicts with reference_id and a list of unique formatted triggered_by_strings paths.

Source code in psengine/classic_alerts/classic_alert.py
@field_validator('triggered_by', mode='before')
@classmethod
def parse_trigger_by(
    cls,
    data: Annotated[
        list[dict],
        Doc('List of dicts, each containing a `reference_id` and an `entity_paths` list.'),
    ],
) -> Annotated[
    list[dict],
    Doc("""
        List of dicts with `reference_id` and a list of unique formatted
        `triggered_by_strings` paths.
        """),
]:
    """Parse a list of data dictionaries to extract and format entity paths.

    Each entity path is transformed into a formatted string where each entity is represented as
    `EntityName (EntityType)`, joined by ` -> `.

    If an entity's type is `MetaType`, it is formatted as `Any EntityName` instead.

    Example:
    ```python
    >>> print(parse_triggered_by([
        {
            'reference_id': '123',
            'entity_paths': [
                [
                    {'entity': {'name': 'URL1', 'type': 'URL'}},
                    {'entity': {'name': 'Domain1', 'type': 'InternetDomainName'}}
                ],
                [
                    {'entity': {'name': 'URL1', 'type': 'URL'}},
                    {'entity': {'name': 'Domain1', 'type': 'InternetDomainName'}}
                ]
            ]
        }
    ])
    [
        {
            'reference_id': '123',
            'triggered_by_strings': [
                'URL1 (URL) -> Domain1 (InternetDomainName)'
            ]
        }
    ]
    ```
    """
    result = []
    for item in data:
        reference_id = item.get('reference_id')
        entity_paths = item.get('entity_paths', [])
        seen_strings = set()
        to_string = []

        for path in entity_paths:
            formatted_entities = [
                (
                    f'Any {entity["name"]}'
                    if entity.get('type') == 'MetaType'
                    else f'{entity["name"]} ({entity["type"]})'
                )
                for obj in path
                if (entity := obj.get('entity'))
            ]
            parsed_string = ' -> '.join(formatted_entities)
            if parsed_string not in seen_strings:
                seen_strings.add(parsed_string)
                to_string.append(parsed_string)

        result.append({'reference_id': reference_id, 'triggered_by_strings': to_string})
    return result

store_image

store_image(image_id: str, image_bytes: bytes) -> None

Store the image ID and image bytes in the @images dictionary.

Example:

1
2
3
4
{
    image_id: image_bytes,
    image_id: image_bytes
}

PARAMETER DESCRIPTION
image_id

The image ID.

TYPE: str

image_bytes

The image bytes.

TYPE: bytes

Source code in psengine/classic_alerts/classic_alert.py
def store_image(
    self,
    image_id: Annotated[str, Doc('The image ID.')],
    image_bytes: Annotated[bytes, Doc('The image bytes.')],
) -> None:
    """Store the image ID and image bytes in the `@images` dictionary.

    Example:
    ```python
    {
        image_id: image_bytes,
        image_id: image_bytes
    }
    ```
    """
    self._images[image_id] = image_bytes

triggered_by_from_hit

triggered_by_from_hit(hit: ClassicAlertHit) -> list[str]

From an Alert Hit block, returns the related Triggered By string representation.

Source code in psengine/classic_alerts/classic_alert.py
def triggered_by_from_hit(self, hit: ClassicAlertHit) -> list[str]:
    """From an Alert Hit block, returns the related Triggered By string representation."""
    return list(
        chain.from_iterable(
            t.triggered_by_strings for t in self.triggered_by if t.reference_id == hit.id_
        )
    )