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.
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.
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.
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
setupwhen 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.
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.
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/nanoleafas the service's writable state and sets it asHOMEthrough 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.
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 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.
- 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.
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.
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.