Velnode Browser · a product of Dignity New Zealand Limited
Product | Version 1.2 | Last updated 2026-07-03
Document type: Product Owner: Dignity New Zealand Limited Applies to: Velnode Browser v2.3.2-beta.1 and later Last updated: 2026-07-03 Document version: 1.2 Status: Approved
This document gives engineers, security reviewers, and partner integrators a structural view of Velnode Browser. It is one level deeper than How It Works and one level shallower than the canon manifest and source code.
Velnode Browser is a product of Dignity New Zealand Limited. The source code is proprietary and source-available; all rights are reserved by Dignity New Zealand Limited.
Velnode is an Electron 37 application. Electron's two-tier process model is preserved unchanged:
main.js). The single trusted process. Owns OS handles, the network, file-system reads/writes inside data/, the AI Gateway, the policy layer, and the IPC allow-list.The renderer is hardened with nodeIntegration: false, contextIsolation: true, sandbox: true. The renderer's only privileged channel is the preload bridge in preload.js, which exposes a small allow-listed API to window.veloraBridge.
preload.js exposes one object to the renderer, window.veloraBridge. Every method on it corresponds to a named IPC channel whose handler is registered in main.js. No additional channels are reachable. Renderer code that tries to invoke an unregistered channel receives an error in the main process and a refusal in the renderer.
The discipline is: new feature โ named channel โ explicit allow-list entry โ explicit handler โ explicit permission check. The order matters; we never add a handler without a permission check, and we never add a channel without a handler.
All AI requests originate in the renderer and are routed to the main process over the vee:request channel. The Gateway:
data/settings.json (Kids Mode lock, Local-Only mode, token caps).node-llama-cpp against a model the user has explicitly downloaded.There is one Gateway. There is one personality wrapper (the Vee system prompt). The renderer cannot bypass either, because the renderer has no direct network access (webRequest is brokered in the main process) and no provider credentials.
The policy layer is a pure function policy(settings, request) โ decision. It is exercised by the Gateway on every call. Its inputs are the current settings.json snapshot and the request envelope; its output is one of {allow_cloud, allow_local_only, refuse_with_reason}.
Kids Mode and Local-Only mode are not UI states; they are policy inputs. Turning them on in the renderer is, in effect, asking the main process to honour them. Once on with the parental code set, the toggle is locked: the renderer can read the state but cannot flip it.
Velnode keeps no application state in localStorage, sessionStorage, IndexedDB, or in-memory caches that are not derivable. State lives in plain JSON files under data/:
| File | Append-only | Notes |
|---|---|---|
data/timeline.json |
Yes | Canonical record. Never edited, only appended. |
data/semantic-memory.json |
No | Derived. Rebuilt deterministically from the timeline. |
data/bookmarks.json |
No | User bookmarks and folders. |
data/notes.json |
No | Sticky notes attached to tabs or URLs. |
data/settings.json |
No | All user-facing settings, including Kids Mode lock. |
data/command-pins.json |
No | Pinned Vee commands. |
data/mindmap.json |
No | Mindmap nodes and edges. |
data/plugins-enabled.json |
No | Plugins explicitly enabled by the user. |
In a packaged installation the data/ folder lives inside Electron's per-user userData directory (on Windows, %APPDATA%\velora-browser\data), so it survives app auto-updates; in development it sits at the repository root.
The choice is deliberate. Plain JSON is auditable, diff-able, and trivially backed up. Append-only is a defence against history rewriting by a bug, a crash, or a malicious plugin.
canon_manifest.json at the repo root is the integrity ledger for tracked files. Every entry is a {path, sha256} pair. The architecture_flags block records invariants that cannot regress (echo-only architecture, single AI gateway, policy layer enabled, Kids Mode locked, file-backed single source of truth, deny-by-default permissions, and others).
Automated hash audits run before and after edits. A drift between an entry's hash and the file's actual hash blocks a release until the drift is reviewed.
The Velnode UI is a small set of HTML files, each rendered into a sandboxed renderer. The chrome and the tab content live in different renderers; HTML surfaces internal to Velnode live in their own renderers:
| Surface | HTML file |
|---|---|
| Main chrome / tabs | index.html |
| Dashboard | dashboard.html |
| Settings | settings.html |
| History | history.html |
| Timeline view | timeline.html |
| Sticky Memory board | sticky-memory.html |
| Markdown viewer | markdown-viewer.html |
| PDF viewer | pdf-viewer.html |
| Blocked-site interstitial | blocked.html |
The canonical design-token vocabulary is --velora-*, shipped in tokens.css (Phase 11 M7). New CSS additions use var(--velora-*) directly; legacy --vlr-* aliases exist only for the ten pre-token CSS files.
The Modular Runtime Core (Phase 8) loads plugins in isolated contexts with declared permissions. Key guarantees:
The official Veehive store is the trusted distribution surface, and Velnode only loads a plugin that carries Velnode's own Ed25519 signature โ an unsigned, tampered, or revoked plugin is refused at load time and never executes. Today Veehive's catalogue is first-party and curated; a signed third-party developer ecosystem is planned for a future release.
Voice is an opt-in surface. The base text-input path works without it. When enabled:
openwakeword-wasm-browser. It listens locally; no audio leaves the device for wake-word detection.Velnode ships under semantic versioning: MAJOR marks a breaking redesign, MINOR a shipped phase, PATCH a sub-phase or fix. The current package.json version mirrors the canon manifest semver. v1.0.0 marked the seal of Phase 8 Vee Workflows v1. Prior phases are not retroactively tagged.
The release workflow seals the canon manifest, bumps package.json, commits with a chore(release): message, tags the semver, and pushes main with --follow-tags.
| Default | Where enforced |
|---|---|
nodeIntegration: false |
Every webContents |
contextIsolation: true |
Every webContents |
sandbox: true |
Every webContents |
| IPC allow-list | preload.js and main.js |
| Navigation guardrails | Main-process navigation hook |
| Deny-by-default permissions | Main-process permission hook |
| API keys in main-process storage | Main process |
| No remote-loaded scripts in renderer | CSP + design rule |
No eval |
Coding rule + lint |
These defaults are reviewed together as a set whenever the AI, voice, or permissions path changes.
As of Phase 21 Stage 1, Velnode adds an optional cloud account layer. The local-first architecture is unchanged; the account layer is additive and opt-in.
Two independent trust layers with different trust properties:
velora-account Cloudflare Worker and a Cloudflare D1 database. Powers login, password-reset, plan state, and Recovery-Kit management.| Module | Role |
|---|---|
lib/account/crypto.js |
Pure Node crypto. KDF, key wrapping, recovery. No I/O, no Electron. Testable in isolation. |
lib/account/store.js |
Local persistence in data/account.json (whole file wrapped via safeStorage, mirroring the vault). |
lib/account/api-client.js |
HTTPS transport to the Worker. No crypto, no disk. |
main.js VLR.ACCOUNT.*.V1 |
IPC orchestration, sender-guarded, plugin-denied. |
account.html / .js / .css |
Presentation only. Secrets are never handled in the renderer. |
All KDF uses scrypt N=2^17, r=8, p=1 with domain-separation labels. Two independent derivations from the account password:
KEK = scrypt(password, "velora-account-v1-kek" โ kek_salt) โ never leaves the device.auth_hash = scrypt(password, "velora-account-v1-auth" โ auth_salt) โ sent to the server.The server re-hashes auth_hash with PBKDF2-HMAC-SHA256 and a per-row salt, storing only the auth_verifier. The server never sees the password or the KEK.
A random 32-byte data_key is generated client-side, wrapped by the KEK with AES-256-GCM, and stored as an opaque blob in D1. A second copy of the data_key is wrapped by a recovery_KEK derived from the 24-word BIP-39 Recovery Kit and stored alongside the first.
The bearer session token is a 32-byte random value; only its SHA-256 is stored on D1. The unwrapped data_key is cached at rest via Electron safeStorage (consistent with the vault and sync-identity pattern) so users are not prompted on every launch. It is cleared on sign-out and on lock.
The velora-account Worker is a separate Cloudflare Worker from velora-sync (which handles R2 blob relay for Phase 14 M3 sync). The two Workers are independent; the existing sync relay is untouched. The velora-account Worker handles only identity, session, and blob-escrow operations.
file_backed_single_source_of_truth โ data/*.json remains the source of truth. The account is an additive opt-in sync path; it does not replace local files.permissions_deny_by_default โ the VLR.ACCOUNT.* IPC channels are sender-guarded and plugin-denied.Document maintained by Dignity New Zealand Limited. Questions: [email protected].