Skip to content

NovaKey is a lightweight, cross‑platform Go agent that turns your computer into a secure, quantum‑resistant password‑delivery endpoint.

License

Notifications You must be signed in to change notification settings

OsbornePro/NovaKey-Daemon

🔐 NovaKey-Daemon

NovaKey-Daemon is a cross-platform Go agent that receives authenticated secrets from a trusted device and injects them into the currently focused text field.

It’s built for cases where you don’t want to type high-value secrets (master passwords, recovery keys, etc.) on your desktop keyboard:

  • the secret lives on a trusted device (e.g. your phone)
  • delivery is encrypted and authenticated
  • the daemon injects into the focused control (with optional clipboard mode when injection is not possible)

Current Design

One port, routed by a preface line

NovaKey listens on one TCP address (listen_addr, default 0.0.0.0:60768) and routes each incoming connection by a one-line preface:

  • NOVAK/1 /pair\n — pairing (and pairing subroutes)
  • NOVAK/1 /msg\n — encrypted approve/arm/disarm/inject messages

Clients must send a route preface line (NOVAK/1 /msg\n or NOVAK/1 /pair\n). Connections without a valid preface are rejected.

Crypto (Protocol v3)

NovaKey uses:

  • ML-KEM-768 (Kyber) for per-message KEM shared secret establishment
  • HKDF-SHA-256 for key derivation
  • XChaCha20-Poly1305 for authenticated encryption
  • timestamp freshness checks, replay protection, and per-device rate limiting

Message Model (Required Inner Frame)

All /msg requests decrypt to the following plaintext structure:

  1. 8-byte timestamp (uint64, big-endian, unix seconds)
  2. Inner Message Frame v1 (required)

The inner frame is:

  • versioned (frame_version = 1)
  • includes device_id, msg_type, and payload
  • authenticated by the outer AEAD (and validated for device-id consistency)

Supported inner msg_type values:

  • Inject — payload is the secret string
  • Approve — payload optional/empty (two-man mode)
  • Arm — payload optional JSON: {"ms":15000}
  • Disarm — payload typically empty

Messages that do not contain a valid Inner Message Frame v1 are rejected.


Safety controls (optional)

  • arming (“push-to-type”)
  • two-man approval window (approve then inject)
  • injection safety rules (allow_newlines, max_inject_len)
  • target policy allow/deny lists (focused app/window)
  • local Arm API (loopback only, token protected)
  • clipboard policy controls:
    • allow_clipboard_when_disarmed (allows clipboard use when blocked by gates/policy)
    • allow_clipboard_on_inject_failure (allows clipboard use when injection fails after gates pass; default true on Linux)

Pairing (single-port)

When there are no paired devices (missing/empty device store), the daemon generates a QR code (novakey-pair.png) at startup.

Pairing uses the /pair route on the same TCP listener. Clients must send the route preface:

NOVAK/1 /pair\n

High-level flow:

  1. Client sends a hello JSON line containing a one-time token:

    {"op":"hello","v":1,"token":"<b64url>"}\n

  2. Server replies with the ML-KEM public key and a short fingerprint:

    {"op":"server_key","v":1,"kid":"1","kyber_pub_b64":"...","fp16_hex":"...","expires_unix":...}\n

  3. Client verifies fp16_hex matches the fingerprint embedded in the QR.

  4. Client sends an encrypted register request (Kyber + XChaCha20-Poly1305). The server saves the device PSK and reloads device keys.

Pairing output is sensitive (treat it like a password).

Pairing subroutes

