diff --git a/strix/report/state.py b/strix/report/state.py index 9dede601..178c4047 100644 --- a/strix/report/state.py +++ b/strix/report/state.py @@ -287,8 +287,8 @@ class ReportState: self.vulnerability_reports.append(report) logger.info(f"Added vulnerability report: {report_id} - {title}") - posthog.finding(severity) - scarf.finding(severity) + posthog.finding(severity, cwe=cwe, is_cve=bool(cve)) + scarf.finding(severity, cwe=cwe, is_cve=bool(cve)) if self.vulnerability_found_callback: self.vulnerability_found_callback(report) diff --git a/strix/skills/__init__.py b/strix/skills/__init__.py index 7ce58959..31ac3059 100644 --- a/strix/skills/__init__.py +++ b/strix/skills/__init__.py @@ -1,9 +1,11 @@ import logging import re +import threading from collections import Counter from collections.abc import Iterator from pathlib import Path +from strix.telemetry import posthog, scarf from strix.utils.resource_paths import get_strix_resource_path @@ -177,6 +179,18 @@ def validate_requested_skills(skill_list: list[str], max_skills: int = 5) -> str return None +def _track_skill_loaded(skill_name: str, file_path: Path) -> None: + builtin = get_strix_resource_path("skills") + if not file_path.is_relative_to(builtin): + skill_name = "custom" + + def _send() -> None: + posthog.skill_loaded(skill_name) + scarf.skill_loaded(skill_name) + + threading.Thread(target=_send, daemon=True).start() + + def _candidate_skill_files(skill_name: str) -> list[Path]: """Resolve *skill_name* to effective matching files.""" if "/" in skill_name: @@ -216,6 +230,7 @@ def load_skills(skill_names: list[str]) -> dict[str, str]: var_name = skill_name.split("/")[-1] skill_content[var_name] = _FRONTMATTER_PATTERN.sub("", content).lstrip() logger.debug("Loaded skill: %s -> %s", skill_name, var_name) + _track_skill_loaded(var_name, file_path) logger.debug("load_skills: %d skill(s) resolved", len(skill_content)) return skill_content diff --git a/strix/telemetry/README.md b/strix/telemetry/README.md index ba9fc1ef..3c594915 100644 --- a/strix/telemetry/README.md +++ b/strix/telemetry/README.md @@ -2,7 +2,7 @@ To help make Strix better for everyone, we collect anonymized data that helps us understand how to better improve our AI security agent for our users, guide the addition of new features, and fix common errors and bugs. This feedback loop is crucial for improving Strix's capabilities and user experience. -We use [PostHog](https://posthog.com), an open-source analytics platform, for data collection and analysis. Our telemetry implementation is fully transparent - you can review the [source code](https://github.com/usestrix/strix/blob/main/strix/telemetry/posthog.py) to see exactly what we track. +We use [PostHog](https://posthog.com), an open-source analytics platform, for data collection and analysis, along with [Scarf](https://scarf.sh). Our telemetry implementation is fully transparent - you can review the source code ([posthog.py](https://github.com/usestrix/strix/blob/main/strix/telemetry/posthog.py), [scarf.py](https://github.com/usestrix/strix/blob/main/strix/telemetry/scarf.py)) to see exactly what we track. ### Telemetry Policy @@ -16,7 +16,8 @@ We collect only very **basic** usage data including: **System Context:** OS type, architecture, Strix version\ **Scan Context:** Scan mode (quick/standard/deep), scan type (whitebox/blackbox)\ **Model Usage:** Which LLM model is being used (not prompts or responses)\ -**Aggregate Metrics:** Vulnerability counts by severity +**Feature Usage:** Which built-in skills are loaded\ +**Aggregate Metrics:** Vulnerability counts by severity and weakness category (CWE) ### What We **Never** Collect diff --git a/strix/telemetry/posthog.py b/strix/telemetry/posthog.py index df6bed4d..e2da6282 100644 --- a/strix/telemetry/posthog.py +++ b/strix/telemetry/posthog.py @@ -73,12 +73,24 @@ def start( ) -def finding(severity: str) -> None: +def finding(severity: str, cwe: str | None = None, is_cve: bool = False) -> None: _send( "finding_reported", { **base_props(), "severity": severity.lower(), + "cwe": (cwe or "").strip().lower() or "unknown", + "is_cve": is_cve, + }, + ) + + +def skill_loaded(skill_name: str) -> None: + _send( + "skill_loaded", + { + **base_props(), + "skill": skill_name, }, ) diff --git a/strix/telemetry/scarf.py b/strix/telemetry/scarf.py index 6e48570c..c0c62964 100644 --- a/strix/telemetry/scarf.py +++ b/strix/telemetry/scarf.py @@ -75,13 +75,26 @@ def start( ) -def finding(severity: str) -> None: +def finding(severity: str, cwe: str | None = None, is_cve: bool = False) -> None: _send( "finding_reported", { **base_props(), "session": SESSION_ID, "severity": severity.lower(), + "cwe": (cwe or "").strip().lower() or "unknown", + "is_cve": is_cve, + }, + ) + + +def skill_loaded(skill_name: str) -> None: + _send( + "skill_loaded", + { + **base_props(), + "session": SESSION_ID, + "skill": skill_name, }, )