Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions contentctl/actions/detection_testing/GitService.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,13 @@ def getChanges(self, target_branch: str) -> List[Detection]:
# Filter to find the Lookup Object the references this CSV
matched = list(
filter(
lambda x: isinstance(x, CSVLookup)
and not isinstance(
x, RuntimeCSV
) # RuntimeCSV is not used directly by any content
and x.filename == decoded_path,
lambda x: (
isinstance(x, CSVLookup)
and not isinstance(
x, RuntimeCSV
) # RuntimeCSV is not used directly by any content
and x.filename == decoded_path
),
self.director.lookups,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def setup(self):

# Init the list of setup functions we always need
primary_setup_functions: list[
tuple[Callable[[], None | client.Service], str]
tuple[Callable[[], client.Service | None], str]
] = [
(self.start, "Starting"),
(self.get_conn, "Waiting for App Installation"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def severity(self) -> RiskSeverity:
f"Error getting severity - risk_score must be between 0-100, but was actually {self.risk_score}"
)

explanation: None | str = Field(
explanation: str | None = Field(
default=None,
exclude=True, # Don't serialize this value when dumping the object
description="Provide an explanation to be included "
Expand Down
2 changes: 1 addition & 1 deletion contentctl/objects/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class BaseTest(BaseModel, ABC):
latest_time: Union[str, None] = None

# The test result
result: Union[None, BaseTestResult] = None
result: Union[BaseTestResult, None] = None

@abstractmethod
def skip(self, message: str) -> None:
Expand Down
4 changes: 2 additions & 2 deletions contentctl/objects/base_test_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class BaseTestResult(BaseModel):
"""

# Message for the result
message: Union[None, str] = None
message: Union[str, None] = None

# Any exception that was raised (may be None)
exception: Union[Exception, None] = None
Expand All @@ -53,7 +53,7 @@ class BaseTestResult(BaseModel):
job_content: Union[Record, None] = None

# The Splunk endpoint URL
sid_link: Union[None, str] = None
sid_link: Union[str, None] = None

# Needed to allow for embedding of Exceptions in the model
model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)
Expand Down
10 changes: 5 additions & 5 deletions contentctl/objects/data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ class DataSource(SecurityContentObject):
source: str = Field(...)
sourcetype: str = Field(...)
separator: Optional[str] = None
separator_value: None | str = None
separator_value: str | None = None
configuration: Optional[str] = None
supported_TA: list[TA] = []
fields: None | list = None
field_mappings: None | list = None
fields: list | None = None
field_mappings: list | None = None
mitre_components: list[str] = []
convert_to_log_source: None | list = None
example_log: None | str = None
convert_to_log_source: list | None = None
example_log: str | None = None
output_fields: list[str] = []
status: ContentStatus = ContentStatus.production

Expand Down
4 changes: 2 additions & 2 deletions contentctl/objects/drilldown.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ class Drilldown(BaseModel):
description="The text of a drilldown search. This must be valid SPL.",
min_length=1,
)
earliest_offset: None | str = Field(
earliest_offset: str | None = Field(
...,
description="Earliest offset time for the drilldown search. "
f"The most common value for this field is '{EARLIEST_OFFSET}', "
"but it is NOT the default value and must be supplied explicitly.",
min_length=1,
)
latest_offset: None | str = Field(
latest_offset: str | None = Field(
...,
description="Latest offset time for the driolldown search. "
f"The most common value for this field is '{LATEST_OFFSET}', "
Expand Down
6 changes: 3 additions & 3 deletions contentctl/objects/lookup.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ class Lookup(SecurityContentObject, abc.ABC):
match_type: list[Annotated[str, Field(pattern=r"(^WILDCARD|CIDR)\(.+\)$")]] = Field(
default=[]
)
min_matches: None | NonNegativeInt = Field(default=None)
max_matches: None | Annotated[NonNegativeInt, Field(ge=1, le=1000)] = Field(
min_matches: NonNegativeInt | None = Field(default=None)
max_matches: Annotated[NonNegativeInt, Field(ge=1, le=1000)] | None = Field(
default=None
)
case_sensitive_match: None | bool = Field(default=None)
case_sensitive_match: bool | None = Field(default=None)
status: ContentStatus = ContentStatus.production

@field_validator("status", mode="after")
Expand Down
8 changes: 4 additions & 4 deletions contentctl/objects/mitre_attack_enrichment.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class AttackGroupType(StrEnum):
class MitreExternalReference(BaseModel):
model_config = ConfigDict(extra="forbid")
source_name: str
external_id: None | str = None
url: None | HttpUrl = None
description: None | str = None
external_id: str | None = None
url: HttpUrl | None = None
description: str | None = None


class MitreAttackGroup(BaseModel):
Expand All @@ -58,7 +58,7 @@ class MitreAttackGroup(BaseModel):
group_id: str
id: str
matrix: list[AttackGroupMatrix]
mitre_attack_spec_version: None | str
mitre_attack_spec_version: str | None
mitre_version: str
# assume that if the deprecated field is not present, then the group is not deprecated
mitre_deprecated: bool
Expand Down
34 changes: 18 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,33 @@ contentctl = 'contentctl.contentctl:main'
[tool.poetry.dependencies]

python = "^3.11,<3.14"
# Pinned to 2.9.2. Newer pydantic versions (tested through 2.13.5) fail to
# resolve the forward reference from Story to Detection, raising
# "`Story` is not fully defined; you should define `Detection`, then call
# `Story.model_rebuild()`." during `contentctl validate`/`build`.
pydantic = "~2.9.2"
PyYAML = "^6.0.2"
PyYAML = "^6.0.3"
requests = ">=2.34.2"
pycvesearch = "^1.2"
xmltodict = ">=0.13,<0.15"
xmltodict = "^1.0.4"
# attackcti 0.6.x requires pydantic>=2.12.5, which conflicts with our
# pydantic pin above, so this stays capped below 0.6.
attackcti = ">=0.5.4,<0.6"
Jinja2 = "^3.1.6"
questionary = "^2.0.1"
docker = "^7.1.0"
splunk-sdk = "^2.0.2"
questionary = "^2.1.1"
docker = "^7.2.0"
splunk-sdk = "^2.1.1"
semantic-version = "^2.10.0"
bottle = ">=0.12.25,<0.14.0"
tqdm = "^4.66.5"
pygit2 = "^1.15.1"
#We are pinned to this version of tyro because 0.9.23 and above
#have an issue when parsing an extremely large number of files
#(in our testing great than 130) when using the mode:selected
#--mode.files command.
tyro = "^0.9.2,<0.9.23"
gitpython = "^3.1.49"
setuptools = "<81"
rich = "^14.0.0"
tqdm = "^4.70.1"
pygit2 = "^1.20.1"
tyro = "^1.0.16"
gitpython = "^3.1.62"
setuptools = ">=80.10.2,<81"
rich = "^15.0.0"

[tool.poetry.group.dev.dependencies]
ruff = "^0.12.10"
ruff = "^0.16.8"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
Loading