|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## What this project does |
| 6 | + |
| 7 | +BlueSkySight is a client that consumes Bluesky posts, scans them for vulnerability identifiers (CVE, GHSA, PYSEC, GSD, CERT-Bund, Cisco, RHSA, MSRC, CERT-FR — see `vulnerability_patterns` in `blueskysight/conf_sample.py`), and pushes each match as a "sighting" to a [Vulnerability-Lookup](https://github.com/vulnerability-lookup/vulnerability-lookup) instance via `pyvulnerabilitylookup`. |
| 8 | + |
| 9 | +Three console-script entry points (declared in `pyproject.toml`): |
| 10 | + |
| 11 | +- `BlueSkySight-Firehose` → `blueskysight/firehose.py` — current default. Connects to `wss://bsky.network/xrpc/com.atproto.sync.subscribeRepos`, decodes DAG-CBOR + CARv1 frames inline, and processes `#commit` messages with `app.bsky.feed.post/*` create ops. |
| 12 | +- `BlueSkySight-Firehose-v1` → `blueskysight/stream.py` — legacy implementation kept for compatibility. Same firehose, but uses the async DAG-CBOR/CAR parsers and `enumerate_mst_records` from `utils.py` to walk the MST and resolve the actual post record from `op["path"]`. |
| 13 | +- `BlueSkySight-Jetstream` → `blueskysight/jetstream.py` — JSON-based alternative via Bluesky's Jetstream relays (`wss://jetstream{instance}.{geo}.bsky.network/subscribe`). Supports optional zstd compression with a remotely fetched dictionary, plus DNS / `.well-known` handle→DID resolution. |
| 14 | + |
| 15 | +When changing the streaming pipeline, remember that `firehose.py` and `stream.py` contain **two independent CBOR/CAR decoder implementations** (sync in `firehose.py`, async in `utils.py`). Don't assume a fix in one applies to the other. |
| 16 | + |
| 17 | +## Configuration loading |
| 18 | + |
| 19 | +`blueskysight/config.py` runs at import time: it `importlib`-loads the path in the `BLUESKYSIGHT_CONFIG` env var (falling back to `blueskysight/conf_sample.py`), then re-exports the names the rest of the code consumes. Required keys: `vulnerability_lookup_base_url`, `vulnerability_auth_token`, `vulnerability_patterns`. Optional: `ignore` (DID denylist used by the firehose), and the Valkey trio `valkey_host` / `valkey_port` / `expiration_period` — if all three are present, `heartbeat_enabled` is set to True and `utils.py` opens a `valkey.Valkey` client at module import. |
| 20 | + |
| 21 | +Implication: importing `blueskysight.utils` with heartbeat keys present will attempt a Valkey connection. Tests / scripts that don't need it should point `BLUESKYSIGHT_CONFIG` at a config without those keys. |
| 22 | + |
| 23 | +## Heartbeat / error reporting |
| 24 | + |
| 25 | +`utils.heartbeat()` writes a timestamp to a Valkey key (default `process_heartbeat_BlueskySight`, overridden to `process_heartbeat_BlueskySight-Jetstream` for jetstream) every 30s with TTL `expiration_period`. `utils.report_error()` rpush'es log entries to `process_logs_BlueskySight` (24h TTL). Both `firehose.py` and `jetstream.py` expose a `launch_with_hearbeat()` that runs the streamer + heartbeat under one `asyncio.gather` and cancels the heartbeat if the streamer raises. `stream.py` (the v1 entry point) does **not** support heartbeat. |
| 26 | + |
| 27 | +## Common commands |
| 28 | + |
| 29 | +```bash |
| 30 | +# Install (dev) |
| 31 | +poetry install --with dev |
| 32 | + |
| 33 | +# Run a streamer (requires BLUESKYSIGHT_CONFIG to point at a config file) |
| 34 | +export BLUESKYSIGHT_CONFIG=$PWD/blueskysight/conf_sample.py |
| 35 | +poetry run BlueSkySight-Firehose |
| 36 | +poetry run BlueSkySight-Jetstream --geo us-west --instance 1 |
| 37 | + |
| 38 | +# Lint / format / type-check (configured in .pre-commit-config.yaml and pyproject.toml) |
| 39 | +poetry run pre-commit run --all-files # black, isort, flake8 (max-line-length=120), pyupgrade, pip-audit |
| 40 | +poetry run mypy blueskysight # strict_optional, check_untyped_defs, warn_unreachable, etc. |
| 41 | + |
| 42 | +# Build / publish (publish runs from the GitHub `release` workflow on tag publish) |
| 43 | +poetry build |
| 44 | +``` |
| 45 | + |
| 46 | +There is no test suite in the repo. |
| 47 | + |
| 48 | +## Things to know before editing |
| 49 | + |
| 50 | +- **Don't commit `blueskysight/conf.py` or `conf1.py`** — these are local, untracked operator configs. The shipped sample is `conf_sample.py`, which is also listed in `pyproject.toml` `[tool.poetry] include`. |
| 51 | +- The Docker image (`Dockerfile`, `docker-compose.yml`) `pipx install BlueSkySight` from PyPI — it does **not** build from the local checkout. Local source changes won't appear in the container until a release is published. |
| 52 | +- AT-URI → public bsky.app URL conversion (`utils.get_post_url`) calls `https://plc.directory/{did}` per post. If that resolution fails, the AT-URI is used as the sighting source instead of a `bsky.app` URL. |
| 53 | +- `extract_vulnerability_ids` deduplicates case-insensitively but preserves the *last* occurrence's case (see `remove_case_insensitive_duplicates`). |
0 commit comments