Skip to content

Security: northfoggy/nanoleaf

Security

docs/SECURITY.md

Security

Security model

This is a personal, LAN-only home-automation application. It is not designed as a multi-user or internet-facing service.

Protected assets include:

  • the Nanoleaf authentication token;
  • the ability to control the lights;
  • device metadata such as serial and firmware information;
  • the Raspberry Pi account and SSH key;
  • location and orientation displayed by the dashboard.

Device safety and third-party status

This independent project is not affiliated with, sponsored, endorsed, authorized, or supported by Nanoleaf Canada Ltd. Follow the official Nanoleaf support instructions for the exact model before pairing, resetting, updating, installing, or changing its electrical configuration. Review SAFETY.md and Nanoleaf's hardware warranty terms.

Third-party control changes the physical device's state. Incorrect, unsupported, interrupted, or excessive operations can cause unexpected behavior, loss of configuration, disconnection, unresponsiveness, reduced service life, or hardware damage, and may affect warranty coverage. Do not interrupt firmware updates or perform a reset unless the manufacturer directs it for the exact model.

Network exposure

The web server binds to 0.0.0.0 and the API has no authentication. Anyone who can reach port 5000 can issue device-control commands.

Required controls:

  • keep the Pi and dashboard on a trusted home LAN;
  • do not forward port 5000 from the router;
  • do not expose it through a public tunnel without adding authentication and TLS in a separate reverse proxy;
  • use Wi-Fi segmentation carefully, because an untrusted IoT VLAN may not be an appropriate place for the unauthenticated dashboard;
  • treat browser access as administrative access to the lights.

Nanoleaf token

The token is stored in ~/.config/nanoleaf-ctl/config.json because the local Nanoleaf API requires it. The application uses atomic replacement and mode 600 for writes.

Safe checks:

stat -c '%a %U %G %n' "$HOME/.config/nanoleaf-ctl/config.json"

Unsafe actions:

  • printing the config into shared terminal output;
  • committing it to Git;
  • placing the token in documentation, screenshots, issues, or chat;
  • sharing device URLs containing /api/v1/<token>/;
  • copying a token into a long-lived shell history via setup when pairing is available.

Application errors redact complete and relative credential-bearing API URLs and token assignments before writing simulator logs or returning API errors. File logging also scrubs the active simulator log and its three bounded rotations at startup.

Filesystem permissions

Expected private paths:

Path Mode
~/.config/nanoleaf-ctl/config.json 600
~/.config/nanoleaf-ctl/ owner-controlled
~/.nanoleaf-ctl/ 700
~/.nanoleaf-ctl/sunlight.log 600
Dedicated SSH private key readable only by its Windows account
~/.ssh/authorized_keys on the Pi 600

The systemd service sets UMask=0077, so new service-created files are private by default. In the reference service deployment, HOME=/var/lib/nanoleaf, so the application paths in this table live beneath that private state directory.

Systemd hardening

The included unit applies:

  • User=nanoleaf: a dedicated, non-root application process;
  • NoNewPrivileges=true: the process cannot gain privileges;
  • PrivateTmp=true: a private temporary directory;
  • ProtectSystem=strict: system paths are read-only;
  • ProtectHome=true: home directories are inaccessible to the service;
  • StateDirectory=nanoleaf: systemd provisions /var/lib/nanoleaf as the service's writable state and sets it as HOME through the unit;
  • StateDirectoryMode=0700: only the service account can access that state;
  • an owner-only umask;
  • an independent process watchdog.

These settings limit host impact but do not authenticate dashboard users.

The optional network-recovery service runs as root because it must ask NetworkManager to reconnect Wi-Fi and may request a guarded reboot. Its script is root-owned, checks the default route, NetworkManager link, and local gateway, writes only to systemd-managed state/runtime directories, uses a six-hour reboot cooldown, and is sandboxed by its service unit. A failed ICMP probe alone cannot trigger recovery. Do not make the script writable by the application account.

SSH administration

Use a dedicated key rather than sharing a password. Keep its private half outside the repository, and give its public-key entry a distinctive label such as nanoserver-admin in authorized_keys.

To find the public-key entry without displaying unrelated keys:

grep 'nanoserver-admin' "$HOME/.ssh/authorized_keys"

To revoke it, edit authorized_keys and remove only that labeled line. Confirm another administrative login works before ending the existing session.

Do not grant the application service account broad, non-interactive sudo. If automation requires privileged service management or journal reads, use a small command allowlist and keep general host administration under a separate account.

Logs and diagnostics

Logs can reveal location, device state, network addresses, and—on historical versions—credential-bearing URLs. Current logging redacts known token formats, uses owner-only permissions, rotates at bounded size, and scrubs bounded rotations during startup.

Before sharing logs, pass them through the redaction filter documented in Troubleshooting and inspect the result manually.

Quarantined historical logs should remain mode 600. Delete them when their diagnostic value is exhausted. Deleting a file does not guarantee forensic erasure from flash media.

Dependency and update practices

  • Install from the repository virtual environment, not system Python.
  • Run tests before restarting production.
  • Review dependency changes in pyproject.toml.
  • Keep the Pi OS and OpenSSH security updates current.
  • Use fast-forward-only Git pulls on the Pi.
  • Do not run arbitrary installation commands copied from dashboard content or logs.

Repository safeguards

The public GitHub repository uses:

  • GitHub secret scanning;
  • push protection for detected credentials;
  • pull-request CI on Python 3.10 and 3.13;
  • ignore rules for common credential, private-key, environment, runtime-log, and local configuration files;
  • a noreply Git commit address for maintainer privacy.

These safeguards reduce future risk but do not replace review. Run a full history scanner before publishing any branch that previously held local data.

Reporting a security issue

Do not include live tokens, private keys, precise private-network details, or unredacted logs in a public issue. Describe the affected code path and provide a minimal redacted reproduction.

There aren't any published security advisories