Privacy Policy
nodeterm is a terminal and coding-agent workspace. It ships as a desktop app for macOS and Linux, a self-hosted Server Edition, and nodeterm mobile, a companion app for iOS (App Store) and Android (Google Play). This policy covers all of them.
The short version
- There is no nodeterm account. You never give us an email, name or password to use the apps.
- We do not collect your content. Terminal output, keystrokes, source code, SSH sessions, agent conversations and transcripts stay on your own machines. One exception: while phone notifications are on, the short text of each notification (for example an agent's question) passes through our push server on its way to your phone. It is encrypted in transit but not end-to-end, and we do not store it (section 10).
- There are no analytics, crash-reporting or advertising SDKs in any of the apps, and no tracking of any kind. The Android app does include two Google components, Firebase Cloud Messaging (for notifications) and ML Kit (for QR scanning, which sends Google diagnostic data), described in section 9.
- Optional, anonymous telemetry is OFF by default on the desktop app. You have to switch it on.
- Remote access is end-to-end encrypted. Our relay server forwards bytes it cannot decrypt.
1. Who we are
nodeterm is an independent project. The data controller for the limited processing described below is the project's maintainer, reachable at support@nodeterm.dev.
2. What we do not collect
None of the apps send us any of the following — not in aggregate, not anonymized, not ever. The only exception is the notification text described in section 10, which passes through our push server without being stored, and only while phone notifications are on.
- Terminal input or output, keystrokes, scrollback or clipboard contents.
- Your source code, file contents, file names, repository names or git history.
- Prompts, responses or transcripts from coding agents (Claude Code, Codex, Gemini) or from the built-in chat node.
- SSH hosts, usernames, passwords, private keys or host keys.
- Your canvas/workspace layout, project names, notes or tags.
- Your location, contacts, photos, microphone audio or camera images. (Photos and files you pick in a mobile app to upload go only to your own computer or server. Dictation may involve Apple's or Google's speech service, never ours; see sections 8 and 9.)
- Any advertising identifier. We do not track you and we do not share data with data brokers or ad networks.
These live on your device (and, if you use SSH, on the servers you yourself connect to). We have no copies and no way to obtain them.
3. The network calls the desktop app actually makes
The macOS app is not offline software, so it is worth being exact about the handful of requests it makes to our infrastructure. Every one of them necessarily reveals your IP address to the receiving server, as any HTTP request does.
3.1 Update check always on
Packaged builds check for a new version on launch and every six hours against
nodeterm.dev/updates (which serves release files hosted on GitHub Releases).
The request reveals your IP address and the app version you are running. No
account, no device id, no content. Downloading an update also involves GitHub's CDN, which
sees your IP address under
GitHub's privacy statement.
3.2 Announcements & minimum-version check always on
Packaged builds call api.nodeterm.dev/v1/check on launch and every six hours to
fetch in-app messages (e.g. "a security update is available") and the minimum supported
version. The request carries exactly three parameters: app version, operating
system (darwin/linux/win32) and the release
channel (stable). Nothing is persisted server-side. It is skipped
entirely in unpackaged/development builds and when the DO_NOT_TRACK or
NODETERM_TELEMETRY_DISABLED environment variable is set.
3.3 Anonymous telemetry opt-in — off by default
If — and only if — you switch on Settings → Privacy → Telemetry, the app sends a
ping to api.nodeterm.dev/v1/ping on launch and at most once a day. The entire
payload is:
{
"deviceId": "random UUID generated on this machine",
"appVersion": "0.3.1",
"os": "darwin",
"arch": "arm64",
"channel": "stable"
}
The deviceId is a random identifier created locally on first run. It is not
derived from any hardware id, is not linked to your name, email or any account, and is used
only to avoid counting one installation many times. We use this to know roughly how many
installs there are and which OS/architecture/versions to support — nothing else. It is never
used for tracking or advertising and is never shared with anyone.
To disable it: leave it off (the default), turn it off again in Settings → Privacy, or
set DO_NOT_TRACK=1 or NODETERM_TELEMETRY_DISABLED=1 in the
environment, which overrides the setting.
3.4 Pro licensing only if you buy Pro
Optional Pro features are paid. If you start a purchase, the app opens a Stripe
checkout page carrying the same random deviceId so your purchase can be bound
to your installation; the app then talks to api.nodeterm.dev/v1/license/* with
your deviceId and, if you use one, your license key, and receives a signed
entitlement token that is verified offline on your machine. We never see your card
details. Payment and any billing email you give Stripe are handled by Stripe as an
independent controller under its
privacy policy. If
you do not buy Pro, the desktop app makes none of these requests. (The Android app behaves
differently: it checks its Pro status on every launch, purchase or not. See
section 9.)
4. Remote access and the relay
The desktop app can be reached from the iOS and Android companions, either directly over
your local network / SSH, or through our relay at relay.nodeterm.dev
when the two devices are on different networks.
- The relay is end-to-end encrypted and cannot read your session. Your desktop and your phone hold their own keypairs (NaCl box: Curve25519 + XSalsa20-Poly1305, with a fresh per-session traffic key). All terminal traffic is encrypted on the sending device and decrypted only on the receiving device. The relay forwards opaque bytes.
- What the relay necessarily sees: the IP addresses of the two connecting devices, the size and timing of the encrypted packets, and the short-lived pairing/join token used to match a phone to its host. It never sees terminal content, commands, files, keys or passwords.
- Pairing is confirmed by a human on both ends. Devices are paired with a QR code and a 6-digit code (a short authentication string derived from the shared key) that you compare on both screens, so a malicious relay cannot insert itself between them.
- What our API stores for relay: to let your phone find your desktop later, we mint a
device token bound to your desktop's public key, your phone's random
deviceId, your desktop's randomdeviceId(when it sends one) and, if you have one, your Pro entitlement, plus a short device label (the Android app always sends the fixed label "Android"). That is connection metadata, not content. - Private keys never leave the device. The desktop stores its host key in your Application Support directory, encrypted with the OS keychain where available; an iPhone stores its keys in the iOS Keychain, device-only, never synced to iCloud; an Android phone stores them in an encrypted file whose key is held by the Android Keystore and cannot be exported, in a directory excluded from Android backup and device-to-device transfer.
5. Data stored locally on your devices
All of this stays on your machine. We cannot read it and it is never uploaded to us.
- Workspace and projects — your canvas layout lives in a per-project
.nodeterm/project.jsoninside your own folders, plus an index in the app's data directory. - Terminal sessions and scrollback — live in tmux on your machine; a capped snapshot of recent output is kept locally so sessions can be restored after a reboot.
- Agent transcripts — written by the agent CLIs themselves into their own directories (e.g.
~/.claude) on your machine. - SSH — the desktop app stores only the connection details you type (host, user, port, path to an identity file). It does not store SSH passwords; it uses your system's
sshand your own keys. The iOS app stores the SSH password or private key you enter in the iOS Keychain (device-only, not synced), and pins each server's host-key fingerprint there so a changed key is refused. The Android app does the same in its Android Keystore-protected encrypted storage, which is excluded from backups and device transfers. - Settings, including whether telemetry is on, and a random
device-idfile used only for telemetry/licensing as described above.
6. Coding agents and other third-party tools you run
nodeterm is a workspace for tools you already have. When you run Claude Code, Codex, Gemini or any other command inside a nodeterm terminal — or use the built-in chat node, which drives Anthropic's Claude Agent SDK with your own Claude credentials — that tool talks to its own provider directly, under your own account and that provider's privacy policy. Your prompts and code go to them, not to us; nodeterm neither proxies nor copies that traffic. The same is true of any server you connect to over SSH: that is your infrastructure.
7. Server Edition
The Server Edition is software you host yourself, on your own machine, reached from a browser. It authenticates with a password you choose (stored only as a scrypt hash) and a session cookie. It sends nothing to us: no telemetry, no update check, no license call. Whatever it processes is on your server, under your control — you are the controller of that data, and we have no access to it.
8. The iOS app (nodeterm mobile)
The iOS companion has no accounts and no analytics. It talks only to (a) computers you point
it at, over SSH or the E2EE relay, and (b) api.nodeterm.dev to resolve a relay
join token for a desktop you have already paired with and, if you turn notifications on, to
register this phone for push notifications (section 10). If you buy Pro in
the app, Apple processes the payment and tells our server which product was bought, tagged
with the phone's random device id; we keep that purchase record (product, transaction id,
expiry) to grant Pro. Its permissions are used strictly for the stated purpose:
- Camera — only to scan the pairing QR code your computer displays. No images are stored, saved to your library, or transmitted.
- Microphone + Speech Recognition — only for the optional dictation composer, so you can speak a prompt instead of typing it. Recording starts when you start dictation and stops when you stop; the resulting text is placed in the composer for you to review, and is sent to your terminal only when you press send. Audio is never sent to us and never stored by us.
Important: dictation uses Apple's on-device/Apple-hostedSpeechframework. Depending on your device, language and settings, Apple may process the audio on its servers to perform the recognition. That processing is governed by Apple's privacy policy, not ours. If you would rather not have that happen, simply do not use dictation — the feature is entirely optional and typing works everywhere. - Local Network — only to reach computers on your own network (SSH / direct connection). Nothing is broadcast to us.
The app's Apple privacy manifest declares no tracking, no tracking domains and no collected data types, which matches the behavior described here.
9. The Android app (nodeterm mobile for Android)
The Android app has no accounts, no analytics, no crash reporter and no advertising
identifier. It talks to (a) computers you point it at, over SSH, an end-to-end encrypted
direct connection on your network, or the E2EE relay; (b) api.nodeterm.dev for
the requests below; (c) the Google services listed below; and (d) Hugging Face, only if you
download an optional dictation model. Every request to our servers uses HTTPS.
9.1 Installation id and Pro status check always on
On first launch the app generates a random deviceId (a UUID; not derived from
any hardware id or the advertising id) and keeps it in the app's encrypted storage. On every
launch and every return to the foreground (at most about once a minute), the app sends this
id to api.nodeterm.dev/v1/license/status to learn whether this phone is entitled
to Pro. This happens whether or not you have bought anything, because a purchase can
also reach the phone from elsewhere (for example a Pro desktop you paired with). If you
entered a desktop license key, the app sends that key with the id to
/v1/license/refresh instead. When the id has no purchase and no paired Pro
desktop behind it, the lookup stores nothing. The same id is used for the relay pairing
(section 4), push notifications (section 10) and
Google Play purchases (below).
9.2 Google Play purchases only if you buy Pro
Pro is sold through Google Play Billing. Google processes the payment; we never see
your card details or your Google account. When you start a purchase, the app passes its
random deviceId to Google as the purchase's obfuscated account id. After a
purchase or a "Restore", the app sends the Play purchase token, the product id
and its deviceId to api.nodeterm.dev/v1/google/purchase. Our server
verifies the purchase with Google's Play Developer API and keeps a purchase record: the
purchase token, product, the device it is bound to, an order reference, and the
subscription's state, expiry and auto-renew status. That record is what grants Pro.
Payment is handled by Google under the
Google Privacy Policy.
9.3 Permissions
- Notifications: to show the notifications described in section 10.
- Camera: only to scan the pairing QR code your computer displays. No images are stored or sent to us. You can also pair by typing the code.
- Microphone: only for the optional dictation composer. Recording starts when you start dictation and stops when you stop; the text is placed in the composer for you to review and goes to your terminal only when you send it. Audio is never sent to us.
- Photos and files you choose to upload are picked with Android's system photo picker (no storage permission) and go only to your own computer or server.
9.4 Dictation
The default dictation engine is Android's own speech recognizer. If your phone has an on-device recognizer (Android 12 and later, on supported phones), the audio does not leave the phone. Otherwise the app uses your phone's default speech-recognition service (on most phones, Google's), which may process the audio on its own servers under its provider's privacy policy. If you would rather avoid that, use one of the optional local engines (Whisper or Parakeet), which run entirely on the phone, or simply type.
Local models are downloaded from Hugging Face (huggingface.co) when you
choose one. The download reveals your IP address to Hugging Face, as any download does; no
audio or other data of yours is sent.
9.5 Google components in the app
- Firebase Cloud Messaging (FCM) delivers notifications. It is the only Firebase product in the app: Firebase Analytics and Crashlytics are not included, and analytics collection is explicitly switched off. The app asks FCM for a push token only while notifications are on (the default) and Android allows them; Firebase then creates a Firebase installation id and a push token, which Google uses to deliver messages to this phone. See section 10 for what we store.
- ML Kit barcode scanning reads the pairing QR code, on the phone (the model is bundled with the app). Google states that ML Kit may send Google diagnostic and usage information (device and app information, performance metrics, API configuration, error codes and a per-installation identifier). It runs only while the QR scanner is open; the camera image itself is processed on the phone.
10. Phone notifications (iOS and Android)
The mobile apps can notify you when an agent on your computer needs you or finishes. This is the one place where text that comes from your sessions passes through our infrastructure, so here is exactly how it works.
- Registration. While notifications are on, the phone registers its push token
(an Apple APNs token on iOS, an FCM token on Android) with
api.nodeterm.dev/v1/push/*. We store one row per phone: its randomdeviceId, the platform, the push token and timestamps. On iOS we also store Live Activity tokens, each tied to the id of the canvas node it shows. We also store the choices you make about notifications: which computers you muted, whether ongoing (live) notifications are off, and an id for each push grant (a signed token the app places on a computer you reach over plain SSH, so that computer can notify this phone). - Delivery. To notify you, your desktop sends the notification to
api.nodeterm.dev, which forwards it to Apple (APNs) or Google (FCM) for delivery to your phone. A notification can contain the session or node title, the computer's label, which agent it is, the agent's question or latest message, the answer options it offers, your last prompt line, and progress details. This text is encrypted in transit (TLS) but not end-to-end encrypted: our push server and Apple or Google can technically read it while delivering it. We do not store it and do not log it; our request logs record only the request path, IP address and status. Apple and Google hold a message only until it can be delivered, under their own terms. - Your replies to a notification (approving a step, choosing an answer) go from your phone to your computer over SSH or the E2EE relay, not through our push server.
- Turning it off. Switch notifications off in the app's settings, or mute a single computer. On Android, switching them off marks the phone's registration as revoked on our server and deletes the FCM token; the revoked row itself is kept until you ask us to delete it (section 12).
11. Third parties
- Apple — distributes the iOS app, processes iOS in-app purchases, delivers iOS notifications (APNs, section 10) and provides the speech-recognition framework used by dictation. Apple gives us only aggregate, anonymous App Store statistics; we cannot identify you from them.
- Google — distributes the Android app through Google Play, processes Android in-app purchases (Google Play Billing), delivers Android notifications (Firebase Cloud Messaging), and provides ML Kit for QR scanning and, on most phones, the system speech-recognition service (section 9). Google acts under the Google Privacy Policy.
- Hugging Face — hosts the optional on-device dictation models the Android app can download. A download reveals your IP address to Hugging Face.
- GitHub — hosts the desktop app's release files. Downloading a build or an update reveals your IP address to GitHub.
- Stripe — processes payment if you buy Pro for the desktop. We never receive your card data.
- Our own infrastructure —
nodeterm.dev,api.nodeterm.devandrelay.nodeterm.dev, which see the requests described above (and, like any web server, may keep short-lived operational logs containing IP addresses for security and abuse prevention).
There are no analytics providers and no advertising networks, and the desktop app, the Server Edition and the iOS app contain no third-party SDKs that collect data. The Android app contains two Google SDKs that send data to Google as described in section 9: Firebase Cloud Messaging (only while notifications are on) and ML Kit (only while the QR scanner is open). Neither is used for advertising or tracking.
12. Legal basis, retention and your rights (GDPR)
- Update and announcement checks — necessary to provide and secure the software (legitimate interest, Art. 6(1)(f)). The check endpoint persists nothing; ordinary server logs are short-lived.
- Telemetry — your consent (Art. 6(1)(a)), given by switching it on and withdrawable at any time by switching it off. Records are anonymous aggregate counters keyed by a random installation id, kept no longer than needed to understand version/platform adoption.
- Licensing, store purchases and relay device tokens — necessary to perform the contract you entered when buying Pro (Art. 6(1)(b)), or, for a relay pairing without Pro, to provide the connection you asked for (Art. 6(1)(f)). Kept for the life of the license or pairing, deleted on request.
- The Android app's Pro status check — necessary to give you the features you are entitled to (legitimate interest, Art. 6(1)(f)). A lookup for an id with no purchase or paired Pro desktop stores nothing.
- Push registrations and notification preferences — necessary to deliver the notifications you have switched on (legitimate interest, Art. 6(1)(f)); you can switch them off at any time. Notification text is forwarded, not stored. Registration rows are kept while in use; a revoked row is kept until you ask us to delete it.
Because there is no account and no content collection, we typically hold no personal data about you beyond the records keyed by a random device id described above. You may ask us to access, correct, export or delete those records (your license or purchase record, relay pairing, push registration and notification preferences), or object to processing, by emailing support@nodeterm.dev. There is no account and therefore no self-service deletion page; we handle requests by hand. Because the records are keyed by random ids rather than by your name or email, we may ask for something that identifies them, such as your purchase's order number or license key. Deleting a purchase record removes Pro from the devices it covered; uninstalling the app does not by itself delete anything on our servers. You also have the right to complain to your local data-protection authority.
13. Children
nodeterm is a developer tool and is not directed at children under 13 (or under 16 where that is the applicable age). We do not knowingly collect personal data from children. If you believe a child has provided us personal data, contact us and we will delete it.
14. Security
Remote sessions are end-to-end encrypted; SSH host keys are pinned on first use; secrets are held in the OS keychain where the platform provides one (the iOS Keychain, or the Android Keystore on Android). No system is perfect — if you find a security issue, please report it to support@nodeterm.dev rather than filing it publicly.
15. Changes
If this policy changes materially, the new version will be published here with a new "last updated" date, and significant changes will be announced in the app.
16. Contact
Questions, requests or complaints: support@nodeterm.dev.