feat(nodes): opt-in encryption at rest for the outbound node API token - #6186
Open
n0ctal wants to merge 1 commit into
Open
feat(nodes): opt-in encryption at rest for the outbound node API token#6186n0ctal wants to merge 1 commit into
n0ctal wants to merge 1 commit into
Conversation
n0ctal
force-pushed
the
upstream-node-token-encryption
branch
from
August 8, 2026 02:15
d74b71b to
166c9d4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add opt-in encryption at rest for the outbound node API token, with an explicit three-state policy (
off/migration/required) and a rotatable keyring. Default isoff, which is today's behaviour byte for byte.Like the port-reservation PR, this is a proposal — it adds a package and a key file, so I would rather agree the shape than land it unannounced.
Why
nodes.api_tokenis the bearer token the panel presents to each worker. It is stored in plaintext, so anyone who reads the database reads a live credential for every node: a database backup, a copied SQLite file, a support dump, a restore onto a machine that should not have had it, an operator with read access to the panel database but not to the nodes.The token is not derivable, expiring, or scoped — possession is sufficient to drive the node's API. That makes the panel database a single artefact whose disclosure compromises every node behind it, and database copies move around far more casually than node credentials do.
Scope
internal/crypto/nodetoken: AEAD encrypt/decrypt bound to the node id, plus aKeyringwith key ids so a token can be re-encrypted under a new key without a flag day.KeySourcewith two implementations: a mode-0600 JSON key file (/etc/x-ui/node_token_key.json, override withXUI_NODE_TOKEN_KEY_FILE) and a single-key environment variable (XUI_NODE_TOKEN_KEY). The key is never accepted as a command-line argument, where it would be visible inps.NODE_TOKEN_ENCRYPTIONpolicy:off(default) — plaintext, exactly as today.migration— read plaintext and ciphertext, always write ciphertext. This is the path onto encryption without downtime.required— the key must load or startup fails.node.goencrypts on write and decrypts on read inside the existing transactions; a plaintext row passes through unchanged so a partially migrated table keeps working.Validation
go build ./internal/...clean.go test ./internal/crypto/nodetoken/ ./internal/web/service/green onmainatece16559.migrationhas to tolerate.Risk
None for existing installs: with
NODE_TOKEN_ENCRYPTIONunset the code path is the current one.The operational cost is real and worth stating — enabling this means a key file that has to survive alongside the database, and losing it while in
requiredmode means the panel will not start until it is restored or the tokens are reissued. That is a deliberate trade: I would rather a lost key stop the panel than quietly return it to plaintext. If you would prefer a different default or a simpler single-key form without the keyring, I am happy to cut it down.