NovaKey also supports /pair/* subroutes on the same listener (routed by the same preface line). These exist for alternative pairing workflows used by clients.


Device Store & Key Vault Behavior

NovaKey stores per-device static keys in the device store referenced by devices_file (default devices.json).

Windows

Device store is sealed using DPAPI.

macOS / Linux

Device store is stored in one of two forms:

  • Sealed wrapper (preferred): encrypted-at-rest using an OS keyring–stored sealing key.
  • Plaintext JSON (explicit opt-in): only used when the OS keyring is unavailable and plaintext storage is explicitly allowed.

Important: On some Linux systems (especially headless services or logins backed by hardware tokens), the daemon may not be able to access the user keyring from a system service context. In those environments, you may need to explicitly allow plaintext device storage.

Control this with:

  • require_sealed_device_store:

    • truefail closed if the store is not sealed / keyring unavailable (recommended default)
    • false → allows plaintext devices.json only when the keyring is unavailable, using strict 0600 perms (enable only if you must)

Configuration

NovaKey supports YAML (preferred) or JSON configuration.

Core Networking & Limits

Option Default Description
listen_addr "127.0.0.1:60768" TCP address NovaKey listens on for /pair* and /msg.
max_payload_len 4096 Maximum allowed decrypted payload size (bytes).
max_requests_per_min 60 Per-device rate limit for accepted /msg requests.
devices_file "devices.json" Path to the device store containing paired device keys.
server_keys_file "server_keys.json" Path to the server’s ML-KEM key material. Treated as sensitive.

Device store hardening

Option Default Description
require_sealed_device_store false If true, NovaKey refuses to run when the OS keyring is unavailable or when the devices store is not sealed.
  • If you want it fail-closed by default, set it to true in your shipped config.

Pairing & key management hardening

Option Default Description
rotate_kyber_keys false If true, rotates the server’s ML-KEM key pair on startup (requires re-pairing).
rotate_device_psk_on_repair false If true, re-pairing an existing device replaces its stored key.
pair_hello_max_per_min 30 Per-IP rate limit for /pair hello attempts (in-memory).

Logging

Option Default Description
log_dir "./logs" Directory for rotating log files. Ignored if log_file is set.
log_file (unset) Single log file path. Overrides log_dir when set.
log_rotate_mb 10 Maximum size (MB) of a log file before rotation.
log_keep 10 Number of rotated log files to retain.
log_stderr true If true, logs are also written to stderr.
log_redact true Best-effort redaction of tokens/secrets and long blobs.

Arming (“Push-to-Type”) gate

Option Default Description
arm_enabled true Blocks injection unless locally armed.
arm_duration_ms 20000 Duration (ms) the daemon remains armed after arming is triggered.
arm_consume_on_inject true If true, a successful injection consumes the armed state.

Clipboard policy

Option Default Description
allow_clipboard_when_disarmed false Allows clipboard use when blocked by gates/policy. Use with care.
allow_clipboard_on_inject_failure true on Linux, else false Allows clipboard use when injection fails after gates pass (Wayland, perms, etc.).

Local Arm API (Loopback Only)

Option Default Description
arm_api_enabled true Enables the local HTTP arm API.
arm_listen_addr "127.0.0.1:60769" Address the Arm API binds to. Must resolve to loopback or it will refuse to start.
arm_token_file "arm_token.txt" Path to the Arm API authentication token file.
arm_token_header "X-NovaKey-Token" Header name used to supply the Arm API token.

Injection safety

Option Default Description
allow_newlines false Reject newline characters in secrets when false.
max_inject_len 256 Maximum number of characters allowed in a single inject.

Two-man approval mode

Option Default Description
two_man_enabled true Requires an approve message before injection is allowed.
approve_window_ms 15000 Window (ms) after approval in which injection is allowed.
approve_consume_on_inject true If true, approval is consumed after a successful injection.

Target policy (Focused app / window restrictions)

Option Default Description
target_policy_enabled false Enables focused target enforcement before injection.
use_built_in_allowlist false Applies a built-in allowlist when enabled and no explicit rules are set.
allowed_process_names (empty) Allowed process names (normalized).
allowed_window_titles (empty) Case-insensitive substrings required in the focused window title.
denied_process_names (empty) Always-denied processes.
denied_window_titles (empty) Always-denied window title substrings.

Recommended defaults

  • Keep listen_addr on loopback unless you need LAN.
  • Prefer require_sealed_device_store: true (fail closed) unless your Linux service environment cannot access the OS keyring.
  • Keep arm_enabled: true and two_man_enabled: true for safest operation.
  • On Linux Wayland, injection may not be possible; rely on clipboard mode (allow_clipboard_on_inject_failure) as needed.

Docs

  • SECURITY.md — threat model + security properties
  • PROTOCOL.md — wire formats for /pair* and /msg

Contact

About

NovaKey is a lightweight, cross‑platform Go agent that turns your computer into a secure, quantum‑resistant password‑delivery endpoint.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •