Skip to content

Commit 039dc40

Browse files
new: [firehose] Optionally include post content in sightings.
Release 1.2.0. When `include_post_content = True` is set in the configuration file, BlueSkySight-Firehose attaches the full post text to each sighting pushed to Vulnerability-Lookup. Defaults to False. Also bundles a CLAUDE.md guide for AI-assisted development, widens the pyvulnerabilitylookup dependency constraint, and declares support for Python 3.13 and 3.14. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent be37680 commit 039dc40

8 files changed

Lines changed: 521 additions & 389 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## Release 1.2.0 (2026-04-29)
4+
5+
### New
6+
7+
- `BlueSkySight-Firehose` can now include the full post text in the sighting
8+
sent to Vulnerability-Lookup. Set `include_post_content = True` in the
9+
configuration file to enable it (defaults to `False`).
10+
11+
### Improvements
12+
13+
- Widened the `pyvulnerabilitylookup` dependency constraint to `>=2.0.0,<5.0.0`.
14+
- Declared support for Python 3.13 and 3.14.
15+
16+
317
## Release 1.0.0 (2025-02-13)
418

519
This release introduces the capability to report errors, warnings,

CLAUDE.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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`).

blueskysight/conf_sample.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
# DID of accounts to ignore
2222
ignore = ["did:plc:xrwz7tco7wyptkqee3wbjmci"]
2323

24+
# Include the full post text in the sighting sent to Vulnerability-Lookup
25+
include_post_content = False
26+
2427

2528
# Hearbeat mechanism
2629
heartbeat_enabled = True

blueskysight/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ def load_config(path):
4141
except Exception:
4242
ignore = []
4343

44+
try:
45+
include_post_content = bool(conf.include_post_content)
46+
except AttributeError:
47+
include_post_content = False
48+
4449

4550
try:
4651
heartbeat_enabled = True

blueskysight/firehose.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,11 @@ async def process_blocks(uri, blocks):
358358
url = await get_post_url(uri)
359359
print(f"Post URL: {url}")
360360
print(f"Vulnerability IDs detected: {', '.join(vulnerability_ids)}")
361-
push_sighting_to_vulnerability_lookup(url, vulnerability_ids)
361+
push_sighting_to_vulnerability_lookup(
362+
url,
363+
vulnerability_ids,
364+
content=content if config.include_post_content else None,
365+
)
362366

363367

364368
def extract_textual_content(blocks):

blueskysight/utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ async def report_error(
4848
raise
4949

5050

51-
def push_sighting_to_vulnerability_lookup(status_uri, vulnerability_ids):
51+
def push_sighting_to_vulnerability_lookup(status_uri, vulnerability_ids, content=None):
5252
"""Create a sighting from an incoming status and push it to the Vulnerability Lookup instance."""
5353
print("Pushing sighting to Vulnerability Lookup…")
5454
vuln_lookup = PyVulnerabilityLookup(
@@ -57,6 +57,8 @@ def push_sighting_to_vulnerability_lookup(status_uri, vulnerability_ids):
5757
for vuln in vulnerability_ids:
5858
# Create the sighting
5959
sighting = {"type": "seen", "source": status_uri, "vulnerability": vuln}
60+
if content is not None:
61+
sighting["content"] = content
6062

6163
# Post the JSON to Vulnerability Lookup
6264
try:

0 commit comments

Comments
 (0)