This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Cron-invoked CLI that pulls vulnerability sightings collected by a third-party Telegram scraper and pushes them to a Vulnerability-Lookup instance via pyvulnerabilitylookup. Sibling project to MISPSight — same config-file pattern.
- Build/packaging: Poetry (
poetry-core >=2.0.0,<3.0.0), Python>=3.10,<4.0. - Install for development:
poetry install(pulls thedevdependency group). - Install as a user tool:
pipx install TeleGramSight(exposes thetelegramsightentry point defined in[project.scripts]). - Runtime deps:
requests,pyvulnerabilitylookup,cryptography(AES-SIV),dateparser(natural-language--since/--until). - Type-check:
poetry run mypy .— this is the only quality gate that CI enforces..github/workflows/mypy.ymlruns it on Python 3.11, 3.12, and 3.13 on every push/PR tomain. The[tool.mypy]block inpyproject.tomlis strict (strict_optional,no_implicit_optional,warn_unreachable, etc.); fix issues at the source rather than adding blanket# type: ignore. - No test suite is checked in.
Single-file flow in telegramsight/main.py:
load_config()reads the Python file pointed at byTeleGramSight_CONFIG— the env var name is mixed-case and matches the one in the README; don't normalise it.iter_results()POSTs to{api_url}/api/get_cve_objswithtag_llm=True, paginating viapage/page_sizeuntilpage * page_size >= total.sighting_type()maps Telegram tags to a Vulnerability-Lookup sighting type:tag_wildusage→exploitedtag_poc→published-proof-of-concept- otherwise →
seen(tag_wildusagewins when both are set — checked first.)
build_sighting()assembles{type, source=Telegram/{enc}, vulnerability, creation_timestamp}andpush_sighting()callsPyVulnerabilityLookup.create_sighting.{enc}is AES-SIV("{chat_id}/{msg_id}") undersource_encryption_keywith no nonce and no associated data, serialized as urlsafe-base64 ofSIV (16B) || ciphertext(padding stripped). This is deterministic on purpose: the same Telegram message always produces the same source string, so Vulnerability-Lookup can dedupe on the ciphertext without decrypting. Key may be 32/48/64 raw bytes (AES-128/192/256-SIV). If you ever need to change the key, all previously-pushed sightings become undiscoverable under the new key.creation_timestampmust be a timezone-awaredatetime(not the raw ISO string from the upstream API) —create_sightinginspects.tzinfo. Naive timestamps are coerced to UTC.
decrypt_source_fragment() is the inverse of encrypt_source_fragment() and backs the telegramsight-decrypt entry point (see CLI contract). It exists so an operator holding the key can resolve a private-channel sighting back to {chat_id}/{msg_id} for investigation. Decryption is local-only — nothing is sent over the network — which is what preserves the privacy invariant: the original chat_id is only ever exposed to a key-holder.
The Telegram endpoint URL is deliberately kept out of source control:
telegramsight/conf_sample.py— versioned template.api_url/api_key/source_encryption_keymust stay blank here; no real URL or key in comments either.telegramsight/conf.py— gitignored (see.gitignore). This is where the realapi_urlandsource_encryption_keylive. Deployments pointTeleGramSight_CONFIGat a copy of this file outside the repo.
When adding new config keys, add them to conf_sample.py with empty/default values, and to conf.py if local dev needs them. Never paste secrets or private endpoints into conf_sample.py.
Two entry points are registered in [project.scripts]:
telegramsight [--since <t>] [--until <t>] [--page-size N] [--no-push]
--since/--untilaccept unix-epoch seconds, ISO 8601 timestamps, or natural-language expressions (2 days ago,yesterday,today). With no args the tool runs over the last 24 hours — that is the intended cron shape, so don't change the default window without updating the README's cron example.--no-pushis a dry run: build and log each sighting but don't instantiatePyVulnerabilityLookupor call out to the instance.
telegramsight-decrypt <fragment> (→ decrypt_main)
- Operator-side helper for resolving a private-channel sighting back to
{chat_id}/{msg_id}. Reads the sameTeleGramSight_CONFIGfile and uses the samesource_encryption_keyas the main CLI. <fragment>accepts either the raw urlsafe-base64 ciphertext or the fullTelegram/<ct>string copied from a sighting source.
- Land all changes on
mainand getmypygreen (CI will block otherwise). - Bump
versioninpyproject.toml. - Add a dated section to
CHANGELOG.mdfollowing Keep-a-Changelog (Added/Changed/Fixed/ …) and append the link reference at the bottom. - Commit as
chg: [release] Prepare <version>.. - Create an annotated tag
v<version>whose message mirrors the CHANGELOG entry (git tag -a v<version> -m "…"). Tags are PGP-signed by the maintainer's git config — don't override that. - Push commits and tag (
git push --follow-tags). - Publish a GitHub Release from the tag —
.github/workflows/release.ymlpicks that up and trust-publishes the wheel to PyPI (the tag push alone does not trigger publishing).
Commit-message convention in the log: chg: [area] … for non-bug changes, fix: [area] … for bug fixes.
GPL-3.0-or-later. New files should be compatible with that license.