This document describes what HashCortx actually does as of v2.0.0. Where a protection is weaker than you might expect, that is stated plainly rather than papered over. If you find a claim here that the code does not support, please open an issue — a security document that flatters the code is worse than no security document.
HashCortx is a local desktop app that:
- Calls AI provider APIs using the user's own keys
- Reads and writes the local filesystem (Coder mode, behind a permission gate)
- Executes shell commands (Coder mode, behind a permission gate and a command denylist)
- Runs Python in a WebAssembly sandbox (Pyodide)
- Has no backend server, no user accounts, and no cloud storage
Out of scope. HashCortx does not defend against a local attacker who already runs code as your user, and it does not defend against a malicious AI provider you have handed a key to. It cannot: it is an app on your machine talking to a service you chose.
Keys are stored as a single JSON bundle under the key hc_api_bundle_v2 in the renderer's localStorage, which Tauri writes into the app's own WebKit data directory:
~/Library/Application Support/com.hashcortx.app/WebKit/
That directory is keyed by the bundle identifier, not by the binary, so it survives every rebuild.
A Keychain item's access control list is bound to the binary's code signature. While the build is unsigned, every new DMG carries a different signature, so macOS would prompt for your password once per key on every single update. That made the Keychain unusable in practice.
src-tauri/src/commands/keychain.rs still ships. On first run, src/platform/tauri/keychain.js silently pulls any keys out of the old Keychain bundle, copies them into the local store, and deletes the Keychain entry so it never prompts again.
What this costs you, stated plainly:
- Keys sit on disk in plain text, inside a directory only this app writes to.
- They are protected by your macOS user account and filesystem permissions, not by encryption.
- Any process running as your user can read them. That is the same exposure as a
.envfile, and weaker than the Keychain. - The JavaScript layer does hold the raw key in memory, and the renderer makes the HTTPS call to the provider directly. The key does not round-trip through Rust.
Code signing is on the roadmap. Once the build is signed, Keychain storage becomes practical again and this section will change.
Every filesystem and shell call the coding agent wants to make passes through HC.guard.request() in src/platform/tauri/guard.js, which raises a dialog before anything executes. The Rust command handlers apply the denylist below independently, so bypassing the JavaScript dialog still does not reach a blocked path.
Coverage is not yet total: Virtual OS and 3D Forge native calls are not routed through the guard. That work is on the roadmap.
From src-tauri/src/security/denylist.rs. These are matched against the absolute, expanded path and refused regardless of what you approve in a dialog:
/.ssh /System /etc
/.aws /usr/bin /bin
/.gnupg /usr/sbin /sbin
/Library/Keychains /usr/lib /private/etc
/private/var
Any path containing these substrings is refused too:
.ssh .aws .gnupg id_rsa id_ed25519 credentials Keychains
This is the important nuance. HashCortx does not restrict the agent to a fixed set of safe commands. It runs what it is asked to run, minus a blocked list that includes:
sudo rm -rf dd mkfs shutdown
su rm -fr fdisk parted reboot
rm -r format diskutil eraseDisk
An allowlist would be stronger. Treat the shell tool as what it is: an agent holding your shell, restrained by a permission prompt and a list of the worst commands.
Every guarded action, allowed or denied, is appended to:
~/.hashcortx/audit.log
Format: TIMESTAMP [scope] action target. It is append-only from the app's perspective, and readable from Settings.
Defined in src-tauri/tauri.conf.json. connect-src is restricted to AI provider endpoints, the grounding backends (Tavily, Google Programmable Search, Wikipedia, PubMed, DuckDuckGo), and local Ollama ports.
Two honest caveats:
script-srcpermits'unsafe-inline'and three external CDNs (cdn.jsdelivr.net,cdnjs.cloudflare.com,cdn.sheetjs.com). This is not a locked-down policy.connect-srcincludes wildcard local ports (http://*:11434,http://*:1234,http://*:8080) so self-hosted model servers work.
Most third-party libraries are vendored into src/js/vendor/ and load from disk rather than a CDN.
- No backend server. Every AI request goes from the renderer straight to the provider you configured. There is no HashCortx intermediary, because there is no HashCortx infrastructure.
- No telemetry. No analytics, no usage reporting, no crash reporting.
- No accounts. Nothing to sign up for.
- No auto-updater. The app never reaches out on its own.
- Air-gapped capable. With Ollama, it works with the network off.
Token counts are appended locally to ~/.hashcortx/usage.jsonl. That file never leaves your disk; HashMeterAi reads it if you install it.
These are commonly assumed, and worth naming because an earlier version of this document claimed several of them:
- No prompt-injection filter. User input is not scanned for jailbreak or instruction-override patterns. (The
/injectcommand toggles RAG and web-context injection into the prompt — an unrelated feature with a confusingly similar name.) - No request rate limiting. There is no concurrency cap and no requests-per-minute cap. There is retry with backoff on
429and5xx, and a Stop button that aborts a run. - No shell command allowlist. See above — it is a denylist.
- No Hardened Runtime, no notarisation, no code signature. The v2.0.0 build is unsigned, so installing it requires a Gatekeeper bypass.
- No encryption at rest for API keys, chat history, or the audit log.
[Your prompt]
|
v
[Renderer: app.js] ---- HTTPS, key in Authorization header ----> [Provider API]
| |
| <----------------------- response ------------------------------+
v
[UI renders] -> token counts appended to ~/.hashcortx/usage.jsonl
[Agent wants a file or a shell command]
|
v
[guard.js: permission dialog] --> [Rust: denylist check] --> [OS]
| |
+----------- refused <---------------+
|
v
~/.hashcortx/audit.log
Open a GitHub issue for non-sensitive findings. For anything that would put users at risk if disclosed publicly, use GitHub's private vulnerability reporting on the repository instead.