Security and privacy,
for your review.
Gnista is a Mac app that watches your coding agents and lets people answer them from the notch. This page is for the people who review it before it goes on a company’s Macs: what it reads, what it sends, and what happens when something goes wrong.
Updated
In short
- Runs on the Mac. No account, no telemetry, no analytics.
- Nothing about anyone’s sessions, code or prompts leaves the Mac.
- Only the network requests listed below, and nothing else.
- It never approves anything by itself. When in doubt, the agent asks in its own prompt.
- Signed with a Developer ID, notarised by Apple, built with the hardened runtime.
What Gnista reads
- Events from the agents’ hooks, sent by its small helper over a Unix socket in Gnista’s folder in Application Support. Local only: the one network listener, with containers turned on, is a port on the Mac’s loopback that asks for a password. A remote machine’s events come through the user’s own ssh, to a socket of its own on the Mac.
- The agents’ own session files and configs, where they are, for titles, progress, cost and plan limits.
- Its own Keychain item, holding the licence key and the start of the trial, which its preferences keep too. Never synced.
- With permission only: Automation and Accessibility, to bring the right terminal tab or editor window forward. Both are optional.
What it never does
- It doesn’t read other apps’ Keychain items, or any credentials.
- It doesn’t probe ports or scan the network.
- It doesn’t send telemetry or analytics to any service, and has no account. A crash report goes only if the user agrees, or the organisation set
CrashReportsModetoalways. - It doesn’t change an agent’s config beyond its own entries: each config is backed up first, formatting and key order are kept, and disconnecting an agent removes exactly Gnista’s lines.
Every network request
| Host | Request | When, and what it sends |
|---|---|---|
gnista.sh | Update checks | About once a day while automatic updates are on. Sends the IP address, as any request does, and Gnista’s version. No profile of the Mac. |
github.com | Downloads: updates; the Linux helper | An update, once a check has found one, and when a remote machine is connected and needs it, the helper for Linux for this version and that processor: from Gnista’s public downloads (github.com/BlixtDunder/gnista-downloads, served from release-assets.githubusercontent.com). Sends the IP address, as any request does. Each is used only if Gnista’s key signed it. |
report.gnista.sh | Licence; crash reports | Licence: when a key is activated, the key and the Mac’s name as the activation’s label; then about once a week to check it, and when it’s removed, to free the Mac’s place, each with the key and this Mac’s activation ID. Gnista’s relay passes only those on to Creem, the merchant of record, and keeps nothing about the licence beyond a count of requests per address, as a salted hash, forgotten the next day. Nothing about agents, projects or usage. Crash reports: only after Gnista or its helper crashes, and only if the user agrees, chose to always send them, or the organisation set CrashReportsMode to always: the error, when it happened and where in Gnista’s code, the names of its threads’ queues, the names and addresses of the libraries it had loaded, Gnista’s and macOS’s versions, the Mac’s model and processor architecture, and a random ID made for crash reports. No user names, file paths, projects, prompts or code. Gnista’s relay, hosted in Sweden, emails it to Gnista’s team and keeps no copy. |
ssh | Remote machines | Only for a machine connected in Settings ▸ Remote, through the user’s own ssh and ssh config, or kubectl for a pod: setting up its helper and its agents’ hooks, a connection that brings their events to the Mac and answers back (none with Connection set to manual), and, on a click, showing an agent’s tmux pane. Nothing goes to Gnista’s team. |
ampcode.com | Amp threads | Only for an Amp session on a row: Gnista runs Amp’s own CLI (amp threads export, amp threads usage, amp usage), signed in as the user, which reads the thread, its cost and the account’s credits from Amp. Gnista adds nothing of its own; nothing goes to Gnista’s team. |
raw.githubusercontent.com | Model prices | Only with Usage turned on: LiteLLM’s public model price list, at most once a day. A plain download; nothing about the user goes with it. |
Turning off automatic updates (or AutomaticUpdates set to false by your MDM) stops the update checks. The trial needs nothing online, and once activated, a personal key works offline; a team key needs a check now and then, for builds released more than 30 days after its last one. RemoteMachinesAllowed set to false closes every tunnel and stops anyone connecting a server.
How approvals fail safe
Answering prompts in Gnista is a setting, for the agents that can take an answer from the notch, such as Claude Code, Codex and OpenCode. A request is only allowed when a person allows it, and Allow waits half a second before it can be pressed. Nothing is approved by default.
- No answer in Gnista: Claude Code shows its own prompt at the same time, and whichever is answered first wins. The other agents get the request back and ask in their own prompt as usual: at once when you go to their tab, after 10 seconds while the notch is closed, and after 45 seconds at most while you’re in the notch.
- Gnista not running: the hook finds nobody listening and returns at once. The agent asks in its own prompt.
- Gnista locked (the trial over, no licence): every request goes straight back, without waiting.
- Hooks never block an agent: status hooks return in about 30 ms and always exit cleanly.
- Remote machines: the helper there waits for the notch only while answering in Gnista is on. Anyone with root on that machine can answer its approvals, as Settings says when a machine is connected.
- Your rules: with a team plan, your MDM can turn answering in Gnista off or limit it to some agents. See Gnista through MDM.
Remote machines
Gnista can show agents running on a server, in a container or in a Kubernetes pod, and answer them from the notch. It’s off until someone connects a machine, and RemoteMachinesAllowed set to false keeps it off.
- The user’s own ssh: Gnista runs the Mac’s
sshwith the user’s ssh config, keys and agent, as they would. It stores no passwords or keys; when logging in needs a person (a password, 2FA), Log in opens Terminal for it. Pods go through the user’s ownkubectl. - A signed helper, no root, no daemon: Gnista copies its helper to
~/.gnistain the user’s home there, and adds its lines to the agents’ configs there, as on the Mac. The Linux helper is signed with Gnista’s key, and Gnista refuses one whose signature doesn’t match; the public key is at gnista.sh/downloads/linux/gnista-linux.pub. It needs no root, installs no service, and runs only when an agent’s hook calls it. - One socket per Mac: each Mac that connects a machine gets a socket of its own there, readable only by that user. Containers on the Mac reach it through a port on the Mac’s loopback, and each connection must start with a random password, kept in
~/.gnista/containersfor the containers that mount it. - Disconnect takes Gnista’s lines out of the agents’ configs there and removes
~/.gnista, unless another Mac still uses it.
When your security tools flag tunnels
Each machine has a Connection setting, and with a team plan, RemoteConnection in your MDM sets it for every Mac:
- Automatic (
automatic): Gnista’s own ssh connection forwards the machine’s socket to the Mac; where the server forwards no sockets, a port on its loopback that asks for a password; and where it forwards nothing, as without forwarding. - Without forwarding (
relay): no forwarding at all. The helper carries the hooks’ messages over an ordinary ssh command’s input and output, as for a Kubernetes pod. - Through my own ssh (
manual): Gnista opens no connection. Set up installs the helper once and gives aRemoteForwardline for the user’s ssh config; their own ssh sessions then carry the agents’ events. WithStreamLocalBindUnlink yesin the server’ssshd_config, a session after a dropped one connects at once.
What it keeps on the Mac
In its folder in Application Support: recently finished sessions, the latest plan limits, the model price list, backups of the agent configs it changed, crash reports not yet sent (dropped after 30 days), the Linux helpers it downloaded, and the user’s own agent, model and jump-rule files. Its settings live in its preference domain, sh.gnista.Gnista; its licence key and trial start in its own Keychain item, and the trial start in its preferences too. Removing Gnista’s integrations from Settings takes its lines out of every agent’s config.
Signing and updates
Gnista is signed with a Developer ID, notarised by Apple and built with the hardened runtime, so it opens without warnings and macOS checks it hasn’t been altered. Gnista finds updates on gnista.sh and downloads them from Gnista’s public downloads on GitHub, through Sparkle, and each one is signed with our EdDSA key; Gnista refuses an update whose signature doesn’t match.
It isn’t sandboxed, and can’t be: it has to read the agents’ files and edit their configs. That’s also why it isn’t in the Mac App Store.
Questions
Something your review needs that isn’t here? Write to support@gnista.sh. More on the website and buying: Privacy.