Skip to content

Fix v1.5.0 startup, safe create_ap lifecycle, passphrase off the command line (phase 1 of #1) - #2

Merged
Fiero1186 merged 9 commits into
mainfrom
fix/phase-1-critical
Sep 27, 2026
Merged

Fiero1186 merged 9 commits into
mainfrom
fix/phase-1-critical

Conversation

@CODER7657

@CODER7657 CODER7657 commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

Part of #1 (phase 1: fix what's broken).

This PR fixes the critical and high findings from the audit in #1. Each commit covers one topic, so it can be reviewed commit by commit.

⚠️ Needs one test on real hardware before merging. All of this was verified in a Linux container against test doubles of create_ap/iw (see #3 for the automated suite), but not yet on a laptop with a Wi-Fi card. The checklist is at the bottom.

What changes and why

Commit Fixes In simple words
chore: enforce LF line endings – .gitattributes + .editorconfig. A Windows clone no longer turns every script into CRLF (which breaks them on Linux).
fix(service) C1, H2, H3 The hotspot can start again. v1.5.0's PrivateTmp=false + ProtectSystem=strict made /tmp read-only, so create_ap could not create its working folder. Brings back PrivateTmp=true and the CAP_CHOWN/CAP_SYS_RESOURCE capabilities, drops RestrictRealtime, adds ReadWritePaths=-/etc/NetworkManager and RuntimeDirectory=fiero-hotspot. Exposure score: 4.2 OK (v1.4.1: 5.7).
fix(daemon) H1, H5, H6, M6, L1 Fiero now tracks only its own create_ap (state in /run/fiero-hotspot). Stop sends USR1 and waits for create_ap's own cleanup (which restores ip_forward), instead of deleting its files immediately. Other tools' hotspots are never touched. The --no-virt fallback is detected correctly. A failing iw counts as "disconnected". help/version work without root or config. Also fixes clients, which never listed any device (real iw prints signal: <tab>-45 [...] dBm).
fix(security) H4 The WPA passphrase goes to create_ap --config via a 0600 file that is deleted once the AP is up, so it is no longer visible in ps.
fix(prompt) M1, M2 Unplugging while the "Start?" prompt is open now cancels it. If nobody answers "Start?", nothing starts unless AUTO_START_ON_TIMEOUT='true'.
fix(installer) H7, L4, M9 The sudoers rule is validated before it is put in place, so a bad rule can't break sudo. The installer refuses to run from a root shell, offers to keep an existing config, asks which Wi-Fi card to use when there are several, and drops grep -P.
fix(uninstall) M9 Adds --keep-config, stops a manually started hotspot cleanly, and no longer deletes other tools' /tmp/create_ap*.
test: C2, M8 The on-hardware harness no longer says PASS when start/stop was skipped (it now exits 2, "incomplete"), and warns if the installed copy is older than the repo. audit.sh no longer says "Zero defects" when tools are missing.
docs: docs table in #1 README and SECURITY.md match the code; new CHANGELOG.md.

Behaviour changes users will notice

  • Nobody answers the "Start?" prompt → no hotspot. Set AUTO_START_ON_TIMEOUT='true' in /etc/fiero-hotspot.conf for the old behaviour. (The "Stop?" prompt still stops on timeout.)
  • sudo fiero-hotspot start exits with a message if another create_ap is already running on the interface, instead of killing it.
  • The installer rejects SSIDs/passwords containing a backslash or starting/ending with a space. create_ap's config reader would silently change them. An existing config like that still works, but falls back to the command line with a warning.
  • sudo ./install.sh must be run with sudo from the desktop user, not from a root shell.

How it was verified

🧪 Hardware test checklist (please run on your laptop before merging)

git fetch origin && git checkout fix/phase-1-critical
sudo ./install.sh          # answer "Y" to keep your config
sudo ./test_harness.sh     # exit 0 = passed, including real start/stop

Then by hand:

  • sudo systemctl start fiero-hotspot → phone connects and has internet
  • ps aux | grep create_ap does not show the passphrase
  • sudo fiero-hotspot status shows the AP interface and client count
  • sudo fiero-hotspot clients lists the phone (MAC, signal, IP)
  • A desktop notification appears on start/stop (checks the capabilities in the unit)
  • sudo systemctl stop fiero-hotspot → cat /proc/sys/net/ipv4/ip_forward is back to what it was before (usually 0)
  • iw dev shows no leftover ap0
  • Plug/unplug the charger: prompts appear; unplugging while "Start?" is open cancels it
  • journalctl -u fiero-hotspot -b has no errors

If anything fails, paste the journal here and I'll fix it on this branch.

🤖 Generated with Claude Code

CODER7657 and others added 9 commits September 26, 2026 17:52
A Windows checkout with core.autocrlf=true converts every script to CRLF,
which breaks them on Linux and makes ShellCheck report ~1,400 errors.
.gitattributes pins LF for every text file; .editorconfig keeps editors
consistent with the existing indentation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v1.5.0 set PrivateTmp=false while keeping ProtectSystem=strict. Per
systemd.exec(5), /tmp is only writable under ProtectSystem=strict when
PrivateTmp= is enabled, so create_ap's `mktemp -d /tmp/create_ap.*` fails
and the hotspot cannot start.

- PrivateTmp=true again (also wipes stale create_ap dirs on stop)
- RuntimeDirectory=fiero-hotspot (0700) for per-instance state
- ReadWritePaths=-/etc/NetworkManager so create_ap can mark the virtual
  AP interface unmanaged
- restore CAP_CHOWN and CAP_SYS_RESOURCE (dnsmasq; pam_limits in the
  sudo session used for notifications)
- drop RestrictRealtime=true, which the README documents as breaking PAM

Client lease lookup (the reason PrivateTmp was disabled) is reworked in
the following commit so it no longer needs a shared /tmp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cleanup used to send `create_ap --stop` (which only signals and returns)
and then immediately rm -rf every /tmp/create_ap* and delete every ap*
interface on the system. That raced create_ap's own cleanup - which
restores ip_forward from /tmp/create_ap.common.conf - and could leave
the laptop routing packets after the hotspot was "off". It also killed
hotspots started by other tools.

Now Fiero tracks its own instance in /run/fiero-hotspot (PID, create_ap
confdir, AP interface):
- stop sends USR1 (create_ap's clean-exit signal) and waits up to 15s;
  only if create_ap hangs is it killed, then its own hostapd/dnsmasq
  (matched by confdir) are stopped and ip_forward is restored
- only a leftover virtual ap* interface of ours is removed, never the
  physical card or another tool's interface
- a create_ap already running on the interface (e.g. the
  linux-wifi-hotspot GUI) is left alone and start exits with a message
- readiness uses the confdir's wifi_iface, so the --no-virt fallback
  (AP on the physical card, no ap* interface) is no longer torn down
- `stop` run by hand while the systemd service owns the hotspot now
  delegates to `systemctl stop`, keeping the unit state correct
- a failing `iw dev link` (driver reset, card removed) counts as
  "not connected" instead of looping forever
- status/clients read the AP interface from the state dir; clients
  reads create_ap's lease file via /proc/<pid>/root so it works with
  the service's PrivateTmp; status shows foreground runs
- clients: iw prints "signal:  \t-45 [-47, -46] dBm"; the old regex
  expected a single space, so no client rows were ever printed
- help/version no longer need root, the config or create_ap installed

Verified with a create_ap/iw test double in a Debian container: start,
status, clients, graceful stop, channel-drift restart, foreign instance,
--no-virt and forced stop (27/27).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… file

create_ap received the passphrase as a command-line argument, so any local
user could read it with `ps aux` or /proc/<pid>/cmdline while the hotspot
was up. create_ap supports `--config <file>`: with no positional
arguments it takes WIFI_IFACE, INTERNET_IFACE, SSID and PASSPHRASE from
the file.

Fiero now writes /run/fiero-hotspot/create_ap.conf (umask 077) and
deletes it as soon as the AP is live.

create_ap reads that file with `read` without -r, which drops
backslashes and trims surrounding whitespace. For an SSID/passphrase
affected by that, Fiero keeps the old command-line behaviour and logs a
warning instead of silently changing the password.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eout

Two behaviour bugs in fiero-prompt.sh, both reproduced with a fake
notify-send/charger in a container (old code fails them, new passes):

1. The prompt held its flock for the whole 10s notification, so a
   second udev event (charger unplugged while "Start?" is open) could
   not get the lock and exited before the code that kills the stale
   prompt. That code was unreachable and the user could still start
   the hotspot on battery. The lock now only guards the debounce/state
   update, and the "nothing to do" early exits run after it, so the
   stale prompt is always cancelled. A prompt that was superseded while
   open also re-checks before acting.

2. When nobody answered, the hotspot started on its own, so a Wi-Fi
   network could begin broadcasting without consent (and right after
   install, because install.sh triggers udev). Timeout/dismiss now does
   nothing unless AUTO_START_ON_TIMEOUT='true' is set in the config.
   The unplug prompt still stops on timeout, which is the safe choice.

Also drops an unused local (ShellCheck SC2034).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The sudoers rule was written straight into /etc/sudoers.d and only
  checked afterwards; if `visudo -c` failed, `set -e` stopped the script
  with the broken file in place and sudo stopped working system-wide. It
  is now written to /etc/sudoers.d/.fiero-hotspot.tmp (sudo ignores
  names containing a dot), validated, then renamed into place.
- Refuse to run from a root shell: TARGET_USER used to fall back to
  root, routing prompts and sudoers to root.
- Re-running the installer offers to keep /etc/fiero-hotspot.conf
  instead of asking for everything again and overwriting it.
- Several Wi-Fi interfaces: ask which one is the upstream instead of
  silently taking the first.
- Reject SSIDs/passwords with a backslash or surrounding spaces (create_ap
  cannot read them from the config file that keeps the password out of
  `ps`) and passwords outside printable ASCII (WPA2 requirement).
- Replace `grep -P` (not available everywhere) with `LC_ALL=C grep`.
- Works from any working directory (files are taken from the script's dir).
- Writes AUTO_START_ON_TIMEOUT='false' and explains the new prompt default.

Verified in a Debian container with fake iw/udevadm/systemctl and the
real visudo (19/19 scenarios).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e other tools' files alone

- stop a hotspot started by hand (not a systemd unit) through
  `fiero-hotspot stop`, so create_ap can restore ip_forward/NetworkManager
- --keep-config keeps /etc/fiero-hotspot.conf for a later reinstall
- remove /run/fiero-hotspot and the sudoers temp file
- no longer rm -rf every /tmp/create_ap*: create_ap cleans its own dirs,
  the service's live in its private /tmp, the rest may belong to the
  linux-wifi-hotspot GUI or another create_ap

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_harness.sh:
- when upstream is on an unsupported channel the start/stop lifecycle
  was skipped, teardown was set to PASS and the run exited 0 - a release
  could go out without the hotspot ever being started (this is how the
  v1.5.0 PrivateTmp regression slipped through). A skipped lifecycle is
  now reported as INCOMPLETE with exit code 2.
- phase 9 tests the installed /usr/local/bin/fiero-hotspot; it now fails
  loudly when that copy differs from the repository copy.

audit.sh:
- printed "Zero defects found" even when shellcheck/systemd-analyze/
  gitleaks were not installed. Skipped checks now make the result
  INCOMPLETE (exit 2); a missing or EXPOSED/UNSAFE systemd score fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- version badge, lifecycle diagram (systemd-run, create_ap --config),
  stop/cleanup behaviour, prompt lock and timeout default
- hardening: 4.2 OK exposure, why PrivateTmp=true and
  ReadWritePaths=-/etc/NetworkManager are needed; CAP_NET_ADMIN is not
  for namespaces (create_ap uses none)
- passphrase handling (config file, when it falls back to argv)
- AUTO_START_ON_TIMEOUT, uninstall --keep-config, harness exit code 2
- SECURITY.md: supported 1.5.x, fiero-prompt sources the config as the
  user (not root), sudoers validation
- CHANGELOG.md in Keep a Changelog format

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@Fiero1186 Fiero1186 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved Phase 1 critical fixes and lifecycle hardening.

@Fiero1186
Fiero1186 merged commit 59dd2a8 into main Sep 27, 2026
@Fiero1186
Fiero1186 deleted the fix/phase-1-critical branch September 27, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants