Releases: NovaCode37/Prism-platform
Release list
PRISM 2.8.1
A maintenance release. No new capability worth a minor bump, but enough security and correctness work that it should not wait for 2.9.
Security
A webhook could still be sent to a host that refused to resolve. The guard checks every address a hostname resolves to and blocks the private ones, but an unresolvable hostname fell through and the request went out anyway. That branch existed only so two delivery tests aiming at hooks.example.com would pass — the production behaviour was a side effect of the tests. It leaves a rebinding window: answer NXDOMAIN while the check runs, resolve to an internal address by the time requests looks it up again. Any refusal from the resolver now stops the send.
The container ran everything as root. It creates uid 1000 and drops to it. If you bind-mount ./results from a directory owned by someone else, run chown -R 1000:1000 results on the host. CI now checks the image is unprivileged and can still write to all four data directories.
Leaflet came off unpkg with no integrity attribute — the map page trusted whatever the CDN served. Script and stylesheet now carry the sha256 hashes published on leafletjs.com.
numverify was called over plain HTTP, with the phone number and API key in the query string. HTTPS now, falling back only on error 105, the one case where the free plan itself refuses TLS.
Secret scanning, push protection and Dependabot security updates are on for the repository. That surfaced nine advisories nobody had seen; five are closed here.
Added
RDAP module (#306, by @sOuL2000s) — registration data over RDAP alongside WHOIS. Finds the server per TLD from the IANA bootstrap file, falls back to rdap.org, and returns registration dates, registrar, nameservers and contacts.
A keyboard shortcuts panel, tooltips on the standalone tool cards (#305), and multi-arch images on GHCR for linux/amd64 and linux/arm64 on every release tag.
CodeQL runs on pushes, pull requests and weekly. Stale handling touches pull requests only and leaves issues alone.
Fixed
RDAP reported 292 as the registrar. jCard properties are [name, params, type, value], so reading index 2 returned the type and every contact came back as "text". The registrar was read from the entity handle instead, which is the IANA registrar id, not a name. The test fixture used a three-element vCard, which real RDAP never sends, so nothing caught it.
Every .ru domain looked unregistered. A 404 from rdap.org was read as "not registered", but plenty of TLDs serve no RDAP at all. The zone is checked against the bootstrap map first and the module reports skipped.
A rate-limited request came back as a CORS error. add_middleware wraps from the inside out, so CORS ended up innermost and anything short-circuited above it answered without the headers — the browser hid the 429 behind an opaque network failure. CORS is outermost now, and proxy headers still run before the limiter, so the client IP behind a proxy is unchanged.
File work blocked the event loop in two routes. The metadata endpoint copied the whole upload to a temp file inline, and clearing scans read every file in scan_data inline. Both stalled every other request while they ran, websocket scan progress included.
The demo now says on the page, not just in the docs, that AI analysis will not work there.
Upgrading
docker pull ghcr.io/novacode37/prism-platform:2.8.1
Nothing to migrate. The one thing to know is the uid change above if you bind-mount a results directory.
326 → 343 tests.
PRISM 2.8.0 — Accurate Username Search, LLM Fallback & Keyless Shodan
Username search stopped inventing accounts, and two panels that used to sit empty now have something in them.
Username search was reporting accounts that do not exist
Most of the 50 sites were judged by HTTP status alone, so any site that answers 200 for a name nobody registered was reported as a hit. Probing every site with a nonexistent username caught ten doing exactly that: Pinterest, Spotify, Medium, 500px, Imgur, HackerRank, Kaggle, Trello, Duolingo and OnlyFans. Five of them returned a body byte-identical to a real profile's, so nothing in the response said the account was there.
A 200 now triggers one control request per site, using a username that cannot exist. The hit only stands if the target's page names the target while the control's page does not name the control. Sites that echo whatever name sits in the URL can no longer produce a hit at all. The control is fetched once per site per search and only for sites that answered 200, so a scan that finds nothing costs no extra requests.
Measured against 21 live sites: false positives went from ten to zero. Six sites that serve identical HTML for every username now report nothing instead of inventing an account — for a reconnaissance tool that is the right way round.
Twitch was a separate case: its text marker had stopped appearing on the page, so every username came back found. Markers are now cross-checked against the control response, and one missing from both is treated as stale.
401, 403, 429 and 5xx became unknown rather than not_found. A site that blocked us tells us nothing about the account, and calling that absence was producing quiet false negatives.
The AI panel only ever tried one provider
It picked one at import — LLM_API_KEY, else OpenRouter, else Groq — and whichever won was the only one ever contacted. A configured Groq key sat unused while OpenRouter answered Access denied by security policy, which is what the public demo has been showing: providers reject traffic from its hosting region at their edge, before the request reaches a model.
Every configured key is now a provider, tried in order until one answers, each carrying its own model. GROQ_MODEL overrides the Groq default. When all of them fail the response names each provider and its reason instead of one opaque message.
That only helps if some provider accepts your instance. If none do, run the model yourself — the compose file now ships Ollama behind a profile:
docker compose --profile ollama up -d
docker compose exec ollama ollama pull qwen2.5:3bLLM_BASE_URL=http://ollama:11434/v1/chat/completions
LLM_MODEL=qwen2.5:3bNo key needed — LLM_BASE_URL alone is enough now. Budget about 4 GB of RAM for a 3B model. Nothing leaves the machine.
Shodan without a paid key
A free Shodan key gets 403 on the host endpoint, and no key skipped the module outright, so that panel was empty either way. It now falls back to InternetDB, Shodan's own keyless dataset: ports, hostnames, tags, CPEs and the CVE list. Checked against 45.33.32.156 with no key — four ports, one hostname, four CPEs and 120 CVEs where the module previously returned nothing.
A paid key still goes to Shodan and still returns organisation, location and service banners, none of which InternetDB carries. The result records which source answered, and the panel says so. An invalid key is still an error rather than a quiet downgrade.
host_info now requires an IP; it was interpolating whatever it was handed into the request path.
Also
GROQ_MODELand theLLM_*variables are in.env.example, which had none of them despite the README documenting them since 2.6.0.- Thanks to @nightcityblade for correcting the language counts in the README and tightening the exception handling in the username checker.
Tests: 285 → 326.
Full notes in the CHANGELOG. If PRISM is useful to you, a ⭐ helps others find it.
PRISM 2.7.0 — Domain Exposure & Security Fixes
A new exposure source, and three security fixes worth reading before you upgrade.
Domain Exposure (Lunar)
A second angle on compromised credentials, next to the Hudson Rock module. Where Hudson Rock gives you a snapshot of what is exposed right now, this one covers a rolling year: how often the domain turns up in infostealer logs and breach data month by month, split between your staff and your customers, plus which malware families were involved, which services were hit, and where from.
No API key. Aggregates only — the endpoint returns no credentials.
Off by default, like every module that talks to a third party:
LUNAR_ENABLED=true
Domain targets only, since the API covers no other type. Outbound requests honour MODULE_PROXY. Lunar builds a report on first request and caches it for a month, so the first scan of a domain nobody has looked up yet comes back as skipped with GENERATING_REPORT — scan it again once it's ready.
Thanks to @rangeva for proposing it in #286 and for putting the terms in writing.
Security
Rate limits could be bypassed with a header. The limiter keyed on X-Forwarded-For / X-Real-IP whatever TRUST_PROXY_HEADERS was set to. With no proxy in front — the default — a different header value per request meant a fresh bucket every time, so 10/minute on /api/scan and the 200/day and 60/hour ceilings did nothing. Each scan that bought fans out to dozens of third-party lookups. The headers are now read only when TRUST_PROXY_HEADERS is on, which is also when FORWARDED_ALLOW_IPS decides who may set them. Behaviour behind a configured proxy is unchanged, and the daily scan quota was never affected — it keys on the API-key principal.
A username could redirect a lookup to another host. Blackbird interpolated the username into its URL templates unescaped, and the Tumblr template carries the placeholder in the host position. Scanning evil.com/# built https://evil.com/#.tumblr.com/ and sent the request to evil.com. validate_target rejects only [;|`$<>{}], so a slash or a hash reached the module untouched. Usernames are percent-encoded now.
A username could write outside results/. Blackbird built its export filenames straight from the username, so ../../pwned escaped the output directory.
If you run an instance that is reachable from the internet, especially with ALLOW_ANON_API=1, upgrade.
Fixes
- JSON export failed on some usernames —
user?nameraisedOSErrorbefore writing anything, a very long name raisedFileNotFoundError. Same root cause as the export path issue above. - A hanging maigret froze the scan — the wrapper read output to EOF and then called
process.wait(), neither with a timeout, so a maigret that stopped producing output blocked its worker for good and the scan neither finished nor reported an error. A watchdog stops it afterMAIGRET_MAX_RUNTIMEseconds, 600 by default. - A failed Blackbird search looked like a clean one — the API and CLI threw away what the module returned and read its result list, storing an empty list for a search that raised, which reads the same as checking every site and finding nothing.
Tests
249 → 285, covering the new module, the maigret watchdog, the rate-limit key, hostile usernames in export paths, and host escapes in the username URL templates.
Full notes in the CHANGELOG. If PRISM is useful to you, a ⭐ helps others find it.
PRISM 2.6.0 — Browser Extension, Bring-Your-Own LLM & Accessibility
PRISM now lives in your browser.
Browser extension
Highlight or right-click any domain, IP, email, phone, or username on a page and a full scan runs inside the extension popup — live module progress, OPSEC score, and result cards, without leaving the page you're on.
It points at your own PRISM instance or the public demo, takes an optional API key, and speaks all 9 languages.
Install it from Firefox Add-ons →
Bring your own LLM
The AI summary and chat are no longer tied to two hardcoded providers. Set these in .env and point PRISM at any OpenAI-compatible endpoint — OpenRouter, Groq, Gemini, a local Ollama, whatever you run:
LLM_BASE_URL=https://your-provider/v1/chat/completions
LLM_API_KEY=...
LLM_MODEL=...
LLM_PROXY=http://user:pass@host:port # optional
Useful if your host is geo-blocked by a provider, or you simply want a different model.
Accessibility
- Animations and transitions are neutralized when the OS requests
prefers-reduced-motion(#136) - Scan progress is announced to screen readers via an
aria-liveregion (#182) - Scan-type toggles carry proper
aria-label/aria-pressed(#198)
Quality of life
- Copy button on the raw JSON tab (#195) and a
/shortcut to jump to the target field (#196) - Clear button on the target input (#197), legend on the entity graph (#205), "Report an issue" link in the topbar (#202)
- Watchlists: pause/resume, a "Send test webhook" button, JSON/CSV alert export, and readable alert labels
Fixes
- Rate limiting behind a proxy — the limiter now keys on the real client IP instead of collapsing every visitor into the proxy address, which was causing shared 429s
- LLM errors — providers that return an error as a plain string no longer crash the endpoint; non-JSON responses surface a readable message
- Clearer message when an instance requires an API key; spinner on Run Scan while the request is in flight; Watchlists screen fully localized
Full notes in the CHANGELOG. If PRISM is useful to you, a ⭐ helps others find it.
PRISM 2.5.0 — Watchlists, Graph Export, Quotas & Chinese locale
PRISM 2.5.0
This release finishes everything that was on the 2.5 roadmap, plus some security fixes.
Watchlists and scheduled scans
You can now save a target and have PRISM re-scan it on a schedule. Instead of dumping the full results every time, it only alerts you when something actually changed — a port that opened, a new subdomain, a fresh breach, a modified DNS record. Timestamps and other noisy fields are ignored, so an alert means a real change.
Alerts can go to a webhook, and there's a Watchlists screen in the top bar. Endpoints: POST/GET/DELETE /api/watchlist and GET /api/watchlist/{id}/alerts.
Graph export for Gephi and Maltego
Any scan's entity graph can be exported as GraphML or GEXF, either from the Graph tab or via GET /api/scan/{id}/graph/export?fmt=graphml|gexf. Open it straight in Gephi or Maltego.
Per-key quotas and a usage endpoint
Set SCAN_QUOTA_PER_DAY to cap scans per API key. Over the limit returns a 429. GET /api/usage reports how many scans a key has used, its limit, what's left, and when the window resets.
Chinese locale
Added a full Simplified Chinese translation. That's nine languages now, with auto-detection from the browser.
Security fixes
- Scan and watchlist targets that resolve to private, loopback, link-local, or cloud-metadata addresses are blocked by default. Self-hosters who actually want to scan internal hosts can set
ALLOW_PRIVATE_TARGETS=true. - Fixed a path-traversal issue in the Maigret module: usernames are sanitized before they're used in filenames and passed after a
--so a username starting with-can't be read as a flag.
Smaller stuff
--quietand--versionflags on the CLI- Copy-as-cURL button, a Gravatar recon module, and empty-state placeholders in results
- Contributor work: custom 404 page, aria-labels, Italian/Portuguese/Polish locales, a
/api/healthendpoint, target normalization, and more tests - The frontend now runs in CI
Thanks to everyone who sent a PR — 30+ people have contributed at this point.
Demo: https://getprism.su
Full list of changes: CHANGELOG.md
PRISM v2.4.0 — Graceful Degradation, Demo & New Tools
PRISM v2.4.0 — a big quality-of-life release: modules now fail gracefully, a one-command demo, two new standalone tools, per-module refresh, and a pile of fixes.
Live demo: https://getprism.su
Added
- Graceful degradation for key-dependent modules — Shodan, VirusTotal, AbuseIPDB, Censys, Leak-Lookup/HIBP and Telegram now report
skipped(no key) orrate_limited(HTTP 429) instead of failing, with per-module status badges in the dashboard (#61). HIBP_API_KEYsupport for breach lookups (#61).- One-command demo —
docker compose -f docker-compose.demo.yml upboots PRISM with preloaded sample scans, no API keys required (#63). - IP / Subnet calculator standalone tool (#45).
- Hash Identifier standalone tool — MD5 / SHA-1 / SHA-256 / SHA-512 (#76).
- Per-module refresh — re-run a single module from its result card (#104).
- Approximate region-level GeoIP map for phone scans (geocoded operator region).
- Bundled Unicode fonts (DejaVu) so PDF reports render non-Latin text.
- Project polish — README API reference + env-vars table, FAQ, Table of Contents,
LICENSE(MIT),CITATION.cff,SUPPORT.md,CODEOWNERS,.editorconfig,.gitattributes,.dockerignore, Sponsor config, and Bug/Feature issue forms.
Changed
- Scan history is sorted newest-first, auto-refreshes after a scan, and is localized in all 5 languages.
- The scan engine caches only successful (
ok) results, so a missing key isn't frozen in cache (#61). - The Censys tab is hidden when the module is skipped (no key) instead of showing an empty card.
- Removed the default Leaflet attribution flag from all maps.
Fixed
- PDF reports rendered non-Latin (e.g. Cyrillic) text as empty grey boxes — fixed with bundled DejaVu fonts.
- GeoIP map sometimes rendered blank — map now recalculates size after layout.
- New scans could be missing from history — list was capped before sorting.
- Sidebar "Modules" label showed a raw i18n key — fixed and localized.
- Empty numeric env vars crashed startup —
CACHE_TTL_HOURS/MAX_STORED_SCANS/MAX_UPLOAD_MBnow fall back to defaults (#122).
Full changelog: https://github.com/NovaCode37/Prism-platform/blob/main/CHANGELOG.md
v2.3.0 — Scan History, Comparison, Exports, CLI & 5 Locales
What's New
Scan History & Comparison
- Scan history panel in sidebar — browse past scans from
/api/scans, click to load results (#38) - Comparison mode — select two scans and view a side-by-side diff table with added/removed/changed fields (#58)
Export Formats
- CSV export — flattened Module/Key/Value download with BOM for Excel (#43)
- Markdown export — structured
.mdreport with OPSEC, WHOIS, DNS, subdomains, accounts (#55)
Localization
CLI
- Standalone CLI —
python cli.py scan <target>with--json,--html,--pdf,--modules, auto-type detection (#39) python -m prism scanentry point
Integrations & API
- Slack/Discord webhook formatters —
WEBHOOK_FORMAT=slack|discordenv var (#35) - Rate-limit response headers —
X-RateLimit-Limit,Remaining,Resetvia slowapi (#37)
UX Improvements
- Keyboard shortcuts — Arrow Left/Right cycles result tabs (#33)
- Copy all found emails to clipboard (#34)
- Scan duration in results header (#29)
- Username filter in accounts tab (#44)
- Loading skeleton during scan progress (#54)
DevEx
PRISM v2.2.0 — Security Hardening, Report i18n, Code Cleanup
Security Fixes
- Webhook SSRF mitigation — all webhook URLs now validated against private/internal address ranges (closes #23)
- Multi-tenant API key scoping — scan listings and access filtered by
principal; no cross-tenant data leaks (closes #24) - SSRF guard hardened — DNS re-resolution check to prevent TOCTOU bypass on webhook delivery
New Features
- Multilingual Report Engine ([modules/report_i18n.py] — PDF/HTML reports now support EN/RU/DE via
?lang=parameter - i18n keys added:
precision,approximatefor IP geolocation map markers - Copy button for AI summary (PR #19)
Version
2.1.0 → 2.2.0 (backend + frontend)
Compatibility
- No breaking API changes
- No new external dependencies
- Drop-in upgrade from v2.1.x
Full Changelog
v2.1.1 — Webhooks, German locale, PDF fix
Changelog
[2.1.1] — 2026-05-18
Added
- Webhook callback support — pass an optional
webhook_urlin
POST /api/scan; aPOSTis delivered to that URL when the scan
reaches a terminal state. Signed withX-Prism-Secretwhen
WEBHOOK_SECRETis set. Private/loopback hosts are rejected.
Docs:docs/ARCHITECTURE.md(issue #18). - OPSEC category tooltips — hover over a category in the score bar
to see a one-line explanation of what it measures (issue #17). - Alt+T keyboard shortcut to toggle dark/light theme. Topbar
tooltip updated with the hint (issue #15). - German (DE) locale — full UI translation; language switcher now
cycles EN → RU → DE and auto-detects fromnavigator.language
(issue #12). - AI summary copy button refactored to share the global
copyValue+ toast mechanism (PR #19 follow-up).
Changed
- PDF export switched from WeasyPrint (52.5, broken on Windows
without GTK) to xhtml2pdf (pure-Python). A dedicated
PDF-friendly template is used so output is stable across OSes.
Fixed
- PDF export endpoint no longer returns
501/ install errors on
Windows. Generated PDFs render OPSEC score, findings, WHOIS, DNS,
GeoIP, subdomains, threat intel and phone data correctly.
v2.1.0 — Censys, Dark Web checker, PDF export, i18n & progress bar
What's new in v2.1
New modules
- Censys — host services, ASN, open ports, certificate-based subdomain discovery
- Dark Web checker — searches .onion mirrors via Ahmia + DarkSearch
PDF export
- New "PDF Report" button generates a server-rendered print-ready PDF via WeasyPrint
- Endpoint:
GET /api/scan/{id}/report/pdf
i18n — English & Russian UI
- Language switcher in the topbar (EN / RU)
- Auto-detects browser language on first visit
- Easy to extend: drop a JSON file in
frontend/src/messages/
Scan progress bar
- Real-time
5/8 modules · 62%progress bar during scans - Per-module status chips (running / ok / error)
- Works via WebSocket with polling fallback
Other improvements
- One-click copy buttons across all result tabs
- Version badge bumped to v2.1
- Module & source counters updated (22 modules, 12 sources)
- Architecture docs added: docs/ARCHITECTURE.md
- CHANGELOG.md added
Bug fixes
- CI lint fixed (was failing on pre-existing whitespace warnings)
- Progress bar now animates correctly in polling fallback mode (shared hosting)
- WeasyPrint version pinned for Python 3.8 / old libpango compatibility
Self-host in one command:
cp .env.example .env && docker compose up --build