Report privately through GitHub's security advisories. Please do not open a public issue for anything exploitable.
This is a personal open-source project maintained by one person. There is no SLA. Expect a first response within a week or so, and please give a reasonable window before public disclosure.
SportWire runs on one machine, for one recipient, and holds three secrets:
| Secret | Where it lives | If leaked |
|---|---|---|
TELEGRAM_BOT_TOKEN |
.env |
Anyone can send messages as your bot to anyone who has started a chat with it. Revoke via /revoke in @BotFather |
TELEGRAM_CHAT_ID |
.env |
Not a credential — useless without the bot token. It identifies the recipient |
BALL_DONT_LIE_API_KEY |
.env |
Someone can consume your free-tier quota. Rotate at balldontlie.io |
.env is gitignored and has never been committed. [VERIFIED] The live bot token and API key
appear zero times in the full git history.
The SQLite database stores only identifiers — article ids and game state hashes — never article text, credentials, or personal data.
- No inbound network listener. It makes outbound HTTPS requests and exits.
- No user input. Data comes from configured feeds only; there is no query interface.
- No arbitrary code execution, no
eval, no plugin loading. - No telemetry. Nothing is sent anywhere except your own Telegram chat.
Third-party feed content is untrusted. Article titles and descriptions come from external sources and are inserted into messages. They are sent as plain text rather than Telegram Markdown, which removes the injection surface that formatted messages would create. If you add a channel that renders markup, escape the content.
LLM summarisation ships enabled, and is the most likely way this project will tell you
something false. It ships disabled. Corrected 2026-08-14: that described the
position before ADR-012 reversed it on 2026-08-10.
Disable it with --no-summary.
[VERIFIED] Every local model tested fabricated player names and contract figures on real
data. What makes it shippable is that the output is checked before it is sent:
processing/validate.py rejects any proper name or figure absent from the source articles,
retries, and falls back to the plain headline list when nothing passes. It fails closed, and
no invented name has reached a phone.
[VERIFIED] The check has a known hole, and it is not theoretical. It grounds entities,
not claims: a sentence composed entirely of real, sourced names can assert a relationship
between them that no source states, and it will pass. One such sentence was delivered on
2026-08-13. Tracked as TASKS.md P5 and recorded as a failing test.
[INFERRED] Treat a generated paragraph as unverified. This is a correctness risk rather than
a security one, but if you forward a brief onward, that is the sentence that will embarrass
you.
External orchestrators are your responsibility. SportWire can print a brief to stdout for another tool to relay. Tools that reach WhatsApp typically do so through unofficial bridges that violate WhatsApp's terms and risk a permanent account ban. SportWire ships no such integration and never will — ADR-013 — but if you connect one, that account and that risk are yours.
Dependencies. Five direct packages, pinned:
requests, pydantic, python-dotenv, plus pytest and ruff for development. Fewer
dependencies is itself the mitigation; the environment this replaced carried 151 packages
before proving anything worked.
Before your first commit, confirm .gitignore covers .env, *.db, .venv/,
__pycache__/, *.pyc, .pytest_cache/ and .ruff_cache/, and check your history:
git log --all --name-only | grep -i "\.env"A leaked bot token in a public repository is found by scanners within minutes.