Helix is a private-LAN release. It has real authentication, capability checks, a typed Linux broker, configured-root file controls, native game management, private gateway policy, and focused security tests. It has not completed an independent security review or a public-network deployment review.
Do not expose this build directly to the public internet or use it as the only protection/copy for valuable data. A passing unit or mock test is evidence for that narrow behavior, not proof that a root-capable control plane is generally secure.
Current major gaps include:
- no MFA;
- no complete production master-key delivery, rotation, or independent recovery workflow;
- no independently signed Helix key beyond GitHub HTTPS, SHA-256, and optional GitHub build provenance;
- no package rollback or complete interruption/conffile/dpkg-recovery matrix for the guarded selected-candidate update job;
- no full clean-host broker, filesystem-race, power-loss, UFW, reboot, and game lifecycle matrix;
- no broker peer-credential check beyond the configured Unix-socket filesystem ownership/mode boundary; and
- no independent authentication, broker, or public-exposure review.
Broad/unattended package upgrades remain unavailable. Exact selected APT candidates have an explicit guarded path, but it makes no rollback claim. Helix self-update can apply a digest-pinned GitHub source archive to Helix containers and broker binaries only. Public release to the internet remains blocked in PROGRESS.md.
Helix is a local-first Linux control plane with authority over host state and valuable game data. Its rules are:
helixdremains unprivileged;- root operations cross only the length-bounded typed Unix-socket broker;
- the broker exposes no general shell, caller-selected binary, or arbitrary systemd-unit operation;
- every protected HTTP operation checks authentication, CSRF proof, and its named capability on the server;
- state-changing HTTP requests validate Origin/Fetch Metadata before side effects;
- configured roots and exact opaque identities are revalidated at the broker;
- AMP remains a separate manager and does not inherit native Helix semantics;
- a failed or unverified action is not converted into a success message; and
- game workloads and console capture remain independent of the browser.
High-value assets include:
- owner credentials, password hashes, sessions, CSRF proofs, and future MFA or recovery material;
- installation keys and encrypted secret records;
- broker socket/configuration and every privileged request/result;
- users, capabilities, preferences, audit history, and instance definitions;
- worlds, server configuration, plugins/mods, console history, backups, and deleted-backup recovery records;
- AMP service credentials and AMP-managed instance identity;
- downloaded runtime/marketplace artifacts and update metadata; and
- availability of
helixd,helix-privd, Docker, AMP, native game servers, storage, and recovery workflows.
Metrics, package caches, and console archives are lower-authority data, but can still expose private content or exhaust resources.
- Browser to private gateway/
helixd: attacker-controlled HTTP input, cookies, CSRF proofs, JSON, paths, names, and search terms. helixdto critical/metrics state: an unprivileged process writes data with different corruption and confidentiality consequences.helixdtohelix-privd: a length-bounded typed frame crosses a group-protected Unix socket into a root process.- Broker to configured storage roots: filesystem names, links, mounts, permissions, and concurrent replacement can be hostile.
- Broker to Docker/native games: server artifacts and plugins/mods may be compromised and must not receive Helix authority.
- Broker to AMP: a separately protected credential reaches a loopback AMP API whose responses may be unavailable, ambiguous, or maliciously shaped.
- Helix to UFW/systemd/APT tools: root-owned host interfaces whose output and current state must be parsed and verified without a shell.
- Helix to upstream catalogs: Minecraft, Java, Modrinth, and other remote metadata/artifacts are untrusted even over TLS.
- Backup/import boundaries: archives may be corrupt, stale, stolen, or crafted to escape intended roots.
- Browser/
helixdtohelix-terminald: a short-lived authenticated WebSocket bridges into one unprivileged Linux user's PTY over a separate group-protected, peer-credential-checked Unix socket. - Local root/kernel: unrestricted root can read memory, replace binaries, forge local evidence, and bypass application policy. This is outside Helix's confidentiality/integrity guarantee.
Before an owner exists, helixctl setup-token creates a random, single-use,
short-lived enrollment token and stores only its hash. Owner creation and token
consumption are atomic so concurrent requests cannot create two first owners.
Passwords use Argon2id with bounded PHC parsing and prospective-password rules. Parameters are stored with the hash and can be upgraded after a successful login. Supported-hardware calibration and compromised-password screening remain release work.
Sessions use opaque random bearer tokens; only a cryptographic hash is stored.
The browser cookie is HttpOnly, host-only, SameSite=Strict, and scoped to
/. By default Helix enforces a 30-minute idle deadline and an eight-hour
absolute deadline. Settings can turn that expiry off so the same cookie lasts
until logout or a password change (capped at 400 days). CSRF pairing and
credential-change revocation stay required. A reviewed HTTPS deployment must
add the appropriate Secure/host-prefix policy.
Every protected route requires the session cookie and the current session-bound
X-Helix-CSRF proof. The frontend stores only that proof in origin-scoped
browser storage so a reload can revalidate the still-HttpOnly cookie and proof
through /auth/me. Normal idle and absolute deadlines remain server-enforced;
an invalid or expired session clears the saved proof. Browser origins include
the port, so another service on the same host but a different port cannot read
it. CSRF rotation is compare-and-swap: one proof cannot successfully create two
replacement proofs.
State-changing routes validate configured Origin and incompatible Fetch Metadata before authorization-dependent body processing. Login, bootstrap, preferences, and expensive storage analysis have bounded operation-specific rate state.
Changing the owner password invalidates older sessions through the account authentication version. MFA does not exist and the UI must not imply it does.
The server is authoritative. Hiding a control in Preact is never an authorization mechanism.
Current capabilities include:
system.view,system.settings.write, andsystem.power;dashboard.customizeandusers.manage;storage.files.read,storage.files.manage, andstorage.analyze;network.firewall.readandnetwork.firewall.write;system.packages.readandsystem.packages.write;terminal.open;games.view,games.manage, andgames.backups.manage.
HTTP authorization is checked before a broker request. The broker then repeats authority-independent validation: root/config identity, configured roots, opaque IDs, exact container names, bounded values, expected state, and operation-specific concurrency.
The current owner model has capability foundations but not a mature multi-user, per-instance delegation UI. Do not present future fine-grained grants as implemented.
helix-privd is a separately deployed root process. It has no TCP listener,
frontend, plugin runtime, or general command executor. Its configuration must be
absolute, root-owned, private, and bounded. Its Unix socket is created with
restricted group access.
The protocol is a closed, versioned Rust enum framed with a bounded length.
Operations name typed values such as an opaque server/rule/backup/operation ID,
bounded port range, or configured-root path. There is no
run_root_command(String) request.
The service example uses a systemd sandbox and explicit writable paths. Native content roots remain narrow. Selected APT updates, UFW changes, and recurring reboot schedules deliberately require write access to reviewed OS-managed trees, and package/UFW work cannot keep kernel-tunable and set-ID creation blocks that would make legitimate host mutations fail halfway through. Those operations therefore carry real root-host authority even though the request protocol remains typed and closed. The profile and its target-host exceptions still need full review. The current socket boundary relies on filesystem ownership/mode and service-group isolation; kernel peer credentials are not yet independently checked by the broker, so the complete peer-identity gate remains open.
File operations are restricted to configured roots. Linux implementations use descriptor-relative traversal and reject unsupported types, traversal, and link escapes at the operation boundary. Text reads/writes, directory listings, and analysis results have explicit limits.
Writes use revision or current-state checks where applicable. Deletion moves an entry into configured recoverable trash rather than claiming irreversible removal succeeded.
The storage analyzer is bounded and cancellable, does not follow symbolic links, stays on the selected filesystem, calculates allocated and logical sizes from metadata without opening file contents, and avoids holding a complete large tree in memory. Read-only analysis roots are configured independently from writable file-manager roots, so whole-host drive inspection does not expand mutation permissions. Mount replacement, permission changes, extremely deep/wide trees, low disk, and live multi-terabyte behavior still require disposable target-host testing.
Dashboard preferences, including Home layout names, notes, widget settings, and colors, are ordinary application state. They are revisioned and included in verified critical-state backups, but they are not encrypted field-by-field. Do not use a Home note as a password, private-key, or token vault.
Helix-native Minecraft instances use Docker and configured state/instance/backup roots. Docker calls use structured argument arrays rather than shell strings. Per-instance operation guards prevent incompatible actions from running at the same time.
Native console input is one bounded line. Output capture runs independently of the browser and writes protected rotating segments with byte/file limits. Paged console history is bounded; persistence means retained across browser closes and boots, not unlimited retention.
Native backup deletion creates an opaque trash record and moves only the exact known archive/metadata pair. Undo restores only the matching protected record within policy. Removed native servers use the same kind of recovery storage. Permanent deletion from Removed and hidden requires the exact server name and then deletes world files, backups, and console history. This does not replace off-host backups or prove restore after disk loss.
AMP credentials must be root-owned/private, and the AMP endpoint must remain on
loopback. AMP responses are bounded and strictly interpreted. An unavailable or
ambiguous AMP action fails. Helix does not adopt AMP or Pterodactyl instances
as native servers. A separate copy job can read a stopped source and write a
new Helix instance; AMP kvp, server.properties, worlds, and Pterodactyl
volumes are not rewritten or deleted. Discovery still does not turn an imported
connection into a helix: server.
The start-on-boot operation accepts one boolean and changes Docker restart policy only on the exact configured Helix dashboard and gateway container names. It does not enable/disable Docker or change current container running state. A future container recreation may reapply Compose policy, which the UI states explicitly.
Native game servers have a separate PUT /api/v1/servers/{instance_id}/start-on-boot
control. It updates that instance’s Docker restart policy and manifest flag
without starting or stopping the workload now. It does not change Helix
dashboard/gateway containers.
One-shot whole-host reboot requires:
- capability
system.power; - exact current hostname in the request (filled by the confirmation dialog);
- explicit confirmation acknowledging the interruption;
- active-player and running-job preflight; and
- an opaque systemd operation: immediate requests use a transient service; optional API delays of 10–300 seconds use a cancellable timer.
Recurring reboot stores one verified daily or weekday local-time schedule and reports the host timezone and next activation. It uses the same hostname, acknowledgement, and workload preflight boundaries. Helix never chains package work to an automatic reboot.
Tests mock these calls and never reboot the host. Disposable live Linux validation remains mandatory before relying on this control.
The Network page deliberately separates:
- sockets bound on this host;
- Docker host/container port publication;
- UFW installed/active/default/rule state; and
- a same-router UPnP mapping and WAN-address classification; and
- outside reachability.
Outside reachability remains unverified even after the router confirms a mapping. Docker DNAT can take a different path than UFW INPUT. Helix never labels a listener or matching rule as proof a router, upstream firewall, CGNAT boundary, or ISP permits traffic.
Rule mutation is available only when UFW is installed, active, and verified. Helix rules use an exact opaque UUID comment and a durable private ownership record. Create, delete, restore, and crash-pending reconciliation compare exact rule bodies and record before/after evidence under one mutation lock.
A separate activation flow can enable an installed inactive UFW only after the operator supplies an exact confirmation and a TCP port that Helix independently observes listening. It stages and records an exact allow rule for that SSH port, enables UFW, verifies the active state and exact rule, and attempts to restore the inactive state if verification fails. Helix never resets UFW or changes its defaults.
Native-server public setup discovers only UPnP Internet Gateway Devices on the
local link. SSDP responses are bounded; the description URL must use plain HTTP
to the literal private/link-local IPv4 sender; redirects, DNS names,
credentials, fragments, cross-origin control URLs, oversized HTTP/XML bodies,
and XML document types are rejected. Helix checks for any existing TCP or UDP
mapping before creation, verifies its exact internal address, port, enabled flag, and
description afterward, and journals ownership. If AMP already has that port
claimed, Helix names the AMP instance when it can and tells you to change the
port in AMP (stop the instance, Configuration → Server Settings / Portals,
Apply). Helix does not rewrite AMP files or call AMP SetConfig. If the instance
is gone and only a leftover UPnP mapping whose description starts with AMP
remains, a separate confirmed release can delete that router mapping only.
Removal of Helix-owned public access re-verifies
that exact body and refuses drifted/unowned state. Minecraft and Terraria
request TCP. V Rising requests UDP for the game and query ports. Valheim
requests UDP for the game port and the next two.
UFW is supplemented only if already active. A CGNAT/private/reserved WAN address
is reported as blocked rather than public. A confirmed mapping is reachable from
the public internet; scanners find common game ports without the operator
sharing the address. Helix does not treat that as proof of a leaked IP. Live
router/UFW mutation testing is still a release gate and must use disposable
rules and a controlled router.
Opening the package page reads dpkg/APT inventory and a no-change preview
without refreshing lists or mutating dpkg. A separate explicit refresh job can
run apt-get update. helix-privd passes APT::Sandbox::User=root so APT's
HTTPS helper does not seteuid to _apt; systemd NoNewPrivileges would
otherwise kill that download. A separate selected-candidate job accepts bounded
exact name/installed/candidate tuples, rejects holds and changed candidates,
requires download headroom and disruption acknowledgement, re-runs a
no-removal/no-new-package preview, preserves existing conffiles, serializes APT
work, and verifies every final installed version. Bounded job logs are retained.
This is not transactional package rollback. Power loss, maintainer-script
failure, dpkg partial configuration, service disruption, and unusual conffile
states still require the distribution's recovery tools and a disposable test
matrix. Helix never auto-reboots after package work. Kernel, libc, systemd, and
similar packages are labeled as often needing a host reboot; /var/run/reboot-required
is the OS truth after apply. Reboot remains a separate system.power action.
The terminal is intentionally not part of the root broker. helix-terminald
runs as one configured non-root Linux account, starts a fresh login shell in
that account's home, and has no path to the broker group. Its private socket is
owned by a distinct helix-terminal group and accepts only a process whose
kernel-reported effective UID exactly matches the pinned dashboard UID. Root is
not an accepted configured peer UID.
Opening a browser terminal requires capability terminal.open, the current
session/CSRF proof, and a fresh verification of the current Helix password. The
API then sets a random 30-second, one-use, session-bound, path-scoped HttpOnly
cookie; the ticket is never returned in a URL or JSON body. WebSocket setup
requires an exact same Origin and one exact helix-terminal-v1 subprotocol.
The daemon caps concurrent sessions and frame/input sizes, clears the inherited
environment to a minimal allowlist (HOME, USER, locale, TERM, and a conventional
runtime dir when it exists), starts an interactive login shell, and kills the
PTY when transport ends. Helix audits password rejection plus session
opened/closed/failed events, but does not store terminal commands, keystrokes,
output, or environment values. Copy and paste stay in the browser clipboard;
Helix still does not log that content. Normal host policy still applies inside
the shell: sudo may prompt and can increase authority if that Linux user is
allowed to use it. Long-running work belongs in tmux or a supervised service
because a browser disconnect ends the PTY.
Helix self-update downloads a SHA-256-pinned GitHub source archive, rebuilds the
dashboard and gateway, replaces helix-privd and helix-terminald, health-checks,
and restores those Helix bits on failure. git pull is not an updater. This is
not a signed-key channel, and it does not replace game containers or reboot the
host.
Native server/runtime and Modrinth requests use HTTPS with constrained expected hosts and bounded responses. Marketplace profiles restrict software kind, loader, and game version before installation. Modrinth's server-side metadata is advisory because projects do not always declare it accurately; missing or negative metadata produces a visible warning instead of a hard block. Paper/Purpur/Leaves receive matching plugin JARs, Folia requires a Folia loader match, and Fabric receives matching mod JARs. Vanilla, Forge, and NeoForge do not receive a fake marketplace path. Custom JARs also have no automatic marketplace because their loader and publisher compatibility is unknown.
Custom server creation accepts a dropped .jar or a local path inside a
configured Storage root. Browser uploads use sequential JSON chunks, ZIP magic
on the first chunk, and Helix's private import directory. Storage-browser paths
are still canonicalized, reject links/non-files/out-of-root paths, and copy
through a private create-new staging file. The source is untouched. Helix
records local-import rather than exposing the original path. This protects
the import boundary; it does not establish publisher trust or make arbitrary
JAR code safe.
Read and execute boundaries stay separate. A managed root of / can support
broad inventory browsing, but it is never inherited as a custom artifact root.
Dropped JARs land in Helix's private {state_root}/imports directory. When
native.custom_artifact_roots is empty, safe managed Storage folders are used as
import roots; set it to narrow that list.
The separate “Start with a modpack” flow accepts only opaque Modrinth IDs and
ordinary server settings from the browser. The broker re-resolves metadata,
permits only listed stable server-capable Fabric releases with one unambiguous
.mrpack, restricts downloads to exact Modrinth API/CDN hosts without
redirects, verifies the Modrinth-declared archive SHA-512 and index-declared
SHA-1/SHA-512, and enforces archive/file/count/byte/expansion/path/depth/time/disk
bounds. Traversal, links, devices, case collisions, and Helix-owned-path writes
are rejected. Fresh same-filesystem staging is activated only after validation;
failure removes the exact incomplete container, manifest, and instance.
Server-optional and client-only files are excluded and counted. The result is a
server-safe subset, not byte-for-byte full-pack parity. Unknown loaders and
every upstream pack matrix remain unclaimed. CurseForge marketplace and modpack
downloads use the official api.curseforge.com catalog with an owner-supplied
API key stored only in helix-privd ({state_root}/curseforge-api-key, mode
0600). The key is never returned to the browser or stored in helixd SQLite.
CurseForge pack archives and every required file are re-resolved by numeric
project/file ID, restricted to approved forgecdn hosts, size-checked, and
verified against the catalog-declared SHA-1 before staged files can be activated.
The known edge.forgecdn.net redirector is converted to its direct trusted
mediafilez.forgecdn.net equivalent; arbitrary redirects remain disabled.
If a release declares serverPackFileId, Helix prefers that publisher server
archive. It enforces a 2 GiB compressed archive limit, 8,192-entry limit, 10 GiB
unpacked limit, per-file and compression-ratio limits, UTF-8 relative paths,
regular-file-only extraction, case-insensitive collision checks, and a 45-minute
operation deadline. Helix-owned launch files are not extracted. If no server pack
exists, required metadata is fetched through CurseForge's bounded bulk-file API
and each selected JAR is still verified individually.
Required CurseForge entries with safe non-JAR names (for example shader-pack
ZIPs) are counted and excluded instead of being written into the server's
mods/ directory. Path separators, drive prefixes, and traversal-like names
still fail the entire staged install.
Helix does not ship a CurseForge secret. If you use CurseForge, this host must
reach api.curseforge.com on a normal ISP address; VPS and VPN exits are often
blocked even with a valid key.
Pack-created servers persist provider/version provenance plus a SHA-256 inventory of pack-managed files. Manual update checks consider only stable, installable releases on the same Minecraft version and loader. Candidates are fully downloaded, publisher-hash checked, and prepared before downtime. After a clean stop, Helix verifies the current inventory, creates a complete backup, and moves old managed files into a same-filesystem rollback directory before activating replacements. Worlds, player/account lists, server settings, icons, logs, crash reports, and backups are outside the inventory. Locally edited configuration is preserved; changed executable JARs and path/link collisions fail closed. The new container must answer a Minecraft health check before the old rollback files are removed. Failure restores the old files, manifest, container, and prior running/stopped state while retaining the safety backup. Before activation, Helix also verifies the completed gzip archive. If the new server fails validation, the complete archive is restored so startup-time world or player-data migrations are rolled back too; failed update files remain in protected recovery storage.
TLS and declared hashes protect specific transport/integrity properties; they do not prove an artifact is safe. Download size, provenance evidence, destination, replacement policy, restart behavior, and rollback still require review per integration. Broad dependency behavior and every upstream failure mode are not supported claims.
Helix releases additionally require signed artifacts, checksums, SBOM/provenance where practical, trust-root rotation, key-compromise recovery, and rollback protection before automatic update.
helix-secrets implements XChaCha20-Poly1305 record envelopes with a fresh DEK
and nonce, master-key wrapping, versioned associated data, bounded plaintext,
and zeroizing/redacted access. The database stores wrapped keys and ciphertext,
not the installation master-key bytes.
Production systemd credential delivery, effective in-memory lifetime, key rotation/rewrapping, fallback-key policy, TPM use, and an independent recovery envelope are not complete. A database backup without its separately protected master credential may be unrecoverable. Reserved schema fields are not proof a workflow exists.
Passwords and verification-only tokens are hashed, not encrypted for recovery.
Server API tokens are the one exception, by owner request: each token is still
authenticated by a domain-separated verifier, and an encrypted copy is stored in
the secret store so the owner can view it again after re-entering the current
password (audited as api_token.revealed). The store's master credential is
generated on first use and kept in secrets/token-vault.key (mode 0600, in a
0700 folder) under the data directory, outside state/, so a state database copy
or helixctl backup-state snapshot alone does not reveal tokens; the whole data
volume does. Revocation and rotation delete the stored copy.
Secret types avoid revealing debug/display output, while documentation remains
honest that copies can still exist in allocators, libraries, kernels, or a
compromised process.
Current Chronicle records bounded authentication/session events without raw passwords, bearer tokens, keys, or full secret-bearing requests. State retention keeps a newest-event floor and prunes older data in bounded transactions.
This is local append-only application behavior, not tamper evidence against root. Broader operator actions, export, holds, hash chaining, off-host forwarding, and a reviewed support-bundle generator remain future work.
Helix has no required cloud account and no advertised telemetry pipeline. Local weather, Modrinth, official CurseForge (when the owner saved an API key), Minecraft/runtime, and optional AMP requests still disclose the information necessary to those configured services.
Development defaults to loopback. The container gateway can be bound to an exact private address/port with explicit host, origin, and client-CIDR policy. An optional second private entry point can be used with an already configured Tailscale route.
On eligible Debian/Ubuntu hosts, the built-in Hook can add Tailscale's exact
official signed APT repository, install the exact tailscale package, and
enable and verify tailscaled.service. It writes only the publisher keyring and
sources.list paths shown in the preflight, into real root-owned directories
without group or world write. It does not run the publisher's remote
root script, authenticate an account, execute tailscale up, approve a node,
choose a tailnet, or reconfigure gateway trust. Do not trust a wildcard
hostname/origin/CIDR or the entire Tailscale carrier-grade NAT range just
because one expected node uses Tailscale. Public exposure of the dashboard is
not supported by this private-LAN release; the narrow native-game UPnP operation does
not change gateway trust or dashboard bindings.
Unrestricted root on the running host can read process memory, alter binaries, keys, files, firewall state, timers, containers, and local audit data. A compromised kernel/firmware or storage device can also lie about isolation and durability. Helix cannot make those conditions safe.
Least privilege, typed operations, explicit writable roots, bounded work, recoverable deletion, independent backups, signed releases, and off-host evidence reduce consequence; they do not make Helix “unhackable.”
Before public recommendation, automated and manual testing must cover:
- first-owner races, password limits, session rotation/revocation, CSRF, Origin/Host, remote onboarding, brute force, and MFA decisions;
- positive/negative capability tests and IDOR across every protected object;
- broker peer identity, malformed frames, concurrency, cancellation, and proof that no request can express an arbitrary command/path/unit;
- traversal, symlink/mount replacement, malicious filenames, permissions, low-disk, archive bombs, and interrupted storage/backup/restore operations;
- disposable live UFW, reboot, start-on-boot, Docker, and AMP matrices;
- SSRF/redirect/DNS/proxy behavior for every outbound integration;
- malicious marketplace/runtime metadata and artifact provenance;
- secret leakage through logs, errors, metrics, audit, API/UI, process arguments/environment, backups, and support data;
- oversized responses/queues, CPU/memory/disk pressure, long-running console retention, and recovery after process loss;
- clean install, upgrade, rollback, uninstall, modes, systemd hardening, and state/key recovery on supported Ubuntu versions; and
- digest-pinned Helix update apply/rollback drills and independently signed keys.
The release gate stays closed while a required control is merely documented, mocked, covered only by one host, or waived without an explicit narrow reason.
- systemd execution: https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html
- systemd transient units: https://www.freedesktop.org/software/systemd/man/latest/systemd-run.html
- Linux constrained path resolution: https://man7.org/linux/man-pages/man2/openat2.2.html
- UFW behavior: https://manpages.ubuntu.com/manpages/jammy/man8/ufw.8.html
- APT preview (
apt-get --simulate): https://manpages.ubuntu.com/manpages/jammy/man8/apt-get.8.html - SQLite durability/WAL: https://www.sqlite.org/pragma.html and https://www.sqlite.org/wal.html
Minimum supported versions and effective target-host behavior must be tested; documentation defaults are not runtime proof.