fb52b1432e
Fibratus EDR profile (kind: fibratus). Pull-from-event-log model, same
shape DetonatorAgent's FibratusEdrPlugin.cs uses: operator configures
Fibratus on the EDR VM with alertsenders.eventlog: {enabled: true,
format: json}; rule matches land in the Application log. Whiskers gains
GET /api/alerts/fibratus/since which wevtutil-queries the log,
extracts <TimeCreated SystemTime> + <EventID> + <Data>, ships the raw
JSON blobs back. The new FibratusEdrAnalyzer mirrors Elastic's
two-phase shape — Phase 1 exec, Phase 2 polls Whiskers — and normalizes
Fibratus's actual schema (events[].proc.{name,exe,cmdline,parent_name,
parent_cmdline,ancestors} + bare tactic.id/technique.id/subtechnique.id
labels) into the saved-view renderer's dict.
Whiskers /api/info now reports telemetry_sources: ['fibratus'] when
fibratus.exe is at C:\Program Files\Fibratus\Bin\, so the
orchestrator can preflight before dispatching. wevtutil's single-quoted
attribute output is parsed correctly.
Dashboard reachability cache (services.edr_health). 30s TTL +
background poller every 15s. Per-probe timeouts dropped 4s/5s -> 2s.
First load post-boot waits at most one probe cycle; every subsequent
load <5ms (cache hit).
GrumpyCats package split: 1085-line monolith into:
grumpycat.py — orchestrator (14 lines)
cli/ — parser, handlers, runner
litterbox_client/ — base + per-domain mixins (files, analysis,
doppelganger, results, edr, reports, system)
composed into LitterBoxClient.
LitterBoxMCP.py rewires its one import. New CLI subcommand
fibratus-alerts and matching MCP tool fibratus_alerts_since pull
Fibratus alerts via a LitterBox passthrough endpoint
(/api/edr/fibratus/<profile>/alerts/since) for wire-checking the agent
without dispatching a payload.
CHANGELOG updated.
60 lines
1.7 KiB
Python
60 lines
1.7 KiB
Python
"""CLI runner — parse args, dispatch, handle exceptions uniformly.
|
|
|
|
Lifted out of `grumpycat.py` so the orchestrator stays a few lines.
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
|
|
from litterbox_client import LitterBoxAPIError, LitterBoxError
|
|
|
|
from .handlers import COMMAND_HANDLERS
|
|
from .parser import build_parser, setup_client
|
|
|
|
|
|
def run(argv=None) -> int:
|
|
"""Parse `argv`, dispatch to the right handler, return an exit code.
|
|
|
|
Catches the client's structured exceptions and turns them into clean
|
|
error messages + non-zero exit codes. Unexpected exceptions print
|
|
their type/message at INFO; with `--debug`, the full stack trace.
|
|
"""
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
|
|
log_level = logging.DEBUG if args.debug else logging.INFO
|
|
logging.basicConfig(
|
|
level=log_level,
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
)
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
return 0
|
|
|
|
handler = COMMAND_HANDLERS.get(args.command)
|
|
if handler is None:
|
|
parser.print_help()
|
|
return 1
|
|
|
|
try:
|
|
with setup_client(args) as client:
|
|
handler(client, args)
|
|
return 0
|
|
except LitterBoxAPIError as e:
|
|
logging.error(f"API Error (Status {e.status_code}): {str(e)}")
|
|
if args.debug and e.response:
|
|
logging.debug(f"Response data: {e.response}")
|
|
return 1
|
|
except LitterBoxError as e:
|
|
logging.error(f"Client Error: {str(e)}")
|
|
return 1
|
|
except KeyboardInterrupt:
|
|
print("\nOperation cancelled by user")
|
|
return 130
|
|
except Exception as e:
|
|
logging.error(f"Unexpected Error: {str(e)}")
|
|
if args.debug:
|
|
logging.exception("Detailed error information:")
|
|
return 1
|