Pythia handles exchange and broker API keys and can place real orders. Security reports are welcome and are treated with priority.
| Version | Supported |
|---|---|
| 0.5.x (the latest release) | yes |
| older releases | no, please update |
Security fixes land on the newest release line only. There is no long-term support branch.
Please report privately, never in a public issue, discussion or pull request.
- Open the repository on GitHub, go to Security, then Report a vulnerability (GitHub's private vulnerability reporting). The report is visible only to the maintainer.
- Describe what is affected (component, version or commit), how to reproduce it, and what an attacker gains. A proof of concept helps; please do not use real third-party accounts or money to demonstrate it.
- If you found a leaked credential in the repository or its history, say where (commit and path), but do not paste the secret itself.
| Step | Target time |
|---|---|
| Acknowledgement of the report | within 7 days |
| First assessment (valid or not, severity) | within 14 days |
| Fix or mitigation for a confirmed high or critical issue | within 30 days |
| Fix for other confirmed issues | within 90 days |
We ask you to keep the issue confidential until a fix is released, or until 90 days after the report, whichever comes first. Once fixed, the issue is published as a GitHub security advisory with credit to the reporter (unless you prefer to stay anonymous). If a fix needs longer, we agree a new date with you.
This is a non-commercial open source project maintained in spare time; there is no bug bounty.
What Pythia protects, and what it relies on you to do.
- Desktop app: API keys are kept in the operating system's keychain (Windows Credential Manager, macOS Keychain, Secret Service on Linux), never in files, the frontend bundle or logs. Settings can remove each key again.
- Server and research lab: keys come from environment variables, usually a
.envfile or a root-only env file on the server..envfiles, key files and wallet files are gitignored, and the pre-commit hook refuses them. - Grant exchange keys trade permission only, never withdrawal. A leaked key then costs trades, not coins.
- Keys are never written to logs. Startup lines name which providers and venues are configured, at most with the last four characters of a key id. Webhook URLs and the Telegram bot token are scrubbed from every error the alert sender records or returns, and the lab's HTTP clients report only the kind of a network failure, never the URL (which holds the token).
- The pump.fun wallet tool is the only code that creates a key. It writes the key file with mode 600 outside the code folder, shows the secret once at an interactive terminal after a typed confirmation, and never logs it. Only the live executor reads the file, at runtime, after all of its gates hold.
The standalone server has no login of its own. That is a deliberate design decision: it is a single-user engine, and authentication belongs in front of it.
- It listens on 127.0.0.1:8787 unless
PYTHIA_BINDsays otherwise, and warns at startup when bound to a non-loopback address. In a container, bind0.0.0.0inside and publish the port on the host's loopback only (-p 127.0.0.1:8787:8787), asserver/Dockerfileshows. - To reach it from another machine, put it behind a reverse proxy that authenticates every request (for example an SSO proxy, mutual TLS, or at the very least HTTP basic auth over TLS), or use an SSH tunnel. Never expose the port directly to the internet.
- Cross-site requests: state-changing requests (every
POST) and the WebSocket stream are refused when the browser sends anOriginthat is not allowed. Allowed arehttp://localhost,http://127.0.0.1andhttp://[::1]on any port, the desktop app's own origins, and whatPYTHIA_ALLOWED_ORIGINSlists. CORS answers the same origins only. Requests without anOrigin(scripts, the lab's jobs,curl) are not affected. - DNS rebinding: the server answers only requests whose
Hostis a loopback name, an IP address,host.docker.internal, or a name inPYTHIA_ALLOWED_HOSTS(or the host of an allowed origin). SetPYTHIA_ALLOWED_HOSTSto the name your reverse proxy forwards, or make the proxy forward to127.0.0.1. - Local-only endpoints:
POST /api/alerts/eventandPOST /api/ai/completeare taken from this machine (or the addresses inPYTHIA_ALERT_EVENT_FROM) only, and never when the request carries proxy headers (Forwarded,X-Forwarded-For,X-Forwarded-Host,X-Real-IP), because behind a local proxy every caller looks like 127.0.0.1. The AI endpoint additionally requires a token derived from the server's Anthropic key (a SHA-256 of it, compared in constant time), so a caller must hold the key itself, and every call goes through the spending caps. - Input checks: requests are typed JSON with bounds on every number that
matters (alert mute at most seven days, AI caps, token limits, prompt size,
event sizes). An event image must be a PNG or JPEG inside the data folder
(resolved with symlinks, so
..and links cannot leave it), at most 10 MB. Error messages name no file system paths and no secrets. - Real money stays behind the same gates as in the desktop app: typed confirmations, the risk manager, a read-only credential check before arming, the kill switch.
- A strict content security policy: scripts only from the app itself, no
eval, no plugins (object-src 'none'), and network access only to the market data and broker endpoints it uses. - The frontend can call only the app's own commands plus window controls,
notifications, dialogs and opening
https://links in the system browser. There is no shell execute or spawn permission and no file system access from the frontend. - Only
VITE_andTAURI_ENV_variables are exposed to the bundle, so build secrets such as a signing key can never be inlined into it.
- Jobs read their keys from the environment and send them in headers, except the Telegram Bot API, which requires the token in the URL; that URL is never printed or logged.
- The pump.fun live executor sends only to
httpsRPC endpoints and refuses bundle, relay and MEV services unless the owner switches one on explicitly.
Checked with scripts/security-scan.sh. None of these is exploitable through
Pythia as far as we know; each is re-checked on every scan.
| Component | Advisory | Why it stays for now |
|---|---|---|
glib 0.18 (Rust, Linux only) |
RUSTSEC-2024-0429, unsound VariantStrIter |
Pulled in by Tauri's GTK 0.18 stack on Linux. The fix is in glib 0.20, which Tauri 2 does not use yet. Pythia ships for Windows and never calls this iterator. Goes away with Tauri's move to newer gtk-rs. |
proc-macro-error, unic-*, zbus_names, zvariant* (Rust) |
unmaintained (RUSTSEC-2024-0370, -2025-0075/0080/0081/0098/0100, -2026-0370/0371/0372) | Transitive dependencies of Tauri (build-time macros, URL pattern parsing, Linux D-Bus notifications). No known vulnerability; replaced when Tauri replaces them. |
braces via tailwindcss 3 (npm, build tooling only) |
GHSA-vfj7-8cjw-p6xm, stack exhaustion on deeply nested patterns | No fixed version exists. It only parses the glob patterns in tailwind.config.js, which are ours; nothing from users or the network reaches it, and it is not part of the shipped app (npm audit --omit=dev is clean). Removed with the move to Tailwind 4. |
npm run hooks:install(orgit config core.hooksPath scripts/hooks) once per clone: the pre-commit hook runs gitleaks on the staged changes and a fast pattern check for tokens, keys, wallet arrays, personal paths, public IP addresses and e-mail addresses.npm run security:scan(sh scripts/security-scan.sh): gitleaks over the whole history,cargo audit,npm audit,pip-auditfor the lab, and the CycloneDX SBOMs. Run it before every release.
See also docs/security/practices.md for the project's security practices (inspired by the essential requirements of the EU Cyber Resilience Act, without any claim of compliance or certification), and SAFETY.md for the financial risks.