This FAQ answers common questions about Elementary CTI: what it is, how to
get access, where the data comes from, how to run your own instance, and how
to solve common problems. It is rendered live at /faq on every deployment.
Elementary CTI is a multi-source ransomware threat-intelligence aggregator. It collects victim and cyberattack data from public ransomware tracking sources, enriches it with MITRE ATT&CK TTPs, Bitcoin payment intelligence, and operational data, and presents everything through a web UI built for analyst investigation.
The name honors the TV series Elementary and its Sherlock Holmes: a
consultant who turns scattered observations into deductions. The codebase is
full of quotes from the series. The project's original code name was
Pestilentia, and the Python package is still named pestilentia/ — only
the product brand changed.
No. Elementary CTI is a personal open-source project, released under the AGPL-3.0 license. There is no company behind it, no paid tier, and no SLA.
Threat-intelligence analysts, SOC teams, researchers, and anyone who wants a self-hosted, auditable view of ransomware activity without depending on a commercial platform.
You are not signed in. Anonymous visitors see a public overview limited to the last 30 days of activity from public sources. Everything else — full history, adversary profiles, maps, watchlists, the AI pipeline — requires an account.
Accounts are created by an administrator of the instance you are visiting. There is no self-registration. Contact the instance operator.
| Role | What it unlocks |
|---|---|
user |
Read access to the full dataset: dashboard, victims, adversaries, cyberattacks, map, articles, campaigns, search |
analyst |
Everything user has, plus the analysis surfaces (IP analysis, review queues, AI actions) as they ship |
admin |
Everything, plus user management, source toggles, and service API keys in the settings |
- Find the sign-in box in the left sidebar, or open
/login. - Enter your username and password.
- Select Sign in.
A session ends 12 hours after sign-in, or after 2 hours without activity, whichever comes first. Sign in again to continue.
Wait. After five failed attempts, the account-and-address pair is locked, starting at 30 seconds and doubling up to 15 minutes. The lock clears by itself. If you forgot the password, ask an administrator to reset it.
Yes:
- Open Settings in the sidebar.
- In Change password, enter your current password and the new one (minimum 10 characters), twice.
- Select Update password. You stay signed in.
Three common causes: your session passed the 12-hour limit, you were inactive for more than 2 hours, or an administrator disabled your account. Disabled accounts are signed out at the next request.
Aggregate counters, a daily victim timeline, recent victim names, and the most active groups — all restricted to the last 30 days and built only from public tracking sources. There are no links into the full dataset.
TLP (Traffic Light Protocol) marks how widely information may be shared. Elementary CTI marks every ingested article with a TLP level. The public dashboard never queries TLP-marked content at all, so nothing above TLP:CLEAR can appear there — by construction, not by filter. The same discipline gates which content may reach a cloud LLM.
The names come from public ransomware tracking sources; the victims were already published by the criminals and indexed by trackers. Elementary CTI republishes only what is already public, limited to 30 days, without amplifying details (no claim URLs, no screenshots, no drill-down).
Structured data: ransomware.live (victims, groups, attacks), MITRE ATT&CK (TTPs), Ransomwhere (Bitcoin payments), and deepdarkCTI (operational data: onion sites, Telegram/Tox channels). Articles: 12 curated RSS feeds from vendor research labs and news outlets (CISA, The DFIR Report, Unit 42, Talos, Microsoft, SentinelLABS, BleepingComputer, and others).
It asks a catalogue rather than deciding for itself. The same adversary is usually known by several names at once, because each research house names what it finds under its own scheme: one calls a Chinese group Panda, another calls it Typhoon, a third gives it a number until it is confident enough to name it. Reading a name and guessing the rest merges groups that are not the same, which is the one mistake here that nothing downstream can catch.
So an alias is recorded only where a published catalogue lists it, and the record keeps which catalogue said so. The catalogues used are the MITRE ATT&CK intrusion sets, the MISP Project's threat-actor galaxy, and Microsoft's own published mapping of its actor names to the names other vendors use. All three are public, versioned, and can be checked by anyone holding the same file, which is what makes an alias evidence rather than an opinion. Where no catalogue lists the name, the platform says so and leaves the question to an analyst instead of inventing an answer.
The scheduler polls sources every 4 hours. MITRE, Ransomwhere, and deepdarkCTI enrichments refresh weekly. The dashboard header shows the last update time.
Elementary CTI mirrors upstream sources; it does not originate claims. Corrections must happen at the source (for example, ransomware.live). When the upstream record changes, the next scheduler cycle picks it up.
Every cycle checks each source with an HTTP and format probe. Status appears on the Pipeline page. A separate weekly sentinel fingerprints the shape of every upstream API contract and reports drift before it breaks ingestion.
Anonymous visits to the public pages are not logged. Signed-in activity is recorded (page, time, address) for security auditing — see the Security section.
Victims are organizations claimed on ransomware leak sites. Cyberattacks are incident records (breach notices, disclosures) tracked separately, with their own dates and descriptions.
Each group page combines the ransomware.live description, the full MITRE ATT&CK profile (aliases, TTPs, software), deepdarkCTI operational channels, BTC transactions, geographic attribution, and a victim history with trends.
/attack shows which MITRE ATT&CK techniques are covered by the groups in
the database, ordered by kill chain. A blank cell means "not observed in our
data", not "not used by anyone".
Add the names of organizations you care about (your company, suppliers, customers). Every new victim is fuzzy-matched against the watchlist; matches raise alerts with severity levels, visible in the UI and dispatchable via webhook.
Articles is the reading list the AI pipeline ingests: deduplicated entries from the curated feeds, with TLP marks and priority flags driven by your watchlist. Campaigns clusters related articles with local embeddings so one incident covered by five outlets reads as one story.
Yes. Each adversary offers a STIX 2.1 bundle export (/api/v1/groups/{id}/stix),
ready for MISP or OpenCTI. The REST API serves JSON for everything else.
Today: article ingestion, deduplication, campaign clustering, and a routed LLM triage layer. In development: a full extraction pipeline that turns articles into structured, source-anchored adversary intelligence with an analyst review queue.
A provider-agnostic router decides per call. The current preferred cloud provider is NVIDIA NIM's free tier (Llama 3.1 8B for triage, Llama 3.3 70B for analysis); Anthropic Claude models are registered for when a funded key exists; a local Ollama fallback is planned.
Content at or below the configured TLP ceiling (PEST_AI_TLP_CLOUD_MAX,
default green) may reach a cloud provider. Anything above stays local or
waits for a human. Every LLM call is logged with cost and token counts.
Three hard ceilings: per-article token cap, daily budget, monthly budget. At 80% of the daily budget the router downgrades to the cheap tier; at 100% it refuses. All limits are configuration, not habits.
The extraction design (in development) requires every indicator to be verbatim-anchored in the source article and every claim to be labeled as observed or inferred; unverifiable output is rejected or staged for human review, never silently merged.
Yes: read-only JSON endpoints under /api/v1/ (stats, victims, groups,
cyberattacks, map data, timeline, STIX export). Interactive documentation is
at /docs after you sign in.
The API uses the same session cookie as the UI. Sign in through /login,
then send the pest_session cookie with each request. Unauthenticated calls
receive 401. Token-based API access is on the roadmap.
Not per-endpoint today. Sign-in attempts are rate-limited. Be reasonable: this is typically a Raspberry Pi, not a CDN.
- Docker and Docker Compose.
- 2 GB of RAM and a few GB of disk. A Raspberry Pi 5 runs the reference instance.
- Optional: a PostgreSQL database (the default is SQLite for development).
- Clone the repository.
- Copy
.env.exampleto.env. - Set
PEST_SECRET_KEYto a random value. Generate one withpython -c "import secrets; print(secrets.token_hex(32))". - Set
PEST_AUTH_USERandPEST_AUTH_PASS. These seed the first admin account. - Run
docker compose up -d --build. - Open
http://localhost:8000and sign in.
At first startup, if the users table is empty and PEST_AUTH_USER and
PEST_AUTH_PASS are set, the application creates that account with the
admin role. After that, manage accounts in Settings → Users (create,
disable, delete, change role, reset password — the last active admin is
protected from lockout-by-accident). The variables are not read again once
users exist.
Run alembic upgrade head with PEST_DB_URL pointing at your database.
Migrations are additive and support downgrade. Always back up first.
For PostgreSQL: pg_dump on a schedule. The reference deployment pushes a
daily dump to a private off-site git branch via a systemd timer. For SQLite:
copy the database file while the app is stopped.
| Variable | Purpose |
|---|---|
PEST_DB_URL |
Database connection string |
PEST_SECRET_KEY |
Signs sessions and CSRF tokens. Required in production |
PEST_AUTH_USER / PEST_AUTH_PASS |
Bootstrap seed for the first admin |
PEST_COOKIE_SECURE |
true behind TLS (default); false only for plain-HTTP LAN use |
PEST_ACTIVITY_RETENTION_DAYS |
How long user-activity log rows are kept (default 90) |
PEST_POLL_INTERVAL_HOURS |
Scheduler cycle (default 4) |
PEST_AI_TLP_CLOUD_MAX |
Highest TLP level allowed to reach a cloud LLM |
PEST_AI_DAILY_BUDGET_USD / PEST_AI_MONTHLY_BUDGET_USD |
Hard LLM spend ceilings |
PEST_AI_NVIDIA_API_KEY |
NVIDIA NIM key for the AI pipeline |
.env.example documents the full list.
Only behind a TLS reverse proxy, and only after you review the security
checklist. The reference deployment uses Caddy for TLS termination and keeps
the app port unpublished. Set PEST_COOKIE_SECURE=true (the default) behind
TLS. An OWASP Top 10 audit gates the reference deployment's own exposure.
Yes, as an administrator: Settings → Sources lists primary sources, enrichments, and article feeds, each with an enable/disable control. A disabled source is skipped from the scheduler's next cycle. Every change is recorded in the admin audit log.
Server-side accounts with argon2id password hashing, signed session cookies (HttpOnly, SameSite=Lax, Secure behind TLS) with a 12-hour absolute and 2-hour idle expiry, session rotation at every sign-in, CSRF tokens on every form, and exponential backoff on sign-in attempts.
Every authenticated request (who, what page, when, from which address), every failed sign-in with the attempted username, every lockout, and every denied request. Anonymous browsing of the public pages is not recorded. Rows are purged after a configurable retention period (default 90 days).
Administrators, in Settings → Activity: filters by event type, username and time window, with counters for failed sign-ins, lockouts and denied requests. Log rows never contain passwords or session tokens.
Use GitHub's private vulnerability reporting on the repository (Security →
Report a vulnerability). Do not open a public issue. See SECURITY.md.
Passwords are hashed with argon2id (the current OWASP recommendation) and never logged, displayed, or sent anywhere. Nobody, including the administrator, can read your password.
- Check the username. It is lowercase.
- Check the password. The error message is the same for a wrong username and a wrong password.
- Wait 15 minutes if you tried many times. The lockout clears by itself.
- Ask an administrator to check that your account exists and is enabled.
Your browser rejected the session cookie. If the instance runs on plain
HTTP (no TLS), the operator must set PEST_COOKIE_SECURE=false. Behind
HTTPS, check that your browser accepts cookies for the site.
- Stop the application.
- Delete all rows from the
userstable. - Verify
PEST_AUTH_USERandPEST_AUTH_PASSin.env. - Start the application. The bootstrap creates the admin account again.
Warning: this removes every account. Use it only for recovery.
Check the browser console. If a Plotly file fails to load, the deployment is missing vendored static assets — rebuild the image. All assets are served locally; the app never fetches from a CDN, so an ad-blocker is not the cause.
The deployment image was built without the markdown files. Rebuild the
image; the Dockerfile must copy CHANGELOG.md and docs/FAQ.md.
Set PEST_SECRET_KEY in .env to a value other than the placeholder.
Generate one with python -c "import secrets; print(secrets.token_hex(32))".
- Read the error. Most failures name the table and revision.
- Restore the database backup.
- Open an issue with the error text and your revision (
alembic current).
- Open the Pipeline page and check the source health dots.
- Check the scheduler container logs:
docker logs <project>-scheduler-1 --since 1h. - A failing source retries on the next cycle. Two consecutive format failures usually mean the upstream API changed shape — check the contract sentinel report.
AGPL-3.0-or-later. If you run a modified version as a network service, you must offer the modified source to its users.
Yes. Open an issue or a pull request on GitHub. The repository ships issue templates, a PR template with the quality gates, and contribution guidelines.
High level: README.md and the /guide page. In detail: docs/adr/ for
architectural decisions and .planning/ for the working plans (private
working repo).
Several, extensively, and they would do it again — although one of them maintains that the correct spelling is "Watson".