projects/start-wrt/CHANGELOG.md
Rama master · réplica de lectura del repositorio original.
# Changelog
All notable changes to StartWRT are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.3.0]
### Added
- **DNS record publishing (RFC 2136).** A device with the new **Allow DNS record
publishing** permission (off by default, on its device page) can publish DNS
names into the router, pointing at any address, and every device on the
network resolves them. A StartOS server uses it for its private domains. An
inbound VPN client needs the same permission, granted from its VPN's client
list. An update from the LAN must arrive over TCP (`nsupdate -v`). Names under
`.lan` are refused, and a name is served only to networks whose Security
Profile can reach the publishing device. The router drops a record whose
device loses the address it published from, and revoking the permission
removes the device's names immediately. A read-only table on the device page
shows what a device has published. DNS record publishing coexists with DNS
servers published on port 53, including access from the WAN and LAN.
- **Inbound VPN clients can publish DNS records.** A client added on an earlier
release must be deleted and re-added before it can publish; its new
configuration carries the address the router expects.
- **Firefox's default DNS-over-HTTPS steps aside.** Wherever the router serves
published names or DNS overrides, its DNS answers Firefox's DNS-over-HTTPS
canary domain, so Firefox's default-enabled DoH steps aside there and those
names and overrides resolve. DoH a user turned on explicitly is untouched.
- K1 boards with four LAN ports bring up all of them, each assignable to its own Security Profile on the Ethernet page.
- **DNS servers for outbound VPNs.** Adding an outbound VPN or editing one now takes DNS servers, which replace the config file's `DNS =` line, and the page warns when a VPN has none.
- Wi-Fi settings have a channel width for each band: 20 or 40 MHz on 2.4 GHz, and up to 160 MHz on 5 GHz where the radio supports it. Each channel offers only the widths it can use in the selected country.
### Changed
- **Sign-in uses a key your browser holds.** Logging in creates a key the browser keeps but cannot export, and every request is signed with it, so no reusable credential crosses the network. This update signs everyone out once.
- **A sign-in lasts 24 hours from when you log in**, even while you keep using it, so expect to log in again each day. It previously lasted until it went a day unused. Logging out or changing the admin password from another device ends it sooner; updates within those 24 hours do not.
- **Changing the admin password signs out every other device.** The browser or CLI that made the change stays signed in. Before first-time setup sets a password, logging in is refused.
- **`startwrt-cli auth login` enrolls a key** at `~/.startwrt/id.key.pem` instead of saving a cookie, and `startwrt-cli auth session list` and `auth session kill` show and revoke sign-ins. On the router itself the CLI needs no login, and `auth login` and `auth logout` say so.
- **Support diagnostics include history from before the last reboot.** The
download now carries the router's saved system log, which spans reboots and
each boot from its first line and holds 32–64 MiB of history, along with
the activity log and a short system summary. It downloads as a `.log.gz`
file.
- **System logs are kept across later updates and included in backups.** Restoring a
backup keeps the router's own log, preceded by any older history the
backup carried.
- **`/var` persists across reboots.** The system log lives at
`/var/log/messages` and the activity log under `/var/lib/startwrt/`, where
a standard Linux system keeps them.
- **Only the router hands out addresses across its ports and Wi-Fi.** DHCP, DHCPv6, and IPv6 router advertisements from another device are no longer forwarded from one of the router's ports or Wi-Fi networks to another. A modem or router plugged into a LAN port instead of the WAN port could hand its own addresses to devices on the router's Wi-Fi, which then bypassed the router entirely. A device that needs to send router advertisements, such as a Thread border router, can be allowed to with the new **Allow IPv6 router advertisements** permission on its device page.
- **Higher transmit power on 5 GHz channels 36–48 in the US.** With the country
set to United States, these channels now allow up to 30 dBm instead of 23 dBm.
### Removed
- Removed "Switch to all traffic" and "Switch to LAN only" from the inbound VPN client list. To change a client's routing, delete it and add it again with the routing you want.
### Fixed
- `apk upgrade` on the router no longer fails because of an unavailable package repository.
- **First-time setup opens at `router.lan`.** While the router waits for its
admin password, a website typed into the browser is sent to
`http://router.lan` instead of showing a certificate warning and then the
setup page under that website's name.
- Deleting a profile, or returning one to the ISP's DNS, no longer leaves the router's name resolving to that profile's old address until the next restart.
- After the router restarts or updates, devices get an address as soon as the router is back, instead of up to two minutes later on routers with several profiles.
- Moving an Ethernet port to another Security Profile gives a connected Linux client an address on the new profile's subnet without reconnecting it by hand.
- **Saving the Ethernet page keeps WAN IPv6 on the WAN connection.** It no
longer moves the IPv6 client from the WAN connection onto the bare WAN port,
which on a PPPoE WAN would have run it outside the PPP link.
- Renaming an inbound VPN client now works. Previously every rename failed with an error and the name never changed.
- The inbound VPN client config dialog's visibility toggle and File/QR selector no longer sit under its close button with a long client name or in a narrow window.
- Editing an inbound VPN server no longer lets you change its Security Profile, which created a second, empty server and left the original running. To move a VPN to another profile, delete it and add a new one.
- Inbound VPN clients added with their own WireGuard public key can now connect. Adding one now shows its configuration, including the preshared key, with a placeholder for the device's private key. Clients added this way before this release never connected and must be deleted and added again.
- Keeping settings while flashing from a microSD card now also preserves the configuration files of installed packages outside `/etc/config` that you have edited, such as SmartDNS and dnsmasq configuration. Files still at their package defaults take the new firmware's defaults.
- A profile's LAN Access setting now reads back exactly as it was saved. A profile set to All keeps access to profiles created afterwards, instead of silently becoming a Whitelist of the profiles that existed when it was saved, and a Whitelist that includes every existing profile reads back as a Whitelist instead of All.
- A profile that was set to All before this fix and has already lost access to newer profiles must be re-saved with LAN Access set to All.
- A profile with LAN Access set to Same profile stays isolated from profiles created afterwards, even if its Whitelist option to include new profiles had been ticked before it was switched to Same profile.
- A Whitelist with the option to include new profiles ticked can be saved with no profiles selected, and keeps that option when the last profile in it is deleted.
- Forwardings between profiles' firewall zones added outside StartWRT, such as through LuCI, no longer show up as LAN Access.
- The LAN IPv6 page shows the prefix size your ISP actually delegated, and checks the LAN prefix length against it rather than assuming a `/48`. A `/64` LAN prefix is now accepted on a `/64` delegation, and a LAN prefix too short for the delegation is shown at the length the router actually uses.
- LAN IPv6 prefix lengths outside `/48` to `/64`, or shorter than the delegated prefix, are now rejected from the CLI as well as the web interface.
- The port-mapping server now answers NAT-PMP requests with "unsupported version" instead of ignoring them, so NAT-PMP clients fall back right away rather than retrying for about two minutes.
- An outbound VPN's detail page shows its tunnel's real state — **Connected**, **Idle**, **Not responding**, or **Disabled** — with the time of its last handshake, instead of reading **Connected** whenever the VPN is enabled. **Not responding** explains that profiles using the VPN have no internet until its server answers, and how to get them back online.
- PPPoE WAN connections now connect.
- Saving a profile, the LAN address, or a VPN or DNS setting no longer takes DHCP and DNS offline on every network for up to a few minutes, and a network that comes up during the save no longer stays without DHCP until another setting is saved or the router restarts.
- Saving published ports while a device is offline no longer removes that device's IPv6 forwards; they keep the device's last IPv6 address. Adding IPv6 to a port for a device that has no global IPv6 address is now refused with an error instead of being silently left out.
- Changing Remote Access closes WAN-side SSH sessions the new setting disallows, including changes applied from the CLI or after a WAN address change, and preserves sessions it still allows.
- Error messages now name the specific error instead of "General error".
- The serial console offers a login again after one times out. An unused login prompt expires after 60 seconds, and the console then stayed silent until the next reboot.
- **Internet access stays blocked until setup is complete.** Before an admin
password was set, devices using their own DNS server, Private DNS, or
DNS-over-HTTPS could reach the Internet.
- The first-boot setup page no longer stays blank where the web interface cannot load, such as in a captive-portal window; it says to open http://router.lan in Safari, Chrome or Firefox to finish setup.
- Changing a LAN Network Block or a profile subnet offers to delete the inbound VPN server it would break, instead of showing an error.
- Disabling WAN IPv6 keeps its settings, which return when it is enabled again, and the `wan6` interface stays visible in LuCI and `ubus` as stopped rather than missing.
- The IPv6 prefix length set on the WAN page is now saved in SLAAC and DHCPv6 modes, not only in Static mode.
- The WAN IPv4 summary reads **Not connected** when nothing is plugged into the WAN port or the WAN has no IPv4 address, instead of leaving the row out.
- **Wi-Fi Blackout holds for the whole window.** Saving a Wi-Fi Blackout window
that covers the current time turns Wi-Fi off right away, after asking you to
confirm, and removing the active window turns Wi-Fi back on. Saving Wi-Fi, WAN,
LAN, Ethernet port, or Security Profile settings during a window no longer
turns Wi-Fi back on before the window ends.
- The Devices page shows which Wi-Fi band (2.4 GHz, 5 GHz, or 6 GHz) each wireless client is connected on, instead of just "Wi-Fi".
- Choosing a Wi-Fi channel that cannot carry the radio's channel width, such as 5 GHz channel 165 in the United States, no longer leaves that band's network down. A router already saved with such a pair narrows the width when it next starts.
### Security
- **Update the web interface and shared dependencies with security fixes**, including Angular, DOMPurify, TLS certificate verification and handshake handling, and HTTP/2 flow control, and replace the unmaintained YAML parser.
- An IPv6 published port now delivers traffic only to the device it was published for. Previously, another device on the same Security Profile could spoof IPv6 neighbor discovery to have the router send that port's Internet traffic to it.
- **Update the Linux kernel to 6.18.57** with upstream stable security and bug fixes.
- **Update the OpenWrt base to 25.12.6**, including the hostapd fix for security advisory 2026-1 (malformed Wi-Fi 7 multi-link frames could crash the access point) and updated OpenSSL, wolfSSL, mbed TLS, and CA certificates.
- Rebooting no longer brings back the sticker Wi-Fi password or turns disabled radios back on when no Wi-Fi password is assigned to the Admin profile. Previously, after any reboot, the sticker password gave Admin access to the LAN and reset the network name to `StartWRT`.
- A router that already picked up the sticker password shows it as a **Default** entry on the `Points of Entry > Wi-Fi > Passwords` page. Delete that entry to remove the access it granted.
- A profile routed through an outbound VPN now sends its DNS queries through the tunnel in every case where it has DNS servers to use there. Previously, a profile with custom DNS servers resolved them over the WAN, so the ISP saw every name it looked up, and a VPN whose config had no `DNS =` line left its profiles on the ISP's DNS servers.
- With custom system DNS, a profile routed through a VPN whose config has no DNS servers resolves again, through the tunnel; previously its DNS stopped working.
- A profile routed through a VPN with no DNS servers to use through the tunnel shows **System (outside VPN)** in the DNS column, because its DNS still goes to the ISP outside the tunnel.
## [1.2.1]
### Fixed
- **A chained VPN no longer falls back to a single hop.** If the VPN it
connects through went down, or had not come up yet after a reboot, a chained
VPN reconnected to its server directly over your WAN, showing that provider
your home IP while every screen still reported the chain. It now stops until
its target is back. Existing chains are protected on the first boot of this
version.
- **Outbound VPNs with an IPv6 server address connect.** A config whose
`Endpoint` was a bracketed IPv6 address, such as `[2001:db8::1]:51820`,
imported without error but its tunnel never came up. Existing VPNs are
repaired on the first boot of this version.
- **VPN chaining refuses setups it cannot route.** A VPN can connect through
another only when its config's `Endpoint` is an IP address, the target VPN
can carry that address (it has an address of the same family and its
`AllowedIPs` include it), and no other VPN uses the same server address.
Previously these were accepted and the VPN either skipped the chain or could
not connect. Renaming a VPN while pointing it at a VPN that connects through
it is also refused as a circular chain. A VPN with a hostname `Endpoint`
offers only Internet as its target.
- **VPN chains that could not route are switched to connect over the
Internet.** A VPN chained with a hostname `Endpoint` connected directly over
your WAN while every screen still reported the chain. On the first boot of
this version it is set to connect over the Internet, and the change appears
in the activity log. To chain it again, import a config whose `Endpoint` is
an IP address.
- **A VPN connecting through another gets a fitting MTU.** With no MTU in its
config, or with the field left blank, it uses its target's MTU less the
chained tunnel's headers instead of 1420, avoiding fragmented packets.
- **Custom DNS works after an update.** On a router using custom system DNS
or a profile DNS override, devices could not resolve names after an update
until a DNS setting was saved again. DNS lookups over TCP, used for answers
too large for UDP, also failed under custom DNS.
### Security
- Hardens authentication for local clients
- Removes a third-party root certificate (`dc.com-CA`), inherited from the
board vendor's base image, from the router's system certificate store.
Routers drop it on their next update.
## [1.2.0]
### Added
- **Wi-Fi regulatory country.** `Points of Entry > Wi-Fi > Settings` gains a
Country selector. The selected country sets the channels each band may use
and the maximum transmit power; the channel dropdowns list only what that
country permits, and automatic channel selection skips radar-detection
(DFS) channels, which take a minute or more to come up. With no country selected the router runs on a conservative
worldwide subset (2.4 GHz channels 1–11, 5 GHz channels 36–48, 20 dBm), so
select yours after setup.
- **Root CA profile for iPhone and iPad.** Downloading the Root CA in Safari
on iOS or iPadOS now fetches a configuration profile that installs through
Settings.
### Changed
- **Setup ends with a link to `router.lan`.** After you set the admin
password, the confirmation page links to `router.lan` to trust your Root
CA, instead of saying the window can be closed.
- **The Root CA walkthrough matches StartOS.** It links to the instructions
for each platform and no longer asks you to bookmark the page. The Root CA
downloads from `/static/local-root-ca.crt`, the path StartOS uses.
### Fixed
- **A published domain typed without `https://` now reaches the published
service, not the router.** While port 443 is published to a device — by a
Published Port or a device's hostname routes — plain HTTP at the router's
public address is answered with a redirect to `https://` instead of the
router's web interface, from inside the network and from the Internet under
every Remote Access setting. A Published Port or a hostname route on 80
takes precedence.
- **Freshly generated Root CAs carry an Authority Key Identifier conforming to RFC 5280 and the CA/Browser Forum Baseline Requirements.** Existing routers retain their trusted Root CA when updated with settings preserved.
- **Publishing a port no longer names the device after its generated label,
which could stop the router's DHCP server.** Publishing a port to a device
with no reserved address reserves one; for a device without a name of its
own, that reservation was saved under the label shown for it, such as
`Android device (4c8f63)`. The router's DHCP server refuses such a name and
failed to start on its next reload or reboot, leaving devices on the network
without addresses. Reserving an address now leaves the device's name alone,
the device page edits only the name you assigned rather than whatever was
shown, and the router rejects a reservation it could not serve — a name must
be a valid hostname (letters, digits, and hyphens; up to 63 characters).
A router that already holds such a name clears it on its first boot on this
version and serves DHCP again, recording the cleared name in Activity. Since
a router in that state is hard to reach, reflashing from a microSD card and
choosing **Keep settings** is the way to get there without losing anything —
that path preserves the settings the name is stored in.
- **A device can move a hostname route it holds to another of its own ports.**
Previously the request was refused as taken until the old lease expired.
## [1.1.1]
### Fixed
- **Flashing to internal storage no longer depends on the microSD card's
partition table being untouched since the image was written.** A card whose
table a partition tool had rewritten to span the whole card, or whose last
partition had been grown to fill it, made the flash fail with
`rootfs_data partition not found on eMMC` and left the router unable to boot
from internal storage until reflashed.
## [1.1.0]
### Added
- Automatic port forwarding (PCP + UPnP IGD). A LAN device can now open and
renew its own port forwards using the standard PCP and UPnP protocols —
StartOS servers use this to configure themselves automatically behind a
StartWRT router, and game consoles/torrent clients are covered too.
Authorization is per-device and **off by default**: enable it with the new
"Allow automatic port forwarding" toggle on the device's detail page. A
device can only ever forward ports to itself — and because PCP runs over UDP,
a request is honored only when it actually arrives from the network the
device lives on, so a device on one network can't open forwards on behalf of
a device on another. Requests that would take
over a manually published port are refused (conversely, publishing a port an
automatic forward holds removes the automatic forward — manual rules win).
Ports the router itself answers on from the internet are refused too, so an
automatic forward can never take over your remote access to the router, its
SSH, or a VPN server you've exposed.
Forwards are stored as tagged UCI firewall redirects (so they survive
reboots and never collide with manual published-port rules), renew on an
in-memory lease (no flash writes on renewal), and expire on the lifetime the
device requested when it stops renewing them — at most a week, even for a
device that asks to hold the port indefinitely. A forward is also removed
once the device no longer holds the address it points at — its DHCP lease
lapsed, or it returned on a different address — so a forward can never
quietly deliver Internet traffic to whichever device is given that address
next (devices with a reserved address are unaffected). Turning the toggle
back off — or forgetting the device — closes that device's forwards and
hostname routes immediately.
The Published Ports page gains a read-only "Automatic" section showing each
port use's device, kind (PCP, UPnP, or SNI), and expiry. UPnP clients see a complete gateway:
the router advertises the `WANCommonInterfaceConfig` service clients use to
recognize an Internet Gateway Device, answers the status actions they check
before mapping anything, and supports reading mappings back
(`GetSpecificPortMappingEntry`/`GetGenericPortMappingEntry`) — a device sees
only its own. The router identifies itself as "StartWRT" to those clients. The
UPnP endpoints refuse browser-shaped requests — DNS-rebinding requests and
blind cross-origin writes alike — so a malicious web page cannot use a LAN
device's browser to read the network's public IP, fingerprint the router, or
open that device's ports. Uses the shared `start-core` PCP/IGD server cores.
Devices can also register **SNI hostname routes** on a shared external port
(over PCP's HOSTNAME extension or the `X_START9_AddHostnameMapping` UPnP
vendor action): the router reads each TLS connection's requested hostname
and delivers it to whichever device owns it, so several devices — or several
services on one StartOS server with their own domains — share one port such
as 443. Hostname routes appear in the Automatic section with their hostname,
follow the same per-device permission and lease expiry as plain forwards,
claim their shared port whole (plain forwards on it are refused; ports the
router itself answers on — SSH, an inbound VPN — are refused to hostname
routes for the same reason), and are re-registered by the device within
minutes after a router restart rather than persisted. Remote access to the
router's own web interface is the exception, not a casualty: hostname
routes and remote access share port 443 — connections naming a routed
hostname reach its device, and everything else (such as browsing the
router by IP address) still reaches the router interface, accepted from
exactly the sources your Remote Access setting allows, so enabling one
feature never silently disables the other.
A routed hostname works from inside your own network too — a laptop on your
LAN can open the same public address and reach the device — with one
consequence worth knowing: because the device answers a local client
directly rather than through the router, the router puts its own address on
those connections, so the device cannot tell one local client from another
in its logs. Connections from another Security Profile, and from the
Internet, still carry the original address.
- The UI now detects when the running firmware ships a newer interface than
the page is displaying (every RPC response and `system.info` report the
firmware's build stamp and the UI compares it to its own). An update
installed from the current tab reloads the page automatically once the
router is back (the "Updated to vX" confirmation follows after login); an
update applied any other way — CLI deploy, another device — shows a
"Refresh Needed" dialog with a Reload button rather than reloading out from
under unsaved work. Detection rides an `x-startwrt-git-hash` header on every
RPC response, so an open tab notices within seconds of its next request even
when the update restarted the daemon too quickly to drop a connection; pages
that make no requests while idle re-check every 30 seconds.
- **Devices that never share a hostname are now identified by operating
system or hardware vendor instead of a meaningless placeholder.** Some
devices deliberately withhold their name from the router — Chromebooks
never send one, and many IoT gadgets can't — and previously showed up as an
opaque `device-3af2b1`. The device list now recognizes the operating system
from how the device requests a network address (its DHCP fingerprint), e.g.
`Windows device (3af2b1)`, or failing that the vendor behind its MAC
address, e.g. `Apple device (3af2b1)` — keeping the short suffix so
identical unnamed devices stay distinguishable. OS recognition works even
for devices using randomized Wi-Fi addresses, survives reboots, and a real
hostname, when one ever appears, still takes over automatically; names you
assign always win.
- **6in4 tunnels can now be configured on the router.** The `6in4` protocol
and the SIT kernel module it needs now ship in the image, so an IPv6 tunnel
from a broker such as Hurricane Electric can be set up over SSH — useful for
reaching IPv6 on an ISP that provides none. There is no UI for this yet.
Previously these packages had to be built and sideloaded by hand after every
update, since a sysupgrade does not preserve separately installed packages.
- **eMMC boot firmware provisioning.** The flash wizard, in-app updates, and a
check on every boot now converge the eMMC hardware boot partitions
(boot0/boot1) to the release's own `bootinfo` + FSBL (u-boot SPL, built from
the pinned `spacemit-com/uboot-2022.10` source). Previously only vendor
factory tooling ever wrote boot0, so a DIY BananaPi BPI-F3 whose factory left
it empty (or carrying an incompatible bootloader vintage) completed the setup
wizard but could not boot from eMMC once the microSD card was removed. Writes
are idempotent (byte-compared, skipped when already current), read-back
verified, and ordered for power-cut safety (boot1 mirror first, the
single-sector bootinfo header last); boards already carrying the current
firmware are not touched. The boot-time check makes provisioning effective
from the first boot after installing this release and self-heals damaged or
interrupted boot firmware thereafter. The `bootinfo_emmc.bin` blob now ships
in the image's bootfs partition to support this.
- **The firmware image now ships MediaTek MT7915 Wi-Fi firmware alongside the
MT7916 firmware.** MT7915-based mini PCIe modules (such as the AsiaRF
AW7915-NP1) previously failed to initialize on DIY builds: the driver was
present but the firmware files were not, so no Wi-Fi radio ever appeared.
Note that MT7915 band-selectable cards operate one band at a time — with the
stock configuration the 2.4 GHz network comes up — unlike the
dual-band-concurrent AW7916-NPD module shipped in Start9 routers.
- **Hardware documentation.** A new Hardware page in the user guide lists the
router's specifications and publishes the SpacemiT K1 reference schematic the
board descends from, noting where the shipped router differs from that
reference design and why.
- **The StartWRT user guide is now published** at
[docs.start9.com/start-wrt](https://docs.start9.com/start-wrt/).
### Changed
- **Publishing a port the router itself answers on now asks for confirmation.**
Ports the router serves from the internet — remote access to its web
interface and SSH (80/443/22, including "When behind NAT" mode while the
router sits behind another router) and an inbound VPN's listen port —
previously could be published to a device without warning, silently cutting
that router service off from outside your network. Saving such a rule now
surfaces the conflict in a confirmation dialog; you can still publish the
port deliberately, and you're asked once per rule — again only if you
change which port that rule publishes, or its protocol. Detection follows
the live configuration (nothing is asked for ports no router service uses) and
matches transports, so e.g. a UDP-only forward on 443 doesn't warn.
- The firmware build stamp is now identical everywhere it appears: the
`startwrt` binary (UI `ETag`, `system.info`, `startwrt verify`) now carries
the same full-hash `-modified`-suffixed stamp the Settings → General
**Build** field bakes in via `config.json`, instead of a separate
short-hash `-dirty` stamp.
### Removed
- **The IPv6 "Reserve" option has been removed — it never worked and never
could.** Devices choose their own IPv6 addresses via SLAAC, so a router
cannot reserve one; the toggle wrote a DHCPv6 hint that no mainstream
client ever requests. The device page now shows the IPv6 address read-only
with an explanation, publishing a port no longer claims the IPv6 address
"will be reserved" (the rule follows the device's current address
automatically), and the LAN IPv6
SLAAC toggle now locks while enabled published-port rules use IPv6 rather
than when "reserved" devices exist. The `devices.update` RPC no longer
accepts `ipv6_static`/`ipv6` fields. IPv4 reservations are unchanged.
- **The Start9 DDNS provider option has been removed.** The Start9 DDNS
service has not launched yet, so selecting it saved a configuration that
could never update a DNS record. Configurations previously saved with the
Start9 provider now read back as Dynamic DNS disabled; pick one of the
supported providers to re-enable. The option will return when the Start9
service goes live.
### Fixed
- **The WAN IPv6 "6RD" mode now works.** Selecting it wrote a valid
configuration, but the image shipped without the `6rd` protocol handler or
the SIT kernel module it depends on, so the WAN interface simply came up
without IPv6 — with nothing in the UI to indicate why. Both now ship.
- **Disabled outbound VPNs are no longer offered as a Security Profile's
outbound route.** The VPN picker on the profile's WAN / Internet tab listed
every VPN, including disabled ones — and picking one silently cut that
profile off the Internet, since a disabled VPN's tunnel never comes up. The
picker now lists only enabled VPNs, and the router refuses a profile pointed
at a disabled — or nonexistent — VPN, so the command line can't create the
same dead end.
The same applied to VPN chaining, where it was quieter and worse: a disabled
VPN could be picked as another VPN's **Connects to** target, and the chain
then silently collapsed to a single hop. The downstream VPN connected
normally — straight out your Internet connection instead of through the VPN
you chained it to — and every screen reported success. Disabled VPNs are no
longer offered as chain targets, and the router rejects one outright.
- **The bottom of the screen is no longer cut off on shorter displays.** On
screens shorter than a page's contextual help content, the sidebar's
Collapse button and the bottom of the page — including a form's Cancel/Save
buttons, even when scrolled all the way down — were clipped, whether or not
the help panel was open. The layout now always fits the screen exactly, and
long help content scrolls within its panel.
- **A narrow window no longer cuts off the right side of the page.** Below
the mobile-layout width, page content is laid out at the width the collapsed
sidebar leaves free; with the sidebar still expanded, that layout ran under
the window's right edge — hiding a form's Cancel/Save buttons and other
right-edge content — with no way to scroll to it. The sidebar now collapses
automatically when the window becomes that narrow (and re-collapses after
navigating on a phone), and expanding it by hand at that width now shows a
horizontal scrollbar, so everything stays reachable either way.
- **Changing a published port no longer briefly drops the firewall.** Applying
a port-forward change restarted the firewall, which tears the whole ruleset
down and rebuilds it as two separate steps — for a moment in between, the
router had no firewall and no NAT, and connections started in that window
were unfiltered. It now reloads instead, applying the new ruleset in a single
atomic step with no gap, and a ruleset that failed to build leaves the
running one untouched rather than leaving nothing in place.
- **Enabling LAN IPv6 no longer silently fails when the Admin profile routes
through an IPv4-only VPN.** Saving the LAN IPv6 settings reported success
but immediately reverted to disabled: the save re-derived the admin LAN's
router advertisements from whether its outbound VPN carries IPv6, undoing
the change in the same write. Editing the Admin profile onto an IPv4-only
VPN likewise switched LAN IPv6 off behind your back — and kept it off even
after switching the outbound back. The LAN IPv6 toggle is now the sole
owner of that setting; with an IPv4-only VPN outbound, LAN devices still
get local (ULA) IPv6 addresses while internet-bound IPv6 remains blocked
by the VPN kill switch, so nothing leaks around the tunnel. That same
fault could also leave a router where the LAN IPv6 page read "Disabled"
while individual Security Profiles carried on handing out IPv6 addresses —
the page and the network disagreeing, with no way to bring them back into
line. Routers left in that state are now repaired automatically on the
first start after updating, which turns IPv6 off for those profiles too;
turn it back on from the LAN IPv6 page if you want it, and this time it
applies everywhere at once.
- **Turning IPv6 off now tells your devices to drop their IPv6 addresses.**
Devices choose their own IPv6 addresses from a prefix the router advertises,
and the only way to take one back is to advertise it one last time as
expired. The router was restarting its advertisement service instead of
reloading it, which skips that goodbye entirely — so after disabling IPv6
(on the LAN, on a Security Profile, or on the WAN) devices carried on using
addresses that no longer worked, for up to 90 minutes, until the addresses
timed out on their own. The notice is now sent while the prefix is still
live. A device that is asleep or misses the notice still falls back to the
timeout.
- **The Devices list no longer shows IPv6 addresses a device has given up.**
The router remembers a neighbouring address long after the device stops
using it, so a device could keep displaying an IPv6 address for hours after
it dropped it — most visibly after turning IPv6 off, where the address on
screen suggested nothing had changed. The router now confirms the device
still answers on an address before showing it, and leaves the field empty
when it does not.
- **Published ports no longer reshuffle their order on every refresh.** The
list is auto-refreshed every few seconds, and each refresh returned the
rows in an arbitrary order, so the table visibly jumped around. Published
ports now appear in a stable order, sorted by label.
- **A device that missed its one chance to share its name over mDNS/Bonjour is
no longer stuck with a generic label until the router reboots.** The name
lookup was attempted exactly once per device, and it usually fired at the
worst moment — the instant the device first appeared (before its Bonjour
service finished starting), or during the reconnection rush right after a
router reboot — and sleeping phones and laptops don't answer at all. The
router now retries silent devices on a backoff schedule (about a minute
after the first miss, stretching to a day) before concluding the device has
no name to share; a device that answers is remembered permanently.
- **IPv6 published-port rules now follow the target device when it changes
its address.** Devices assign their own IPv6 addresses and change them
routinely — privacy addresses rotate daily, and most operating systems
derive new addresses whenever the ISP rotates the delegated prefix — but
rules were only re-resolved on a prefix change, so a device-side change
silently broke the forward. The router now watches the network for
neighbor changes and retargets affected rules within seconds. Rules also
now pin the device's long-lived (stable) address instead of whichever
address happened to be observed first, which could be a short-lived
privacy address that expired within days. The **Endpoints** column shows
that same stable address, so the endpoint you copy always matches the rule.
- Browsers could keep serving a stale, cached copy of the web UI after a
firmware update — the router previously sent no cache headers, leaving cache
behavior to per-browser heuristics (Firefox/Safari could silently run an old
UI against the new backend). The embedded UI is now served with explicit
headers: stable-named files (`index.html`, `assets/`) revalidate on every
load against a per-build `ETag` (a cheap `304 Not Modified` when unchanged),
while Angular's content-hashed bundles are cached as `immutable`. Requests
for assets from an older build now get a `404` instead of a mis-typed
`index.html` fallback.
- The "Updated to vX" confirmation shown after a firmware update is now
translated instead of always appearing in English.
- **Documentation corrected against the code in a full docs-vs-code audit.** The
user guide no longer misstates product behavior: backups _do_ preserve assigned
device names and data-usage history; a Fresh Start reflash sets a new admin
password rather than clearing it; published-port statuses do not detect CGNAT;
Ethernet profile changes apply immediately (there is no Save button); inbound
VPN client configs must be captured when the client is created (the private
key is never stored). Also refreshed drifted UI labels throughout the guide,
corrected `API_CONTRACT.md` wire types and documented the previously missing
endpoints, and fixed stale paths, commands, and structure descriptions across
the developer docs.
- **Cloudflare Dynamic DNS now saves a working configuration.** The saved
config was missing fields the update client requires (the Bearer-token
marker and the zone), so Cloudflare updates could never succeed. The form
now asks for the **Zone** — the domain registered with Cloudflare, e.g.
`example.com` — instead of a Zone ID. Previously saved Cloudflare
configurations must be re-saved with the zone filled in. Note that the
DNS record must already exist in Cloudflare (the client updates records,
it does not create them), and the API token needs Zone:Read and DNS:Edit
permissions. Proxied (orange-cloud) records are supported: the client
reads the registered IP through the Cloudflare API rather than DNS, which
would only ever see the proxy's address.
- **Dynamic DNS now actually updates your provider.** The image was missing
the `ddns-scripts` update client (and its Cloudflare and No-IP extensions),
so DDNS settings were saved but no DNS record was ever updated. The FreeDNS
provider also pointed at a service name (`freedns.afraid.org`) that modern
`ddns-scripts` no longer recognizes; it now uses the afraid.org update-key
service (`afraid.org-keyauth`), and configurations saved with the old name
are still read back correctly. DDNS configurations are also bound to the
WAN interface, so an update fires the moment the connection comes back up
(e.g. after a modem reboot or PPPoE reconnect) instead of waiting for the
next scheduled check.
- **Published Ports endpoints no longer disappear when a WAN setting can't be
read.** A network interface hand-configured with a protocol StartWRT doesn't
manage (e.g. a `6in4` tunnel on `wan6`) made the WAN/LAN IPv6 settings
endpoints error, and the Published Ports page treated that one failure as
fatal — every port's IPv4 endpoint showed `—` even though the forwards were
active. Unmanaged protocols now read back gracefully (reported as IPv6
disabled) and are preserved untouched on disk, and the page now loads each
WAN setting independently, so one failure can no longer blank out the
endpoint list.
- **A published port is now reachable from your other Security Profiles at the
router's public address.** Reaching a published port by the router's public
address — or a domain name pointing at it — rather than the device's LAN
address only worked from the target device's own profile. From any other
profile the router answered instead of the published service, so an app or
bookmark holding a public address worked on one network and not another. A
published port is a public resource, so those connections are now delivered
to the device from every profile that could reach it from the Internet and
from every profile with Access to the device's profile — and from those only.
A profile could reach it from the Internet when its WAN Access is All, a
Blacklist that does not block the router's public address, or a Whitelist
that includes it, outside any blackout window. The routes follow your
Security Profile settings and your public address as they change, apply to
the device's global IPv6 address as well (where a Whitelist or Blacklist
entry counts by the device's address), and cover ports opened through
automatic port forwarding (UPnP/PCP) the same way. Nothing else on the device
is opened: a profile without Access still cannot reach it at its LAN address
or on any other port.
### Security
- **A published port restricted by Source is no longer reachable from your own
network at the router's public address.** The restriction applied to
connections arriving from the Internet, but a device on your own network
could reach the port through the router's public address regardless of it.
Restricted rules are no longer served that way; reach them from inside your
network at the device's own LAN address.
## [1.0.1]
### Added
- **`startwrt-cli verify` now checks the EEPROM WiFi password.** The factory
QC utility reads EEPROM tag 0x2F, verifies it satisfies the password
constraints (valid TLV, 12 characters from the non-ambiguous charset), and
prints the password for visual comparison against the device sticker. On
failure it reports the specific violation (invalid TLV, missing tag, wrong
length and/or bytes outside the charset).
### Changed
- **The Inbound VPN dialog no longer steals focus when it opens**, which on
mobile raised the keyboard the instant the dialog appeared.
## [1.0.0]
First stable release of StartWRT — Start9's OpenWrt-based router OS for home
self-hosting, built around per-device Security Profiles (assigned by Ethernet
port, WiFi password, or inbound VPN), with inbound/outbound WireGuard VPNs and
VPN chaining, WiFi schedules, dynamic DNS, and published-port forwarding.
Ships as a flashable image for the SpaceMiT K1 (BananaPi-F3), with OTA updates
delivered through the Start9 registry.
## [0.1.0-beta.4]
### Added
- **`--version` flag.** `startwrt --version` now reports the StartWRT version (e.g.
`0.1.0-beta.4`); the CLI previously exposed no `--version`.
### Changed
- OpenWrt base upgraded **25.12.4 → 25.12.5** (`r33051-f5dae5ece4`), picking up
the upstream stable-branch fixes. All three Start9 build-infra patches apply
unchanged, and no upstream path collides with the Start9 overlay.
- The OpenWrt image now builds from **pristine upstream OpenWrt** (the release
tarball pinned by sha256 in `build/openwrt-version`) with the Start9 delta
applied at build time from in-repo `openwrt-patches/` (3 build-infra patches)
and `openwrt-overlay/` (the SpacemiT K1 target + boot packages). The
`Start9Labs/openwrt` fork and the monorepo's last git submodule are retired;
cloning no longer needs `--recursive`, and the `openwrt/` build workspace
contains no git repo at all. The prepared tree is byte-identical to the
former fork (verified by git tree hash), so image contents are unchanged.
- Relocated into the `start-technologies` monorepo as the `start-wrt` product. The
three backend crates (`startwrt-core`/`ctrl`, `uciedit`, `uciedit_macros`) are now
members of the root Cargo workspace and build against the **shared** `start-core`
crate (`shared-libs/crates/start-core`, pulled in aliased as `startos`), the
vendored `rpc-toolkit`, and the vendored `imbl-value` — replacing the previously
embedded `start-os` submodule and the git/crates.io copies of those deps.
- Build orchestration moved from the standalone product `Makefile` to
`projects/start-wrt/build.mk` (included by the root `Makefile`): `make start-wrt`,
`make start-wrt-image`, `make start-wrt-update`.
- The Angular web UI is now a project (`start-wrt`) in the **root Angular workspace**
instead of a standalone app. It shares the root `package.json`/`node_modules`/
`tsconfig.json` and builds via `npm run build:wrt` (serve `npm run start:wrt`,
type-check `npm run check:wrt`) — and so upgrades in lockstep with the other Start9
Angular apps. `RELATIVE_URL`, `pauseFor`, and the markdown pipe now come from
`@start9labs/shared`. The HTTP/RPC/connection stack (its aborting-timeout, code-0
reconnect flow is deliberately different from shared's), the bespoke error surfacing,
`WorkspaceConfig`, the WebSocket progress types, and the i18n-routed `validation-errors`
provider stay local where the shared code would regress behavior or the shapes don't fit.
- `@start9labs/shared` is now marked `sideEffects: false` so importing a few symbols from
its barrel tree-shakes cleanly (start-wrt's embedded UI bundle would otherwise pull in
~875 kB of unused shared code). This also shrinks the other apps' bundles.
- Restored the release CI that the monorepo migration had dropped, then folded StartWRT
into the monorepo-wide release tool: `start-wrt.yaml` again has a `deploy` job, but it
now _only_ uploads the built images to S3 (`s3://startwrt-images`) — the CDN the registry
serves from. To match `startos-iso.yaml`, it is `workflow_dispatch`-gated on a `deploy:
release` input (rather than the old standalone workflow's `v*`-tag push) and reads the
version from `backend/ctrl/Cargo.toml` (the standalone workflow read the now-removed
`web/package.json`). Tagging, cutting the GitHub release, and the registry publishing are
now driven by the top-level `scripts/manage-release.sh` (a new `wrt` project kind alongside
os/cli/deb/npm), which replaces the standalone
`projects/start-wrt/scripts/manage-release.sh`. Registry publishing mirrors the OS's staged
flow: `register start-wrt` indexes a CI build into a beta registry, where beta routers
(UCI `startwrt.system.registry` pointed at it) soak the version as a normal OTA update, and
`release start-wrt` then promotes it into the production registry — both deliberate local,
developer-key-gated steps. Releases are cut on `Start9Labs/start-technologies` with the
monorepo's `<project>/v<version>` tag convention (`start-wrt/v<version>`), since the
monorepo hosts every product's releases on independent cadences. Release assets follow the
startos naming convention —
`startwrt-<version>-<git hash>_spacemit-k1-{sdcard.img.gz,sysupgrade.img.gz}` — instead
of the raw OpenWrt output names, which carried no product, version, or hash; the sdcard
image is now gzipped (it was previously published raw), and balenaEtcher flashes the
`.img.gz` directly.
- Restored the OpenWrt download-cache keying the migration had narrowed: the `image` job's
cache key again includes `build/feeds.conf` (so changing the feed set busts the cache) and
carries a `restore-keys` fallback (so a partial older cache can seed a fresh run).
### Fixed
- Changing the admin password now enforces the 12-character minimum. The
Settings → Password form and its `auth.set-password` backend endpoint (also
reached via the `startwrt auth set-password` CLI) accepted passwords shorter
than 12 characters, even though first-time setup required it and the docs
documented the minimum. Both the frontend form and the backend now reject
passwords under 12 characters on change, matching initial setup.
- Changing the Router IP can no longer strand the network on a colliding subnet.
The LAN IPv4 page exposed a "Router IP" (3rd-octet) field that duplicated the
Admin Security Profile's subnet field but, unlike it, had no collision guard —
so setting the router onto a subnet already used by another profile put two
interfaces on the same /24, producing overlapping routes that silently broke
all access to the router (unrecoverable even by a keep-settings reflash). The
duplicate field is removed (the LAN page now only selects the /16 network
block; the Admin profile is the single source of truth for the 3rd octet), and
the backend now rejects `profiles.create`/`profiles.edit` requests whose /24
collides with an existing profile (including the admin LAN). The `lan.ipv4-set`
endpoint (reached via the CLI) is covered by the same guard, so a direct
RPC/CLI call can't strand the router either.
- The Settings → General **Build** field no longer goes stale after new commits.
`build.mk` had lost the wiring that refreshes `build/env/GIT_HASH.txt` on every
build and treats it as a prerequisite of `web/config.json`, so the UI's `gitHash`
froze at whatever it was when `config.json` was first generated. `build.mk` now
refreshes `GIT_HASH.txt` at parse time and re-stamps `config.json` whenever `HEAD`
moves.
- The **Build** field now shows the `-modified` marker on a dirty build. It shortened
the 40-char hash with `slice(0, 12)`, which dropped the trailing `-modified` suffix;
it now preserves any trailing marker (matching the `-dirty` indicator the
`startwrt verify` CLI already shows).
- Adding an Outbound VPN no longer silently does nothing. On submit the dialog
called `tuiMarkControlAsTouchedAndValidate`, which re-ran the WireGuard `.conf`
async validator and left the form stuck `PENDING` (the in-flight validation is
cancelled when the file input remounts during the pending phase), so the create
request was never sent. Submit now completes directly when the form is already
valid, and only marks fields touched — without re-validating — when it isn't.
- Web-only changes are now re-embedded into the `startwrt` binary on rebuild. The
UI is baked in at compile time via `include_dir!`, which does not register the
embedded files as cargo dependencies, so `ctrl`'s `build.rs` now emits a
`cargo:rerun-if-changed` for the web `dist` directory. Previously a changed web
bundle was silently ignored unless a `.rs` file also changed, shipping a stale
UI.
## [0.1.0-beta.3]
StartWRT is Start's fork of OpenWrt — a router OS for home self-hosting built around
per-device Security Profiles, with profiles assigned by Ethernet port, WiFi password,
or inbound VPN. Features inbound/outbound WireGuard VPNs with VPN chaining, WiFi
schedules, dynamic DNS, and published-port forwarding. A Rust backend (`startwrt`
binary: RPC daemon + CLI over JSON-RPC 2.0, UCI as source of truth) serves an embedded
Angular + Taiga UI frontend, shipped as a flashable OpenWrt image for the SpaceMiT K1
(BananaPi-F3).