Documentation

GentzyCloak 0.1.1 documentation

Everything shipped with the source, on one page. Pick a topic on the left. Written for people who may be at real risk, so it states plainly what the tool cannot do.
User guidedocs/USER-GUIDE.md

User guide

Carry your privacy. Trust no host. — Powered by LordMoritz.

Written for a Senior Special Assistant (or chief of staff, protocol officer, close-protection lead) responsible for the devices and communications of a principal. No security background is assumed; where a step requires judgement, the guide says so.

Terms: the principal is the person you protect. The host is whatever computer GentzyCloak is running on. The drive is the USB stick that holds GentzyCloak and its GentzyCloak-Data folder.

0. Before you start — preparing the drive (once, on a trusted machine)

  1. Obtain the release file for each platform you may encounter (.exe for Windows, .zip for macOS, .AppImage for Linux). Keep all three on the drive; they are small.
  2. Verify the download hash. On a machine you trust, compute SHA-256 and compare with the value published on the release page:
    • macOS/Linux: shasum -a 256 GentzyCloak-*
    • Windows PowerShell: Get-FileHash .\GentzyCloak-*.exe -Algorithm SHA256

If it does not match, do not use the file.

  1. Use an encrypted USB drive (BitLocker To Go on Windows, an encrypted volume on macOS, LUKS/VeraCrypt elsewhere). This protects anything you export if the drive is lost.
  2. Launch GentzyCloak once on the trusted machine. On the Dashboard note:
    • Portable mode: on — the app is writing only to the drive.
    • Integrity: OK and the root hash (first 16 characters are enough).

Write the root hash on paper or in your phone's notes. You will compare it later on untrusted hosts.

  1. Create the Vault (Section 6) now, on the trusted machine, and enter the items you will need while travelling.
  2. Agree a chat relay with your security team (Section 7) and enter its wss:// URL under Chat → Settings.

macOS note — "Not portable" right after unzipping. If the Dashboard shows NOT PORTABLE and the scanner reports GentzyCloak running from a temp folder, macOS Gatekeeper is running the downloaded, unsigned app from a read-only temporary location ("App Translocation"). GentzyCloak shows a banner when it detects this. Quit, move GentzyCloak.app with Finder to the drive (or run xattr -dr com.apple.quarantine GentzyCloak.app), and relaunch. Portable mode then works and the finding disappears.

1. The Dashboard

The first screen after launch. Read it every time.

IndicatorGreen meansRed / amber meansWhat to do
Portable modeAll data stays on the driveThe drive is read-only; the app is using the host's profile folderPrefer to stop. If you must continue, treat the host as retaining traces and use the panic button before leaving
IntegrityEvery bundled file matches the build manifest and the root hash equals the one you wrote downA file was modified, is missing, or the root hash differs from your noteStop. Someone altered the app on the drive. Use a fresh download on a trusted machine
HostHostname, OS, user, whether you are an administrator—Administrator rights let the scanner see more; as a normal user some collectors will report warnings
Last scanGrade and time of the last scan in this session—Scans are not saved between launches by design; export a report if you need a record

Panic button (top-right, red): wipes browser data, reports and downloads on the drive, locks the vault and exits. The encrypted vault file is kept. The same wipe runs automatically if the drive is unplugged while the app is open.

