mirror of
https://github.com/usestrix/strix.git
synced 2026-08-18 17:52:32 +02:00
The SDK ships its own tracing pipeline (``agents.tracing``) plus ``SQLiteSession`` for native conversation persistence. Strix's custom OTEL bootstrap + Traceloop integration was dead weight — the SDK does not bridge to OpenTelemetry, so all of our adapter code was solving a problem we didn't actually need solved. Telemetry purge: - Drop the ``traceloop-sdk`` and ``opentelemetry-exporter-otlp-proto-http`` runtime deps. ``uv sync`` uninstalls ~30 transitive packages (the OTEL family, ``traceloop-sdk``, ``protobuf``, ``opentelemetry-exporter-otlp-*``, ``deprecated``, ``wrapt``, ``backoff``, etc.) — about 1000 lines off ``uv.lock``. - Delete ``bootstrap_otel`` and ``JsonlSpanExporter`` from ``telemetry/utils.py``; strip the OTEL pruning helpers, ``parse_traceloop_headers``, ``default_resource_attributes``, ``format_trace_id`` / ``format_span_id`` / ``iso_from_unix_ns``. Keep only the sanitizer + JSONL writer + write-lock registry. - Strip ``Tracer._setup_telemetry``, ``_otel_tracer``, ``_remote_export_enabled``, ``_active_events_file_path``, ``_active_run_metadata``, ``_get_events_write_lock``, ``_set_association_properties``. ``_emit_event`` now generates trace/span ids from ``uuid4`` directly. - Drop the ``traceloop_base_url`` / ``traceloop_api_key`` / ``traceloop_headers`` / ``strix_otel_telemetry`` config knobs. - Rename ``is_otel_enabled`` → ``is_telemetry_enabled`` (the gate now controls JSONL emission only). Native session resume: - ``entry.py`` now constructs an ``agents.memory.SQLiteSession`` keyed by ``scan_id`` and persists conversation history at ``strix_runs/<scan_id>/session.db``. A second call to ``run_strix_scan`` with the same ``scan_id`` resumes from where the prior run left off — no manual state plumbing needed. Tracer.agents fix (TUI agent tree was silently empty): - ``StrixOrchestrationHooks.on_agent_start`` now mirrors bus state into ``tracer.agents`` (id / name / parent_id / status), and ``on_agent_end`` flips the entry to ``completed`` / ``crashed``. The TUI now actually shows the agent tree during scans. Tooling: - Drop ``pylint`` from dev deps; ``ruff`` covers everything we used it for. Strip the ``make lint`` pylint step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
import json
|
|
import logging
|
|
import re
|
|
import threading
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from scrubadub import Scrubber
|
|
from scrubadub.detectors import RegexDetector
|
|
from scrubadub.filth import Filth
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_REDACTED = "[REDACTED]"
|
|
_SCREENSHOT_OMITTED = "[SCREENSHOT_OMITTED]"
|
|
_SCREENSHOT_KEY_PATTERN = re.compile(r"screenshot", re.IGNORECASE)
|
|
_SENSITIVE_KEY_PATTERN = re.compile(
|
|
r"(api[_-]?key|token|secret|password|authorization|cookie|session|credential|private[_-]?key)",
|
|
re.IGNORECASE,
|
|
)
|
|
_SENSITIVE_TOKEN_PATTERN = re.compile(
|
|
r"(?i)\b("
|
|
r"bearer\s+[a-z0-9._-]+|"
|
|
r"sk-[a-z0-9_-]{8,}|"
|
|
r"gh[pousr]_[a-z0-9_-]{12,}|"
|
|
r"xox[baprs]-[a-z0-9-]{12,}"
|
|
r")\b"
|
|
)
|
|
_SCRUBADUB_PLACEHOLDER_PATTERN = re.compile(r"\{\{[^}]+\}\}")
|
|
_EVENTS_FILE_LOCKS_LOCK = threading.Lock()
|
|
_EVENTS_FILE_LOCKS: dict[str, threading.Lock] = {}
|
|
|
|
|
|
class _SecretFilth(Filth): # type: ignore[misc]
|
|
type = "secret"
|
|
|
|
|
|
class _SecretTokenDetector(RegexDetector): # type: ignore[misc]
|
|
name = "strix_secret_token_detector"
|
|
filth_cls = _SecretFilth
|
|
regex = _SENSITIVE_TOKEN_PATTERN
|
|
|
|
|
|
class TelemetrySanitizer:
|
|
def __init__(self) -> None:
|
|
self._scrubber = Scrubber(detector_list=[_SecretTokenDetector])
|
|
|
|
def sanitize(self, data: Any, key_hint: str | None = None) -> Any: # noqa: PLR0911
|
|
if data is None:
|
|
return None
|
|
|
|
if isinstance(data, dict):
|
|
sanitized: dict[str, Any] = {}
|
|
for key, value in data.items():
|
|
key_str = str(key)
|
|
if _SCREENSHOT_KEY_PATTERN.search(key_str):
|
|
sanitized[key_str] = _SCREENSHOT_OMITTED
|
|
elif _SENSITIVE_KEY_PATTERN.search(key_str):
|
|
sanitized[key_str] = _REDACTED
|
|
else:
|
|
sanitized[key_str] = self.sanitize(value, key_hint=key_str)
|
|
return sanitized
|
|
|
|
if isinstance(data, list):
|
|
return [self.sanitize(item, key_hint=key_hint) for item in data]
|
|
|
|
if isinstance(data, tuple):
|
|
return [self.sanitize(item, key_hint=key_hint) for item in data]
|
|
|
|
if isinstance(data, str):
|
|
if key_hint and _SENSITIVE_KEY_PATTERN.search(key_hint):
|
|
return _REDACTED
|
|
|
|
cleaned = self._scrubber.clean(data)
|
|
return _SCRUBADUB_PLACEHOLDER_PATTERN.sub(_REDACTED, cleaned)
|
|
|
|
if isinstance(data, int | float | bool):
|
|
return data
|
|
|
|
return str(data)
|
|
|
|
|
|
def get_events_write_lock(output_path: Path) -> threading.Lock:
|
|
path_key = str(output_path.resolve(strict=False))
|
|
with _EVENTS_FILE_LOCKS_LOCK:
|
|
lock = _EVENTS_FILE_LOCKS.get(path_key)
|
|
if lock is None:
|
|
lock = threading.Lock()
|
|
_EVENTS_FILE_LOCKS[path_key] = lock
|
|
return lock
|
|
|
|
|
|
def reset_events_write_locks() -> None:
|
|
with _EVENTS_FILE_LOCKS_LOCK:
|
|
_EVENTS_FILE_LOCKS.clear()
|
|
|
|
|
|
def append_jsonl_record(output_path: Path, record: dict[str, Any]) -> None:
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with get_events_write_lock(output_path), output_path.open("a", encoding="utf-8") as f:
|
|
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|