Skip to content

Commit 0fa259d

Browse files
committed
fix: event.pop("_timestamp", None)
1 parent 9292646 commit 0fa259d

3 files changed

Lines changed: 58 additions & 9 deletions

File tree

linux_edr/app.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,8 +332,30 @@ def _process_event(self, evt: str) -> None:
332332
if self.verbose_debug:
333333
self._log_parsed_event(evt)
334334

335-
# Add event to aggregator
336-
self.agg.add(evt)
335+
# If event is already a dict (e.g., when injected by tests or future extensions),
336+
# we assume it's been validated and directly buffer it.
337+
if isinstance(evt, dict):
338+
self.agg.add(evt)
339+
return
340+
341+
# Otherwise, treat it as raw text from trace_pipe and try to parse/validate.
342+
parsed = parse_execve(evt)
343+
344+
if not parsed:
345+
# Not an execve line – skip buffering
346+
return
347+
348+
# Validate with Pydantic schema (ensures correct types/structure)
349+
try:
350+
from .domain.models.event_models import ExecveEvent as ExecveEventModel # Local import to avoid cycles
351+
352+
model_event = ExecveEventModel.from_namedtuple(parsed)
353+
354+
# Buffer as plain dict (safer for serialization & downstream processing)
355+
self.agg.add(model_event.model_dump())
356+
except Exception as e:
357+
# Any validation or conversion error – log and drop the event
358+
logging.warning("Invalid event skipped: %s", e)
337359

338360
def _log_parsed_event(self, evt: str) -> None:
339361
"""
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from pydantic import BaseModel, Field
2+
from typing import List, Any
3+
4+
class ExecveEvent(BaseModel):
5+
"""Validated execve syscall event."""
6+
7+
timestamp: str = Field(..., description="Trace timestamp string as reported by ftrace")
8+
pid: int = Field(..., description="Process ID of the task that executed the syscall")
9+
command: str = Field(..., description="Executable invoked (basename)")
10+
args: List[str] = Field(default_factory=list, description="Arguments supplied to the executable")
11+
12+
# Helper constructors ---------------------------------------------------
13+
@classmethod
14+
def from_namedtuple(cls, nt: Any) -> "ExecveEvent":
15+
"""Build ExecveEvent from an ExecveEvent NamedTuple instance."""
16+
return cls(timestamp=nt.timestamp, pid=nt.pid, command=nt.command, args=list(nt.args))
17+
18+
def __str__(self) -> str: # pragma: no cover – convenience only
19+
"""Human-readable representation useful in logs."""
20+
cmd_line = " ".join([self.command, *self.args])
21+
return f"[{self.timestamp}] pid={self.pid} cmd={cmd_line}"

tests/test_app.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
parse_execve,
99
setup_logging,
1010
LinuxEDRApp,
11-
ExecveEvent,
1211
SyscallTracer,
1312
)
1413

14+
from linux_edr.domain.models.event_models import ExecveEvent
15+
1516

1617
class TestApp(unittest.TestCase):
1718

@@ -259,7 +260,7 @@ def test_process_event(self, mock_parse_execve, mock_log_info, mock_log_debug):
259260
app.agg = MagicMock()
260261

261262
# Setup mock for parse_execve
262-
parsed_event = ExecveEvent("12345.6789", 1000, "test_cmd", ["-a", "-b"])
263+
parsed_event = ExecveEvent(timestamp="12345.6789", pid=1000, command="test_cmd", args=["-a", "-b"])
263264
mock_parse_execve.return_value = parsed_event
264265

265266
# Import the method to test it independently
@@ -268,23 +269,28 @@ def test_process_event(self, mock_parse_execve, mock_log_info, mock_log_debug):
268269
# Call the method directly
269270
LinuxEDRApp._process_event(app, "test_event")
270271

271-
# Verify debug logging
272-
app.agg.add.assert_called_once_with("test_event")
273-
self.assertTrue(mock_log_debug.called)
272+
# The aggregator should receive a validated dict version of the parsed event
273+
expected_dict = {
274+
"timestamp": "12345.6789",
275+
"pid": 1000,
276+
"command": "test_cmd",
277+
"args": ["-a", "-b"],
278+
}
279+
app.agg.add.assert_called_once_with(expected_dict)
274280

275281
# Reset mock and test with verbose_debug=False
276282
mock_log_debug.reset_mock()
277283
app.verbose_debug = False
278284

279285
LinuxEDRApp._process_event(app, "test_event2")
280-
app.agg.add.assert_called_with("test_event2")
286+
app.agg.add.assert_called_with(expected_dict)
281287

282288
# Test with debug=False
283289
mock_log_debug.reset_mock()
284290
app.debug = False
285291

286292
LinuxEDRApp._process_event(app, "test_event3")
287-
app.agg.add.assert_called_with("test_event3")
293+
app.agg.add.assert_called_with(expected_dict)
288294
mock_log_debug.assert_not_called()
289295

290296

0 commit comments

Comments
 (0)