2. Pre-meeting checklist (principal's laptop or a loaner)

Allow 15 minutes. Do this before the principal uses the device for anything sensitive, and again after it has been out of your sight (hotel room, repair, customs).

  • [ ] Drive plugged in, GentzyCloak launched from the drive, Dashboard shows Portable mode on and Integrity OK with the expected root hash.
  • [ ] Device disconnected from Wi-Fi/Ethernet during the scan if possible (the scan is local; a disconnected device cannot exfiltrate while you look).
  • [ ] Run a Full scan (Section 3).
  • [ ] Grade A or B with no critical/high findings → proceed.
  • [ ] Any critical finding → Section 5. Do not let the principal use the device.
  • [ ] Any high finding → apply the remediation steps or escalate; re-scan.
  • [ ] Export a report (Section 4) to the drive as a record, labelled with the device and date.
  • [ ] Note in your log: device, date/time, grade, report filename, action taken.

3. Running a scan

  1. Open Scanner.
  2. Choose Quick (processes, ports, posture — about a minute) or Full (adds persistence enumeration, privacy grants, deeper checks — several minutes). Use Full for pre-meeting checks.
  3. Press Start. Progress shows each collector. You can Cancel.
  4. When finished you see the grade, the score and the findings list.
  5. Scroll to Collector warnings at the bottom. A warning means part of the device could not be inspected (usually because you are not an administrator). The grade may then be optimistic. If you can, re-run the scan as an administrator.

Reading the grade

GradeScoreMeaning for you
A90–100No significant indicators. Proceed. Re-scan after the device leaves your sight.
B75–89Minor hardening gaps (e.g. firewall off, updates pending). Proceed; fix when convenient.
C60–74Several weaknesses, typically one high finding. Fix the high items before sensitive use.
D40–59Serious. Treat the device as untrusted until findings are resolved.
F0–39Critical indicators present. Go to Section 5 now.

Scoring: start at 100; minus 25 per critical, 12 per high, 6 per medium, 2 per low finding. A single critical finding therefore caps the grade at B, two at D. The grade is a summary; always read the top findings.

Reading a finding

Each finding has:

  • a severity badge and a title;
  • What this means — one paragraph written for the principal;
  • Technical detail and Evidence — paths, commands, ports — for your security team;
  • Remediation steps in order;
  • Recommended tools — free or freemium, reputable, with links;
  • a MITRE ATT&CK id your CERT will recognise.

Do not act on a critical finding's remediation steps before preserving evidence (Section 5).

4. Exporting a report

  1. After a scan, open Report.
  2. Fill in Asset label (e.g. Governor's Office — Laptop 3) and your name as Operator. These appear on the cover page and in the filename.
  3. Choose a format:
    • HTML — the one to send to a principal or a decision-maker. Opens in

any browser, prints cleanly, contains no scripts and loads nothing from the internet.

  • Markdown — for ticketing systems and e-mail.
  • JSON — the complete machine-readable result for your security team.
  1. Tick Include process list only when sending to a technical team; it makes the report long.
  2. Press Export. The file is written to GentzyCloak-Data/reports/GentzyCloak-Report-<asset>-<yyyymmdd-hhmm>.<ext> on the drive. The app shows the path and size.

The first page of the HTML report is an executive summary: asset, operator, date, grade, severity counts and the top risks in plain language. Hand that page to the principal; hand the whole file to the security team.

5. What to do on a critical finding

Critical means the scanner found an indicator that the device is being remotely controlled or actively monitored (for example a known remote-access tool running unsigned, a hidden persistence item launching from a temp folder, screen sharing enabled to an unknown party).

Do, in this order:

  1. Isolate. Turn off Wi-Fi and unplug Ethernet. Do not put the device in airplane mode via software if you can physically disconnect; if it is a loaner, simply stop using it.
  2. Do not reboot, log out, or "clean" the device. Volatile evidence (running processes, network connections) disappears on reboot, and malware may notice removal attempts. Leave it powered on and locked.
  3. Preserve. Export the JSON and HTML reports with the process list included. Photograph the screen if a message or pop-up is visible. Note the time.
  4. Protect the principal. Assume anything typed or displayed on this device recently is known to the adversary: change passwords from a different, clean device; cancel or move any meeting that depended on this device's confidentiality.
  5. Escalate to your organisation's security team or the national CERT / CSIRT with the reports. Keep the drive; do not hand it over without a copy. Do not unlock the GentzyCloak vault on that device.
  6. Only after the security team has taken images or told you to proceed, follow the finding's remediation steps or reinstall the OS.

High findings (e.g. unsigned persistence item, SSH enabled, firewall off): apply the steps, re-scan, and log it. Escalate if the item is unknown to you or reappears after removal.

6. The Vault

The vault is an encrypted file (vault.gcv) on the drive for the items you need on the road: Wi-Fi passwords, backup codes, contact numbers, short notes.

  • Create: Vault → Create vault → choose a passphrase of at least 12 characters. Prefer four or five random words. There is no recovery: if you forget it, the contents are gone.
  • Unlock / Lock: the vault locks automatically when the app quits or you press Panic. Lock it yourself as soon as you have what you need.
  • Entries: Note, Credential (with username and URL) or Code. Body up to 64 KB.
  • Where it is safe to unlock: on your own clean device, or on a host that just received grade A/B. Never on a host with critical findings — a monitoring tool would see what you display.
  • Changing the passphrase: create a new vault on a trusted machine and re-enter items (rename or delete the old file first).

7. Secure chat — invite exchange procedure

GentzyCloak chat is end-to-end encrypted; the relay server only forwards scrambled data. Both sides must have the same invite code ("GentzyKey").

Creating a room (you):

  1. Chat → Create room → give it a name (only you see it).
  2. The app shows the invite code (GZ-XXXXX-…) and an 8-character fingerprint.
  3. Deliver the invite code to the other party by a channel the adversary does not control — in person, a phone call read aloud, a sealed envelope. Never over the same network or messaging app you are trying to avoid. The code has a built-in checksum: a mistyped character is detected when they enter it.

Joining (the other party): Chat → Join room → paste or type the code.

Verify before talking:

  1. Both sides read the fingerprint aloud over a phone call or compare in person. If the eight characters match, you are in the same room with the same key. If they differ, someone tampered with the code — create a new room.
  2. Set an alias each; aliases are self-declared and change each session. Trust the fingerprint, not the alias.

Using it:

  • Messages are held in memory only; closing the app deletes them. Say what needs saying, then close.
  • When IP exposure matters, run Tor on the device and set the proxy mode to Tor in Settings before connecting.
  • Create a new room for each engagement; do not reuse invite codes.

8. The hardened browser

  • Type a URL or a search term in the bar (search goes to a JavaScript-free DuckDuckGo endpoint). The tab shows a lock for HTTPS and a blocked count for trackers stopped.
  • Defaults: HTTPS-only, DNS over HTTPS (Quad9), trackers and third-party cookies blocked, downloads off. Enable downloads only when needed; they go to the drive. The Downloads section in the sidebar lists every file the browser saved (this session and earlier), shows live progress, opens the folder, reveals a file, or shreds it. Reveal and delete refuse any path that is not on the GentzyCloak drive.
  • Proxy: Settings → Direct, Tor (needs a running Tor client) or Custom SOCKS5. The dashboard shows whether Tor is connected.
  • Wipe (browser menu) clears the whole browsing session immediately; quitting or Panic does the same.
  • Do not sign in to the principal's accounts from a host that has not passed a scan. If the host has an unknown root certificate installed (the scanner flags this when it can detect it), HTTPS on that host can be read by the host's operator; the browser cannot fix that.

8a. Vetting a website (Site Scan)

Use Site Scan when a provider sends a demo site and asks the government to consider buying it, or whenever you need to assess a website's security posture.

  1. Open Site Scan and type the address (for example vendor-demo.example.com). Press Scan site.
  2. Passive analysis always runs and does not touch the target beyond loading it like a normal visitor: TLS certificate health, security headers, cookies, technology fingerprint (including exposed .env/.git, source maps and other signs of a rushed or template deployment), DNS and email records, hosting provider, registration date and registrar, and the subdomain surface. These lookups honour your proxy setting, so under Tor mode they travel over Tor.
  3. The active port scan is optional and gated. To include open-port findings, tick "I am authorized to actively test this target" and then "Run active port scan". Only tick the first box if you genuinely have authorization — a vendor inviting you to evaluate a demo is not always the same as permission to scan their infrastructure, and their site may share hosting with third parties you are not entitled to scan. The attestation, the target and the time are written into the report as your audit trail. The scan is connect-only and sends no attack payloads.
  4. Read the grade (A–F) and the findings, ordered by severity. Each finding explains what was found, why it matters, and what to do. Tabs show the full Intelligence (hosting, registration, TLS, fingerprint), Ports and Subdomains.
  5. Export a report (HTML, Markdown or JSON) for the procurement file. The HTML report is self-contained and printable; the JSON is machine-readable for a CERT.

A very new domain, a self-signed or expired certificate, an exposed .env, an open database port, or a session cookie without Secure are all reasons to withhold a purchase recommendation until the provider remediates.

9. Leaving

  1. Export any report you need.
  2. Lock the vault.
  3. Close the app (it wipes browser data and exits) — or press Panic.
  4. Unplug the drive. If you forget step 3, unplugging triggers the wipe.

10. Troubleshooting

SymptomLikely causeAction
macOS: "cannot be opened because the developer cannot be verified"Unsigned buildRight-click → Open, once. Check the integrity root hash afterwards
Portable mode offDrive mounted read-only, or write-protect switch onRe-mount / flip the switch; otherwise Panic before leaving
Integrity red on a host but green at homeApp files changed on the drive, or the host's antivirus quarantined a fileDo not proceed; use a fresh copy
Scan shows many collector warningsNot running as administratorRe-run with elevated rights if policy allows; otherwise treat the grade as optimistic
Chat "invalid invite code"TypoThe checksum caught it; re-enter carefully (O/0 and I/L/1 are interchangeable)
Chat will not connectRelay URL wrong or not wss://; network blocks WebSocketsCheck Settings; try Tor/SOCKS mode
Vault "Invalid passphrase or corrupted vault"Wrong passphrase, or the file was alteredRetry; if the file was altered, restore from your own backup copy

GentzyCloak — Powered by LordMoritz.

Threat modeldocs/THREAT-MODEL.md

Threat model

Powered by LordMoritz.

This document states plainly what GentzyCloak protects, against whom, and where it stops. If a claim in marketing material contradicts this document, this document is right.

1. Assets

#AssetWhere it livesSensitivity
A1Vault contents (notes, credentials, codes)vault.gcv (encrypted at rest); main-process memory while unlockedVery high
A2Vault passphraseuser's head; renderer input → IPC → main; never storedVery high
A3Chat plaintext, room secrets, room keys, session identitymain-process memory onlyVery high
A4Browsing activity (URLs, cookies, cache, downloads)in-memory partition; downloads on USB only if enabledHigh
A5Scan results and exported reportsmemory; reports/ on USB when exportedHigh (they describe a principal's device)
A6The app bundle itself (executable, bundled JS)USB driveHigh (a modified bundle compromises everything above)
A7Settings (relay URL, proxy, alias)*-settings.json on USBMedium
A8Relay availability and metadataoperator's serverMedium

2. Adversaries

AdversaryCapabilities assumedGoal
ADV-1 Public-computer operator (café, hotel business centre, loaner-laptop provider, previous user)Administrator on the host before you arrive; may have installed commodity RATs, stalkerware, keyloggers, browser extensions, proxy/CA certificates; can inspect the disk afterwardsCapture credentials, documents, communications; profile the user
ADV-2 Network observer (hostile Wi-Fi, ISP, national gateway)Passive capture; active MITM attempts; DNS manipulation; traffic-analysisLearn who the user talks to and which services they use; inject content
ADV-3 Malware already on the principal's own machineUser-level or admin-level persistence; may hide from casual inspectionLong-term surveillance of the principal
ADV-4 Physical theft or seizure of the USB driveFull offline access to the drive's contents; unlimited time; forensic toolingRecover vault contents, reports, browsing traces
ADV-5 Relay operator / relay compromiseFull control of the relay process and its network vantage pointRead messages; identify participants; disrupt
ADV-6 Supply-chain tampering (modified download, tampered drive contents)Replace or patch the app before the user runs itTurn the "security tool" into the implant

3. What is mitigated

ThreatMitigationWhere
ADV-1 installed persistence / RAT / remote-access serviceScanner enumerates persistence mechanisms, remote-access services, listening ports, unsigned processes, privacy grants; rules flag known indicators with MITRE mapping and remediation stepssrc/main/scanner
ADV-1 leaves data on the host for laterAll Electron paths redirected onto the USB; browsing tabs use in-memory partitions; panic wipe; automatic wipe on drive removalsrc/main/portable.ts, src/main/index.ts, browser
ADV-1 injected CA / proxy for MITMHTTPS-only mode; DoH bypasses local DNS; tracker blocklist; (certificate pinning is not implemented — see §4)browser
ADV-2 DNS snooping / manipulationDNS over HTTPS (Quad9 / Cloudflare / Mullvad)browser
ADV-2 tracker/beacon-based profilingHost-based blocklist, third-party cookie blocking, user-agent normalisation, WebRTC leak preventionbrowser
ADV-2 traffic contentTLS (browser); AES-256-GCM end-to-end (chat)browser, chat
ADV-2 correlation via IPOptional SOCKS/Tor proxy mode for the browser only — the relay connection is not proxied by the app (see §4)browser
ADV-1 identity certificates planted in the host's certificate storeselect-client-certificate is refused app-wide; no site can silently obtain a client certificate from the hostsrc/main/index.ts
ADV-1 hostile process environment reaching scanner child processes (DYLD_INSERT_LIBRARIES, LD_PRELOAD, NODE_OPTIONS, user-writable PATH, foreign locale, forged SystemRoot)Children get a sanitised environment: preload/interpreter hooks stripped, fixed system PATH, fixed locale; Windows SystemRoot must look like X:\Windowssrc/main/scanner/exec.ts, collectors
ADV-6 symlink planted on the drive (GentzyCloak-Data → the host's home directory)The data directory must be a real directory physically inside the portable root (lstat + realpath), else the app falls back to host userData; the panic wipe never descends symlinked directories and opens files O_NOFOLLOW, sizing the overwrite from the open descriptorsrc/main/portable.ts
ADV-5 relay resource exhaustionGlobal and per-address connection caps, 256 members per room, slow-consumer drop, HTTP header timeout, per-socket rate limit and frame-size caprelay/server.ts
ADV-4 offline vault attackscrypt N=2^17 (≈134 MiB memory-hard per guess) + AES-256-GCM; 12-char minimum passphrase; tamper → authentication failure, not partial datavault
ADV-4 recovering deleted reports/downloadsPanic wipe overwrites once then unlinks (see caveat)portable
ADV-5 reading or forging messagesRelay only sees HKDF-derived room id and opaque ciphertext; messages are Ed25519-signed and replay-filtered; room key never leaves the clientschat, relay
ADV-5 guessing roomsRoom id is 128-bit HKDF output of a 256-bit secretchat
ADV-6 modified bundleSHA-256 manifest of every bundled file, verified at startup; root hash displayed for out-of-band comparison; CI smoke test fails if integrity failsscripts/build.mjs, portable
Renderer compromise via malicious pageBrowsing tabs are sandboxed WebContentsViews with no preload and no IPC access; UI window has sender-guarded IPC (top-level file:// frame only), every IPC argument type/length/enum-validated in main (src/main/ipc-validate.ts), window-open denied, navigation and subframe navigation blocked, webview attach blocked, all web permissions denied; browser settings (including the download directory, which must stay on the GentzyCloak drive) are validated in main, never trusted from the UI; the plaintext-relay override cannot be set over IPCmain, preload
Host data injecting into reports (stored XSS in a report opened on another machine)Every host/user string HTML/Markdown-escaped; report has CSP default-src 'none', no scripts; only http(s) tool links are clickablereport
Torn files on drive yankAtomic temp+fsync+rename writes for vault and settingssettings, vault
Passphrase / vault contents lingeringKey buffer zeroed on lock; plaintext buffer zeroed after parse; entries clearedvault

4. What is NOT mitigated

Be honest with users about these.

GapWhyConsequence
Hardware keyloggers / malicious USB hubs / BadUSBInvisible to softwarePassphrases and messages typed on that host are captured
Screen capture, camera, mic on a compromised hostIf ADV-1/3 already run privileged code, they can read the screen and input regardless of our sandboxingAnything displayed (decrypted vault entries, chat) is exposed
Kernel-level / firmware / hypervisor implantsThe scanner uses userland OS tools (ps, launchctl, lsof, registry, systemctl, …); a rootkit that lies to those tools is not detectedA clean grade is "no known indicator found", not a guarantee
Zero-day or bespoke malwareRules are heuristic and indicator-basedSame as above
Tor is not bundledLicensing, size and update cadence; a stale bundled Tor is worse than none"Tor mode" needs a Tor client running on the host or reachable via SOCKS; without it, IP is exposed to the relay and to sites
No certificate pinning / CA store hardeningWe rely on Chromium's trust store, which an admin-level ADV-1 can have poisonedAn injected root CA on the host enables TLS interception of browser traffic; DoH does not help here. Chat E2E encryption is not affected by TLS interception
Relay metadataThe relay necessarily sees client IPs, connection timing, room ids and message sizes/timingTraffic analysis can link participants; use Tor/SOCKS and a trusted relay operator
Relay availabilityA hostile relay can drop or delay framesDenial of service, not confidentiality loss
Alias / identity is self-declared and per-sessionAnonymity is a featureWithout out-of-band fingerprint verification, a person holding the invite code can join as anyone
Panic wipe on flash mediaSSD/USB wear-levelling remaps blocks; a single overwrite does not guarantee physical erasureForensic recovery possible; use an encrypted drive
Portable mode fallbackRead-only media forces use of the host's userDataTraces on host; the dashboard warns, the user must decide
Files added to dist/ that are not in the manifestThe manifest lists what we built; it cannot list what an attacker adds. Extra files are not loaded by the bundle, but an attacker who can add files can usually also modify listed ones, which is detectedA tampered listed file is caught; a re-packaged app with a regenerated manifest is not — compare the root hash with a value obtained on a trusted machine
The manifest covers dist/ onlypackage.json, the Electron binary/framework and the OS loader are outside it (all JS dependencies are bundled into dist/ by esbuild, so node_modules does not exist at runtime)A patched Electron binary or a replaced package.json main entry is not detected by the in-app check; only the published download hash and OS code signing (not yet in place) would catch it
Relay traffic is not proxied by the appThe chat client uses Node's ws directly; no SOCKS/Tor agent is wired in yetThe relay operator sees the host's real IP even while the browser is in Tor mode. Use a system-wide VPN / Tor transparent proxy, or a Tor-capable network, when the fact of chatting must be hidden
Vault rollbackvault.gcv carries no version counter; an older copy decrypts under the same passphraseADV-4 with write access to the drive can silently revert entries to an earlier state (never read them). Keep a trusted copy if entry history matters
Unsigned buildsNo Apple notarisation / Authenticode yetOS warnings on first launch; the integrity root hash is the compensating control
Electron/Chromium vulnerabilitiesLarge attack surfaceKeep the build current; portable builds are not auto-updated

5. Residual risk table

ScenarioLikelihoodImpactResidual riskPrimary control
Commodity RAT/stalkerware on a public PCHighHighMedium — detection likely for known families; user must act on findingsScanner + report
Host with injected root CAMediumHighHigh for browsing; low for chat/vaultUser guidance: do not enter credentials in the browser on such a host
Kernel rootkit on a loaner laptopLowVery highHigh — undetectedGuidance: assume compromise; use device only for low-value tasks
Hostile Wi-FiHighMediumLow — DoH + HTTPS-only + E2E chatBrowser, chat
USB drive stolenMediumHighLow for vault (scrypt + AES-GCM, strong passphrase); Medium for exported reports unless the drive is encryptedVault; encrypted drive
Relay operator maliciousLow–MediumMedium (metadata)MediumSystem-level Tor/VPN (the app does not proxy the relay itself); run your own relay
Tampered downloadLowVery highLow if hash verified on a trusted machine, High otherwisePublished hashes + integrity root
User unlocks vault on a compromised hostMediumVery highHighOperational guidance below

6. Operational guidance

  1. Verify the build on a trusted machine. Compare the download's SHA-256 with the published value; after first launch, note the integrity root hash shown on the dashboard. On any other machine, if that hash differs or the check is red, stop.
  2. Use an encrypted USB drive. BitLocker To Go, an encrypted APFS volume, LUKS or VeraCrypt. This — not the panic wipe — is what protects exported reports and downloads from theft.
  3. Do not unlock the vault on an untrusted host. Run the scanner first. Only unlock on a device that is yours and clean, or accept that the entries you view may be captured. Prefer copying one entry at a time.
  4. Run Tor yourself when IP exposure matters, and set the browser to Tor mode; verify torConnected in the browser state. The relay connection is not routed through that proxy by the app (see §4) — if the relay operator must not learn your IP, use a system-wide VPN/Tor as well.
  5. Verify the chat fingerprint out-of-band (voice call, in person) before discussing anything sensitive. Read the 8-character fingerprint aloud.
  6. Never type credentials into the browser on a host with a foreign root CA. The scanner's posture and integrity rules try to surface this; if in doubt, use the device only for reading public information.
  7. On a critical finding: isolate, preserve, escalate. Disconnect network, do not reboot or "clean" the machine (evidence lives in memory and in persistence entries), export the report to the USB, and hand the device to your security team or national CERT.
  8. Pull the drive when done. The app wipes ephemeral data and exits automatically; it also wipes on normal close. Use the panic button when you must leave in a hurry.
  9. Rotate. Invite codes are per room; create a new room for a new engagement. Vault passphrase changes require creating a new vault and re-entering items (no in-place rekey yet).

7. Assumptions

  • Node's crypto (OpenSSL) implementations of scrypt, AES-GCM, HKDF and Ed25519 are correct.
  • Chromium's sandbox holds against the sites the user visits.
  • The user can obtain one trusted machine at some point to verify hashes.
  • The USB drive's own firmware is not malicious.
Browser hardeningdocs/BROWSER-HARDENING.md

Browser hardening

Powered by LordMoritz. Module: src/main/browser/. Verified by tests/browser.*.test.ts (pure logic) and scripts/browser-smoke.mjs (real Electron 44 / Chromium 152).

The hardened browser is an ephemeral, privacy-inclined browser that runs inside the GentzyCloak window using Electron WebContentsView. Its design goal is narrow and honest: leave nothing on the host disk and minimise what websites learn about you while you use a machine you do not trust. It is not a replacement for Tor Browser for high-risk anonymity — see "Comparison with Tor Browser" below.

1. What is protected

1.1 Nothing persists on the host

MechanismDetail
In-memory partitionsession.fromPartition('gentzy-ephemeral-<random>', { cache: false }). No persist: prefix, so Chromium never creates a profile directory: cookies, cache, localStorage, IndexedDB, service workers, history, HSTS state live in RAM only.
Fresh partition per wipewipe() (panic button, window close, manual) closes every tab, then runs closeAllConnections, clearAuthCache, clearHostResolverCache, clearCache, clearStorageData, clearData, clearCodeCaches, flushes the cookie store, and rotates to a brand-new random partition.
Verified, not assumedThe smoke test writes a localStorage canary and a cookie canary, loads a page, then byte-scans the whole userData tree: no file contains the page content or either canary; userData/Partitions does not exist.
Downloads off by defaultallowDownloads=false cancels every download. When enabled, files go only to a folder on the GentzyCloak drive — <portableRoot>/GentzyCloak-Data/downloads by default. A custom downloadDir is accepted only if it resolves inside the portable root or the data directory; this is enforced in the main process (sanitizeBrowserSettings), not in the UI, so neither a tampered settings file nor a compromised renderer can point downloads at the host's Desktop/Downloads. Filenames are sanitised and timestamped.
No spellcheck, no preloadSpellchecker off (no dictionary downloads, no text sent to OS services). No preload scripts are registered in the browsing session.

1.2 Network privacy

FeatureSettingWhat it does
Tracker / ad / telemetry blockingblockTrackers560+ curated hosts (Google Analytics/Ads, Meta pixel & SDK, Microsoft Clarity/Bing, Amazon ads, Hotjar, Mixpanel, Segment, comScore, Nielsen, Criteo, Taboola, Outbrain, AppNexus, PubMatic, OpenX, …) blocked in onBeforeRequest, with suffix matching on label boundaries (stats.g.doubleclick.net blocked, notdoubleclick.net not). Plus path-scoped pixels on hosts that must stay reachable (facebook.com/tr, google.com/pagead/, youtube.com/api/stats/). Per-tab and total counters shown in the UI.
HTTPS-onlyhttpsOnlyhttp:// requests are rewritten to https:// before they leave. Exceptions: localhost, 127.0.0.0/8, [::1], RFC1918/link-local addresses, and .onion (Tor already encrypts end-to-end).
Referrer minimisationalwaysSame-site requests carry origin only (https://site.tld/); cross-site requests carry no Referer; https→http never leaks.
Third-party cookiesblockThirdPartyCookiesCookie request headers are stripped and Set-Cookie response headers are dropped when the request's registrable domain (eTLD+1, with a built-in Public Suffix subset — co.uk, com.au, co.jp, gov.ng, com.ng, com.br, github.io, …) differs from the top frame's. Main-frame navigations are never touched.
Privacy signalsalwaysDNT: 1 and Sec-GPC: 1 (Global Privacy Control) on every request.
Chrome telemetry headersalwaysX-Client-Data and other Chrome-internal identity headers are removed.
DNS-over-HTTPSdohEnabled, dohProviderapp.configureHostResolver({ secureDnsMode: 'secure' }) with Cloudflare (cloudflare-dns.com), Quad9 (dns.quad9.net, default) or Mullvad (dns.mullvad.net). secure mode means no fallback to the host's plaintext resolver. Note: this setting is process-wide in Chromium and must be applied after app.whenReady().
Tor (SOCKS5)proxyMode: 'tor'Routes all traffic to socks5://127.0.0.1:9050 (tor daemon / Expert Bundle) or :9150 (Tor Browser), whichever answers. Chromium's socks5:// scheme resolves hostnames on the proxy side, so DNS goes through Tor and DoH is bypassed for proxied requests (dohEnabled shows as off in the state while Tor is active). Fails closed: if no Tor port answers, the session is still pinned to 127.0.0.1:9050, so nothing leaks to the direct network; the UI shows torConnected=false.
Custom proxyproxyMode: 'custom'socks5://, socks4://, http:// with optional credentials (answered via the login event). An invalid proxy string fails closed to an unroutable address.
WebRTCalwayssetWebRTCIPHandlingPolicy('disable_non_proxied_udp') on every tab: no local/LAN IP leaks via ICE, and with a proxy configured no UDP escapes it.
Loopback bypassalways<-loopback> is excluded from proxying so GentzyCloak's own local services keep working.

1.3 Fingerprinting reduction (partial — see §2)

  • User-Agent: a plain Chrome UA for the host OS with no Electron or app token, using the real Chromium major that is running (a mismatched UA vs. Client Hints is itself a fingerprint). Accept-Language is normalised to en-US,en;q=0.9.
  • WebGL disabled by default (webgl: settings.webglEnabled, default false): removes GPU vendor/renderer strings and WebGL canvas hashing, the strongest passive fingerprint. Trade-off: WebGL-dependent sites (maps, 3D, some games) fail with "Failed to initialize WebGL". The operator can opt in per session (Settings → Browser → WebGL, or the GL button in the browser bar, which reopens the current page because the flag is fixed when a tab is created). The start page and the GL button both show when WebGL is on, so the exposure is never silent.
  • No plugins, no WebSQL, no experimental features, no DevTools, navigator.webdriver === false.
  • All permissions denied except HTML fullscreen: camera, microphone, geolocation, notifications, MIDI, USB, HID, serial, Bluetooth (pairing cancelled), clipboard-read, display capture, idle detection, pointer lock, local fonts, sensors, storage-access, payment handler, etc. Both setPermissionRequestHandler and setPermissionCheckHandler deny, setDevicePermissionHandler returns false, device-chooser events are cancelled.
  • Autoplay requires document-level user activation.

1.4 Process and navigation hardening

  • Every tab: sandbox: true, contextIsolation: true, nodeIntegration: false (including workers and subframes), webSecurity: true, allowRunningInsecureContent: false, webviewTag: false, navigateOnDragDrop: false, safeDialogs: true.
  • Navigation allow-list: only http:, https:, about:blank. file:, chrome:, devtools:, javascript:, blob: and top-level data: are refused at will-navigate, will-redirect, will-frame-navigate, in the omnibox, and in window.open handling. Subframes may use data:/blob:/about:srcdoc (needed by ordinary pages).
  • Pop-ups never create native windows; allowed URLs open as a new managed tab.
  • Certificate errors are never bypassed (callback(false)); Chromium's interstitial stays.
  • Client certificates are never offered: select-client-certificate is refused app-wide, so a site (or a MITM) cannot make Chromium silently present an identity certificate from the host's store.
  • Settings are validated in main: every BrowserSettings change from the UI goes through sanitizeBrowserSettings (enum values, booleans, proxy string length, download directory containment); unknown keys are refused.
  • At most 64 tabs (MAX_TABS): window.open beyond the cap is ignored, so a hostile page cannot exhaust memory by spawning views.
  • Only the active tab's WebContentsView is attached to the window; others are detached and background-throttled.
  • The omnibox never sends input to Google: non-URL input goes to https://html.duckduckgo.com/html/?q= (a lightweight, JavaScript-free results page that also works with javascriptEnabled=false).

2. What is NOT protected — read this before relying on it

  1. A compromised host defeats any browser. If the machine you plug into has a keylogger, screen recorder, malicious accessibility service, kernel rootkit, or a hostile administrator, everything you type and see is exposed regardless of how the browser is configured. GentzyCloak's scanner module can surface some of these indicators; it cannot make a hostile host safe. Do not enter credentials or sensitive text on a host you have reason to distrust.
  2. Tor is not bundled. Network-level anonymity (hiding your IP address from websites and your destinations from the local network/ISP) requires Tor, and GentzyCloak does not ship it. Without Tor mode, sites and the network see the host's real IP address; DoH hides DNS names from the local resolver but not your IP from the DoH provider or destination sites. To use Tor mode: run Tor Expert Bundle (tor listens on 127.0.0.1:9050) or simply start Tor Browser (its Tor listens on 127.0.0.1:9150) on the same machine, then choose Tor in browser settings. The status pill shows whether a SOCKS listener answered — a listener on 9050/9150 is assumed to be Tor; the app cannot prove it. When the Tor port is not reachable the browser fails closed (no traffic) rather than leaking.
  3. Fingerprinting is reduced, not eliminated. Screen size, timezone, installed-font metrics, canvas 2D, AudioContext, hardware concurrency, device memory, Client Hints and many behavioural signals are still exposed. Tor Browser's letterboxing, uniform UA, canvas prompt and disabled APIs make its users look alike; GentzyCloak users look like an ordinary Chrome on the host OS — which is a good disguise on a public machine, but not anonymity against a determined tracker.
  4. Anti-fraud and CAPTCHA vendors are deliberately not blocked (Arkose/FunCaptcha, hCaptcha/reCAPTCHA, ThreatMetrix, Sift, PerimeterX, DataDome). Blocking them silently breaks bank logins and checkouts; they do fingerprint you. Email link redirectors (SendGrid, Mailchimp) and podcast prefix hosts are likewise left alone so links keep working.
  5. RAM is not wiped on power loss or process crash. In-memory state is gone when the process exits, but a memory-forensics attacker with physical access to a running machine can read it. Also, the OS may swap process memory to disk; on a hostile host, swap/hibernation files can contain browsing data. GentzyCloak cannot control host swap settings.
  6. Chromium's default profile still exists. Chromium always creates a default profile directory (Cache, GPUCache, Local Storage, DIPS…) under userData for the app's own UI. The browsing session never uses it (verified: no fetched content or canaries appear there), and in portable mode userData is redirected onto the GentzyCloak drive, not the host.
  7. Traffic analysis / local network observation. Without Tor, the local network sees the IPs and SNI hostnames you connect to (TLS 1.3 + DoH do not hide SNI unless Encrypted Client Hello is negotiated by both sides).
  8. DoH provider trust. Your chosen DoH provider sees every domain you resolve. Pick the one whose policy you trust (Quad9 is the default; Mullvad is the most privacy-focused).
  9. Hardware attacks — malicious USB ports (BadUSB/juice-jacking), firmware implants, hardware keyloggers in the keyboard cable, or a rogue display cable capturing video are entirely outside software control.
  10. javascriptEnabled changes apply to new tabs only (it is a per-renderer preference); open a new tab after toggling it.
  11. Blocklist is static. It ships with the app and is not updated at runtime (no network fetch of lists — that would itself be a beacon). New trackers appear constantly; the list is a strong baseline, not an exhaustive filter like uBlock Origin's.

3. Comparison with Tor Browser

GentzyCloak browserTor Browser
Network anonymityOnly when an external Tor is running and selectedBuilt in, always on
Fingerprint model"Look like normal Chrome on this OS""All users look identical" (letterboxing, uniform UA, RFP)
PersistenceNone (RAM only, per-partition, wiped on close/panic)Configurable; private by default
Tracker blockingCurated host list + third-party cookie strippingNoScript + first-party isolation; no ad-list
JavaScript controlGlobal on/offPer-site security levels
Suitable forReading, research, webmail on a public/shared machine you distrust; leaving no traces on that machineWhistleblowing, censorship circumvention, adversaries with network visibility

If your threat model includes a network-level or state-level adversary, use Tor Browser (or Tails). GentzyCloak's browser is about the host not learning what you did, and websites learning less than usual.

4. Operational notes

  • Panic (app:panic) wipes the browser session before the app exits.
  • Closing the app window wipes the session.
  • All settings (BrowserSettings) can be changed live; proxy, DoH, UA and header policy are re-applied immediately. Tor connection status is re-probed on every proxy change.
  • When running GentzyCloak from tooling that exports ELECTRON_RUN_AS_NODE=1 (some IDE terminals do), Electron runs as plain Node and the app will not start; the smoke runner unsets it — do the same in your launcher.

Verification commands

npx tsc -p tsconfig.json --noEmit
npx eslint src/main/browser tests scripts/browser-smoke.mjs
npx vitest run tests/browser.policy.test.ts tests/browser.blocklist.test.ts
node scripts/browser-smoke.mjs   # real Electron, hidden window, ~10 s, needs internet for example.com
Chat protocoldocs/CHAT-PROTOCOL.md

Chat protocol

Anonymous, end-to-end encrypted group chat over a dumb relay. Powered by LordMoritz.

  • Code: src/main/chat/ (client), relay/server.ts (relay), src/shared/relay-protocol.ts (wire format).
  • Crypto: Node built-in crypto only — HKDF-SHA256, AES-256-GCM, Ed25519, SHA-256. No third-party crypto.
  • Protocol version: payload version byte 0x01, relay frame version 1.

1. Goals and non-goals

GoalNon-goal
Anyone with the invite code can talk; nobody without it can read anythingAccounts, phone numbers, contact discovery
The relay is a pipe: sees only opaque room ids and ciphertextHiding that you use the relay from your network / ISP
No persistent identity: a fresh Ed25519 key per app sessionMessage history; late joiners see nothing
No server-side storage or content loggingForward secrecy against invite-code compromise (see §7)
Typos in invite codes are caught before anything is sentDeniability against a peer who holds the invite

2. Invite code ("GentzyKey")

GZ-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXX
  • secret = 32 random bytes (crypto.randomBytes) — the only long-term secret of a room.
  • Encoded as 52 Crockford base32 symbols (0-9 A-Z without I L O U; 260 bits, last 4 bits are zero padding and must be zero on decode).
  • 2-symbol check appended (54 symbols total), then grouped 5-5-5-5-5-5-5-5-5-5-4 with the fixed GZ- prefix.
  • Check value = (Σ dᵢ · 32^(51−i)) mod 1021 over the 52 data symbol values, written as two base32 symbols (check >> 5, check & 31). 1021 is the largest prime below 32²; because every symbol delta is < 32 and 32^k mod 1021 ≠ 0, every single-symbol substitution and every adjacent transposition is detected deterministically (the test suite exhaustively tries all 54 × 31 substitutions). Random garbage is rejected with probability ≈ 1 − 1/1021.
  • Parsing (parseInvite) accepts any case, with or without dashes/spaces, with or without the GZ prefix, and applies Crockford's look-alike mapping O→0, I→1, L→1. U is always invalid.

3. Key derivation

All room material is derived with HKDF-SHA256, salt "gentzycloak/v1":

OutputinfoLengthPurpose
roomId"room-id"16 byteshex → 32 chars; public, sent to the relay
roomKey"room-key"32 bytesAES-256-GCM key; never leaves the process
fingerprint"fingerprint"5 bytesbase32 → 8 chars, shown in the UI to verify out-of-band

HKDF is one-way and the three outputs are independent: knowing roomId (which the relay and any network observer sees) reveals nothing about roomKey, and a fingerprint cannot be turned into either. The invite secret is zeroed in memory when a room is left or the manager is destroyed.

4. Identity

  • On ChatManager construction an Ed25519 keypair is generated (generateKeyPairSync('ed25519')). It is never written anywhere; restarting the app yields a new identity.
  • fromId = base32(SHA-256(rawPublicKey)[0..8]) → 13 chars. This is what peers see next to the alias.
  • The alias is self-declared, ≤ 64 chars, defaults to anon-XXXX. It does not prove who a person is — only fromId is bound to a key — but it is covered by the message signature (§5), so another room member cannot take a captured message and re-label it with a different alias while keeping the original fromId. Receivers strip control characters from it before display. UIs should show both alias and fromId.

5. Message envelope

Plaintext (JSON, UTF-8):

{ "id": "<32 hex>", "alias": "alice", "fromId": "<13 base32>", "pub": "<base64 raw ed25519 pk>",
  "body": "…≤ 8 KiB…", "sentAt": "2026-09-06T00:00:00.000Z", "sig": "<base64 ed25519 sig>" }
  • sig = Ed25519 over canonical(roomId, id, sentAt, alias, body) where canonical concatenates each field as u32be(byteLength) || utf8(field) — unambiguous, so no field can be re-split. pub needs no explicit coverage: it is the key the signature is verified under, and fromId is recomputed from it.
  • Payload bytes = 0x01 || nonce(12, random) || AES-256-GCM(plaintext) || tag(16), with AAD = utf8(roomId). The payload is base64-encoded for the relay frame.

Receiver checks, in order — any failure drops the message silently except where noted:

  1. Version byte is 0x01; length ≥ 29.
  2. AES-GCM authentication with the room key and AAD (wrong key, wrong room, or any flipped byte fails here).
  3. JSON shape; body ≤ 8 KiB; id ≤ 64 chars; alias present (may be empty) and ≤ 64 chars — a missing or oversized alias is a reject, never a default.
  4. pub decodes to 32 bytes and fromId == base32(SHA-256(pub)[0..8]) (constant-time compare).
  5. Ed25519 signature verifies under pub.
  6. |now − sentAt| > 10 min → delivered with verified = false (not dropped; clocks drift).
  7. Replay: per-room LRU of 5000 seen ids; a repeated id is dropped. The sender records its own id too, so an echo is ignored.

The sender renders its own message locally (mine = true); the relay never echoes.

6. Relay wire protocol

One JSON object per WebSocket text frame. Room ids must match ^[0-9a-f]{32}$; payloads are base64, ≤ 16 KiB.

DirectionFrameNotes
client → relay{t:'join', room}idempotent; ≤ 64 rooms per socket
client → relay{t:'leave', room}
client → relay{t:'msg', room, payload}must have joined room first
client → relay{t:'ping'}application keepalive
relay → client{t:'joined', room, peers}ack for join
relay → client{t:'peers', room, peers}membership changed
relay → client{t:'msg', room, payload}fan-out to every other member
relay → client{t:'error', code, message}codes: bad_json bad_frame bad_room payload_too_large rate_limited too_many_rooms room_full not_joined server_error
relay → client{t:'pong'}

Relay policy: 20 frames/s per socket (token bucket, burst 20) → rate_limited + close 1008; WebSocket frames > 64 KiB are refused at the transport; at most 256 sockets per room (room_full); at most 10 000 open sockets and 64 per client address (further sockets are closed with 1013 before any frame is read; the per-address cap uses X-Forwarded-For only when the operator sets RELAY_TRUST_PROXY=1 behind their own TLS terminator); a member whose outbound queue exceeds 1 MiB is dropped rather than buffered without bound; the HTTP upgrade must complete within 15 s; WebSocket ping every 30 s, sockets that miss a pong are terminated; GET /healthz → 200 ok for load balancers; logs contain only connection/room counts, never payloads, room ids or addresses.

Client policy: wss:// required unless the host is loopback; ws:// elsewhere needs the allowInsecureRelay knob, which is a local ChatManager option only — it is deliberately not settable from the UI/IPC, so a compromised renderer cannot downgrade the relay link to plaintext. Relay URLs are validated when set (bad ones are rejected immediately). Reconnect with exponential backoff 1 s → 30 s plus jitter, rejoining all rooms; while disconnected send() fails fast (relay not connected) rather than queueing.

7. Threat model

What the relay operator (or anyone on the path to it) can see

VisibleHidden
Client IP addresses and connection timesMessage text, aliases, identities, public keys, signatures
Room ids (opaque 128-bit values)The invite secret and room key (HKDF is one-way)
Who is in which room, and when (by IP)Whether two rooms belong to the same people
Message timing and ciphertext sizesMessage content — even after compromising the relay later
Nothing after the fact: no storage

The relay can also drop, delay, duplicate or reorder ciphertext. Duplicates are caught by the replay LRU, forgeries by AES-GCM + Ed25519; dropping/delaying is a denial of service the protocol does not try to hide.

Mitigations

  • Network metadata (IP, timing): run the app's traffic through Tor or a VPN, or host the relay as an onion service. Note that the browser module's SOCKS/Tor mode applies to browsing only — the relay WebSocket is opened by Node's ws and is not proxied by the app yet. A system-wide VPN or Tor transparent proxy is currently the only way to hide the client IP from the relay.
  • Traffic analysis by size: not applied by default. Bodies are ≤ 8 KiB; a future version may pad payloads to fixed buckets (e.g. 512 B / 4 KiB / 8 KiB). Operators and users should assume message lengths are observable.
  • Malicious relay injecting frames: every payload must authenticate under the room key and carry a valid Ed25519 signature from the claimed sender; a relay cannot produce either.
  • Room id enumeration: ids are 128-bit HKDF outputs; guessing is infeasible, and joining a room without the key yields only undecryptable ciphertext.

What a peer with the invite can do

The invite is the room. Anyone holding it can read every message sent while they are connected, send messages under any alias of their own, and re-share the invite. What they cannot do is alter or re-label somebody else's message: the alias, body, id and timestamp are all under that sender's Ed25519 signature, and fromId (bound to the key) is stable within a session. Verify the 8-char fingerprint out-of-band before trusting a room, and rotate (create a new room) if an invite leaks.

8. Forward secrecy — known limitation

The room key is static for the life of the invite. Consequences:

  • Anyone who obtains the invite can decrypt future traffic and any past traffic they captured while online.
  • There is no server-side history, so past traffic is not retrievable from the relay after the fact — a later compromise of the invite does not expose messages nobody recorded.
  • Per-session identity keys are ephemeral, so a device seized later cannot be tied to earlier sessions by key material (the invite, if still in memory, is the only link — leave rooms / quit the app to zero it).

There is no ratchet and no per-message key agreement. This is a deliberate trade-off for a zero-setup, invite-only design. Future work: hash-ratchet the room key per epoch, or run X3DH/Double-Ratchet pairwise sessions on top of the room channel.

9. Recommendations

  1. Exchange invites over a channel you already trust (in person, Signal, an encrypted note in the GentzyCloak vault). Never post them where a third party can read them.
  2. Compare fingerprints out-of-band; if they differ, someone has a different invite (or a typo passed a manual check).
  3. Use a wss:// relay you control or trust for availability; treat any relay as hostile for confidentiality — the design assumes it.
  4. Prefer a system-wide Tor / VPN when the fact of communication is sensitive — the app does not proxy the relay connection itself.
  5. Keep sessions short: the identity and all room secrets are gone when the app exits (ChatManager.destroy() zeroes them).
  6. Do not rely on aliases; use fromId to correlate messages within a session, and expect it to change next session.
  7. Message bodies are limited to 8 KiB — there is no file transfer, by design.

10. Test coverage

npx vitest run tests/chat.crypto.test.ts tests/relay.test.ts tests/chat.e2e.test.ts

  • crypto: base32 round-trips, invite format/round-trip, exhaustive single-symbol typo and transposition detection, HKDF determinism and independence, encrypt/decrypt, tamper/wrong-key/AAD failures, forged-signature and impersonation rejection, fromId mismatch, 8 KiB limit both sides, skew → verified=false, replay LRU.
  • relay: health endpoint, A→B delivery / room isolation / no echo, pings, error frames for bad JSON/frames/rooms, oversize payloads, not_joined, rate limit + close, leave semantics, room cap, heartbeat termination.
  • e2e: two ChatManagers exchanging verified messages via an in-process relay, invite typo rejection, garbage and wrong-key injection produce no message event, replay suppression, relay-side socket kill → reconnect + rejoin + traffic resumes, relay URL policy, settings/constructor forms.
  • security (tests/security.chat.test.ts, tests/security.relay.test.ts, tests/security.ipc.test.ts): alias re-labelling of a captured message is rejected, missing/oversized alias rejected, bad relay URL rejected at set time, allowInsecureRelay refused over IPC, peer alias control characters stripped end-to-end; relay global/per-address connection caps (with and without X-Forwarded-For trust), room_full, slow-consumer drop.
Architecturedocs/ARCHITECTURE.md

Architecture

Powered by LordMoritz. Electron 44, TypeScript, Node 24.

This document is for reviewers who want to know what runs where, what talks to what, and what touches disk.

1. Process model

┌───────────────────────────── main process (Node) ──────────────────────────────┐
│ src/main/index.ts                                                              │
│  ├─ portable.ts     resolve paths, integrity check, panic wipe, drive watch     │
│  ├─ settings.ts     atomic JSON stores (browser-settings.json, chat-settings)   │
│  ├─ browser/        BrowserManager: WebContentsView tabs, in-memory partition   │
│  ├─ scanner/        collectors (exec OS tools) → snapshot → pure rules → score  │
│  ├─ chat/           ChatManager: invite/HKDF/AES-GCM/Ed25519, RelayClient (ws)  │
│  ├─ vault/          Vault: scrypt + AES-256-GCM file, in-memory entries         │
│  └─ report/         renderReport / exportReport (html, md, json)                │
│                     ipcMain.handle(...) with sender guard                       │
└──────────────────────────────────┬─────────────────────────────────────────────┘
                                   │ contextBridge (typed, allow-listed channels)
┌──────────────────────────────────┴─────────────────────────────────────────────┐
│ preload  src/preload/index.ts  →  window.gentzy : GentzyApi                    │
└──────────────────────────────────┬─────────────────────────────────────────────┘
                                   │ (sandbox: true, contextIsolation: true,
                                   │  nodeIntegration: false, no remote module)
┌──────────────────────────────────┴─────────────────────────────────────────────┐
│ renderer  src/renderer/  (plain TS, no framework)  shell: sidebar, topbar,     │
│           statusbar, command palette · views: dashboard, browser, scan, chat,  │
│           vault, reports, settings · design system: docs/DESIGN-SYSTEM.md      │
└────────────────────────────────────────────────────────────────────────────────┘
                                   │
                        ┌──────────┴──────────┐
                        │ WebContentsView tabs │  one per browser tab; partition
                        │ (browser content)    │  'gentzy-<random>' (in-memory,
                        │ sandboxed, no preload│  no `persist:` prefix); shown
                        └─────────────────────┘  inside the renderer's layout rect

Key properties:

  • One privileged process. Only main has Node and OS access. The renderer UI is a sandboxed page with a fixed preload bridge; browsing tabs are separate sandboxed WebContentsViews that get no preload at all.
  • IPC sender guard. Every ipcMain.handle verifies event.sender is the app window's own webContents; a browser tab cannot call app APIs.
  • Window hardening. setWindowOpenHandler(() => deny), will-navigate prevented on the UI window, will-attach-webview prevented globally, menu removed, devTools only when unpackaged.
  • Chromium switches set before app.ready: WebRTC non-proxied UDP disabled, mDNS IP hiding, breakpad/crash reporter off, background networking, component update, domain reliability, sync, speech, pings off.
  • Ephemeral by design. Closing the last window wipes the browser session and quits; before-quit locks the vault.

2. Module map

ModuleFilesPure / ElectronTests
Shared contractssrc/shared/types.ts, ipc.ts, relay-protocol.tspurevia consumers
Portable coresrc/main/portable.tspure (electron required lazily inside resolvePortablePaths())tests/portable.test.ts
Settingssrc/main/settings.tspuretests/settings.test.ts
Vaultsrc/main/vault/index.tspure (node:crypto)tests/vault.test.ts
Reportsrc/main/report/index.tspuretests/report.test.ts
Browsersrc/main/browser/{index,policy,blocklist}.tspolicy, blocklist pure; index Electrontests/browser.*.test.ts
Scannersrc/main/scanner/{collectors,parsers,rules,score,remediation,exec}rules/score/parsers pure; collectors exec OS toolstests/scanner.*.test.ts
Site scannersrc/main/sitescan/{target,catalog,net,intel,analyze,rules,index}.tstarget/catalog/intel-parsers/analyze/rules pure; net/index use the browser session + node:tls/nettests/sitescan.*.test.ts, tests/security.sitescan.test.ts
Site reportsrc/main/report/site.tspure (reuses report escaping/CSS)tests/sitescan.report.test.ts
Network statussrc/main/netstatus.tsElectron (session-bound egress probe)e2e
Chatsrc/main/chat/{crypto,index,lru,relayClient}.tspure + wstests/chat.*.test.ts
Relayrelay/server.tsNode + wstests/relay.test.ts
Preloadsrc/preload/index.tsElectronsmoke, e2e
Renderersrc/renderer/DOMtests/renderer.*.test.ts
Main wiringsrc/main/index.tsElectronsmoke (--smoke, CI on Linux under xvfb); e2e (--e2e, src/main/e2e.ts, every feature through window.gentzy, CI on all three OSes)

Rule of thumb enforced by review: anything with logic worth testing lives in a module that does not import electron at top level.

3. IPC contract (summary)

Channel names are constants in src/shared/ipc.ts; payload types in src/shared/types.ts. The renderer sees exactly the GentzyApi interface.

NamespaceRenderer → main (invoke)Main → renderer (events)
apphostInfo(), integrity(), panic(), openExternal(url) (https only)—
scanstart(mode), cancel(), last()scan:progress, scan:done
browsertab CRUD, navigate, back/forward/reload/stop, setBounds, setVisible, get/setSettings, wipebrowser:stateChanged
chatcreateRoom, joinRoom(invite), leaveRoom, send, rooms, get/setSettingschat:message, chat:roomChanged
vaultstatus, create(pass), unlock(pass), lock, list, upsert(entry), remove(id)—
reportexportLast(opts)—

Fallible operations return Outcome<T> = { ok: true, value } | { ok: false, error } rather than throwing across the bridge, so error strings are controlled.

Vault specifics: passphrases cross the bridge once per unlock and are never stored; entries live in main-process memory only while unlocked; lock() zeroes the key buffer.

4. Data-at-rest map

With portable mode active, the only location GentzyCloak writes to is:

<USB>/GentzyCloak-Data/
├── browser-settings.json     proxy mode, DoH provider, blocking toggles (atomic write)
├── chat-settings.json        relay URL, alias                       (atomic write)
├── vault.gcv                 encrypted vault (scrypt + AES-256-GCM, 0600, atomic)
├── reports/                  exported GentzyCloak-Report-*.{html,md,json} (0600)
├── downloads/                browser downloads, only if the user enables downloads
├── electron/                 Electron userData + session (cache, cookies of the
│   └── session/              *UI* partition; browsing tabs use in-memory partitions)
├── tmp/                      Electron temp + crash dumps (crash reporter disabled)
└── logs/                     Electron logs path (app does not write logs itself)

What is never written:

  • vault contents in plaintext, passphrases, room secrets, room keys, chat messages, session identities (memory only);
  • browsing history / cookies / cache of the browsing tabs (in-memory partition, wiped on close and on panic);
  • anything under the host user's profile (~/Library, %APPDATA%, ~/.config) when portable mode is active.

Fallback: if <root>/GentzyCloak-Data is not writable (read-only media, locked-down mount), the app uses Electron's default userData on the host and reports portableMode: false prominently on the dashboard. The user can then decide not to proceed.

panicWipe() overwrites every file under the data directory once with random bytes, unlinks it, removes emptied directories, and keeps vault.gcv (it is useless without the passphrase and the user may need it later). Symlinks are unlinked, never followed.

5. Build and integrity manifest

scripts/build.mjs (esbuild) produces:

  • dist/main/index.js (CJS, electron external),
  • dist/preload/index.js (CJS),
  • dist/renderer/app.js (IIFE) plus copied index.html, styles.css, assets/,
  • dist/integrity.json = { root, files: { "<relpath>": "<sha256>" } } where root = sha256(JSON.stringify(files)).

At startup checkIntegrity(distDir) re-hashes every listed file, recomputes root, and reports { computed, expected, ok, checkedFiles }. ok requires every listed file to exist and match and the recomputed root to equal the recorded one. Files on disk that are not in the manifest are ignored — they are not loaded by the bundle, and the manifest cannot enumerate what an attacker might add; see THREAT-MODEL §4 for why this is still useful and where it stops. If the manifest is missing or corrupt, expected is null, ok is false, and computed is the root over everything found so a user can compare it with a value obtained on a trusted machine.

electron-builder.yml packs dist/** + package.json into an asar (Electron's patched fs reads inside it, so the check works in packaged builds). Targets: Windows portable (x64), macOS zip (universal), Linux AppImage (x64). Builds are not code-signed; see README limitations.

6. CI matrix

.github/workflows/ci.yml:

JobOSSteps
qualityubuntu, macos, windows (fail-fast off)npm ci → typecheck → lint → test → build → (Linux only) headless smoke launch under xvfb-run which asserts renderer readiness, host info, integrity ok and a browser tab open/wipe → headless end-to-end run (npm run e2e, xvfb-run on Linux) that drives browser, scanner, chat (with an in-process relay and a second peer), vault, reports and openExternal through the real preload/IPC bridge against a fresh GENTZY_DATA_DIR, and verifies the project's own GentzyCloak-Data was not touched
packageone runner per target, main branch only, needs qualitypackage:win / package:mac / package:linux → upload release/*

npm run ci locally is the same steps minus the smoke launch (typecheck, lint, test, build, e2e).

7. Cryptographic parameters (reference)

PurposePrimitiveParameters
Vault KDFscryptN = 2^17, r = 8, p = 1, 32-byte salt, maxmem 256 MiB; bounds enforced on unlock
Vault cipherAES-256-GCM12-byte nonce (fresh per save), 16-byte tag, AAD gentzycloak-vault-v1
Chat KDFHKDF-SHA256salt gentzycloak/v1; infos room-id (16 B), room-key (32 B), fingerprint (5 B)
Chat cipherAES-256-GCMAAD = room id; payload 0x01 ‖ nonce ‖ ct ‖ tag
Chat identityEd25519per-session keypair; fromId = base32(SHA-256(pub)[0..8])
Invite code32 random bytesCrockford base32, 2-symbol mod-1021 checksum
IntegritySHA-256per-file + root over canonical JSON

All primitives are Node's built-in crypto; no third-party crypto libraries.

Security policySECURITY.md

Security policy

GentzyCloak is a security tool used by people who may be at real risk. We treat every report seriously and we prefer an awkward disclosure over a silent bug.

Supported versions

Only the latest tagged release and the main branch receive fixes. Portable builds are not auto-updated; users must download a new build and re-verify its integrity hash (see docs/USER-GUIDE.md, "Verifying a build").

Reporting a vulnerability

Please do not open a public GitHub issue for anything that could be exploited before a fix ships.

  1. Open a private security advisory on the repository (GitHub → Security → "Report a vulnerability"), or
  2. If GitHub is unavailable to you, e-mail the maintainer address listed on the LordMoritz GitHub profile with the subject GentzyCloak security. If you need to send sensitive details, ask for a PGP key first and we will reply with a fingerprint you can verify through a second channel.

Include, where you can:

  • affected version / commit and platform (macOS, Windows, Linux),
  • a minimal reproduction or proof of concept,
  • your assessment of impact (what an attacker gains, which asset in docs/THREAT-MODEL.md is affected),
  • whether the issue is already public.

What to expect

StepTarget time
Acknowledgementwithin 3 working days
Triage + severity agreedwithin 10 working days
Fix or mitigation publisheddepends on severity; critical issues are prioritised over all feature work
Public credit (if wanted)in the release notes

We do not run a bug bounty. We will credit you by name or handle unless you ask us not to.

Scope

In scope:

  • the Electron application (src/), its IPC surface and preload bridge,
  • the vault format and its cryptography (src/main/vault),
  • the chat protocol, client crypto and the relay (src/main/chat, relay/),
  • the report renderer (HTML injection through host data is a vulnerability),
  • the build/integrity manifest pipeline (scripts/build.mjs, src/main/portable.ts),
  • the CI workflow and release packaging.

Out of scope (documented limitations, not bugs): everything listed under "What is NOT mitigated" in docs/THREAT-MODEL.md, e.g. hardware keyloggers, screen capture by a compromised host, or kernel-level malware evading userland scanning.

Hardening expectations for contributors

  • No new IPC channel without an entry in src/shared/ipc.ts and a sender guard.
  • No string from the host or the user reaches HTML/Markdown output unescaped.
  • No secrets written to disk outside the vault; no plaintext logging of vault contents, passphrases, invite codes or room keys.
  • npm run ci must be green on all three platforms before merge.

Rendered from the repository at build time · GentzyCloak 0.1.1 · commit 481b794 · Powered by LordMoritz