Skip to content
Test Station Setup

Why & How to Run Test Stations Without Root

Running a test station as root is a costly shortcut. Learn least-privilege setup on Linux and Windows: low-privilege account, scoped hardware access, a service.

JJuliette Lansoy
intermediate11 min readJuly 8, 2026

Why and How to Run Test Stations Without Root Access

Running a production test station as root (Linux) or Administrator (Windows) is the fastest way to get hardware access working, and a shortcut you pay for later in security exposure and operational fragility. The highest-value habit is simple: don't log the station in as an admin. This guide covers that cheap win first, then how far you can take least privilege on Linux and Windows without breaking hardware access or boot-time startup.

The Shortcut and What It Costs

When the priority is to deploy fast, running the whole station as root is often the shortest path: a device throws a Permission denied, and elevating unblocks it right away. Over time it hardens into one of these:

Shortcut you see on the floorWhat it savesWhat it quietly costs
Auto-login as root, no passwordOperator opens the session instantlyAnyone with physical access owns the machine
Root password on a sticky noteNo one waits to unlockSame as no password, plus a paper trail to your secret
sudo calls inside the runtime codeScript "just works"Every run holds full-system power it never needs
NOPASSWD in sudoers for the runnerNo prompts during a shiftAny bug or misuse escalates silently

Sometimes it is a shortcut under deadline; sometimes a vendor tool genuinely demands root and there's no clean alternative that day. Either way, the cost is the same and it compounds across every station, every audit, and every machine that leaves the building for repair. The fix is not "never elevate." It is: elevate to install, never to run. Root is fine for setup (installing software, drivers, device rules), not for the identity that executes tests shift after shift.

The Cheap Win: Don't Log In as Admin

Before any of the advanced setup below, one change removes most of the risk for almost no effort: the operator logs in as a standard, non-admin user, and the station runs in kiosk mode. This alone is the 80/20.

RiskA standard account + kiosk removes it?
Operator "borrows" the station to browse, copy files, install a toolYes, no admin rights, and the kiosk hides the desktop
Operator changes a system setting they shouldn'tYes, they can't, by design
A bug in a parsed file corrupts the whole OSMostly, the process can't write outside the user's reach
Stolen/repaired machine leaks every stored secretReduced, the account's reach bounds what's exposed

A standard account protects the operator from the machine as much as the machine from the operator: they should physically not be able to do what they aren't meant to. Kiosk mode is covered separately; the point here is that the account it runs under should not be an admin. Everything below is how to keep it that way while still reaching hardware and starting on boot.

The Security Cost (Why This Matters)

Least privilege is error containment: the software equivalent of fusing a circuit instead of trusting nothing will ever short. A test runner parses files it did not write (firmware images, calibration data, serial responses, config from a share), so any bug runs with the identity of the process.

RiskAs a low-privilege accountAs root / admin
Blast radiusA bad parse corrupts one working directoryThe same bug can overwrite the OS or another line's data
Credential exposureProcess reads only its own API keyProcess can read every secret on the machine
Supply chain / privilege escalationA compromised dependency is containedA single bad package owns the box, no escalation needed
Lost or repaired machineData at rest is bounded by the account's reachFull disk and every stored credential are exposed
AuditLogs attribute actions to a station identityLogs say root did X for everything, unattributable

The escalation risk is concrete enough that good tooling refuses to enable it: the TofuPilot CLI will not install a root boot-service whose binary lives in a user-writable directory, because a non-root user who could overwrite that binary would gain root at the next boot. The audit line is now a business consequence too. Standing local admin on floor workstations is increasingly a CMMC / NIST 800-171 audit failure, a real barrier for anyone in aerospace, defense, or high-end EMS supply chains. And the blast-radius line is what security teams call lateral movement: an attacker who lands on one over-privileged station moves across the whole plant before anyone notices.

"But the Station Is Offline"

Root-on-benches is often defended with "the station has no internet, so who cares." That misreads what offline means on a factory floor. The station is air-gapped from the internet, but it usually sits on a secured local network, reachable from other machines and often able to reach the MES and the production server where centralized production data lives.

An IT team only accepts the painful drawback of an offline station when the machines and data on that local network are sensitive enough to justify it. In other words, "offline" is a signal that security matters more here, not less. A station running as root on that network is a soft entry point to everything else on it: exactly the lateral movement the isolation was meant to prevent. Removable media compounds it: an offline station still takes firmware images on USB sticks and still leaves the building for repair (the Stuxnet worm reached isolated industrial systems precisely by USB).

The Principle: Elevate to Install, Never to Run

TaskNeeds elevation?Who runs it
Install software, drivers, device rulesYes, onceAdmin, at setup
Read/write a serial or USB device at runtimeNoLow-privilege account via group / rule
Execute tests, upload resultsNoThe station's non-admin account
Flash firmware via raw I/OSometimesGrant the one capability, not full root

The rest of this guide is how to build that on Linux and Windows.

Linux: Least-Privilege Setup

Step 1: Run Under a Low-Privilege Account

The account the station runs under should be a normal user with no admin rights: not root, not a member of sudo/wheel. On most benches this is the operator account the kiosk already uses.

01-create-operator-user.sh
# A normal low-privilege account for the station. NOT in sudo/wheel/adm.
# The operator can log in and run the kiosk UI, but can't touch the system.
sudo useradd --create-home operator
id operator   # confirm: not a member of sudo, wheel, or adm

Step 2: Grant Hardware Access With Groups, Not Root

The reason people reach for root is device permissions. The correct fix is group membership: add the operator account to the group that already owns the device.

02-grant-hardware-access.sh
# Serial ports (/dev/ttyUSB*, /dev/ttyACM*) are group-owned by 'dialout'
sudo usermod -aG dialout operator
# Hot-plugged USB instruments are usually group-owned by 'plugdev'
sudo usermod -aG plugdev operator

# Confirm which group owns your device (4th column)
ls -l /dev/ttyUSB0
# crw-rw---- 1 root dialout 188, 0 ... /dev/ttyUSB0
#                   ^^^^^^^ group that grants access

For a device not owned by a standard group, write a udev rule (udev is the Linux subsystem that assigns ownership and permissions to devices as they appear):

/etc/udev/rules.d/99-station-dut.rules
# Match a specific instrument by USB vendor/product ID and hand it to
# the 'dialout' group (which the operator is in). No root at runtime.
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", GROUP="dialout", MODE="0660"
03-reload-udev.sh
sudo udevadm control --reload-rules && sudo udevadm trigger

For a binary that needs one specific kernel privilege (e.g. raw I/O to flash firmware), grant that one capability instead of root. Capabilities split root's powers into independent units:

04-grant-capability.sh
# Give only raw-I/O to this one binary, nothing else root can do
sudo setcap cap_sys_rawio+ep /usr/local/bin/flash-tool
getcap /usr/local/bin/flash-tool

Step 3: Launch on Boot Without Root

This is where root usually sneaks back in. When you enable Launch on boot, the TofuPilot CLI installs the right startup service automatically, so you don't write a unit file by hand:

CLI runs asService it installsKiosk
operator (non-admin)systemd user service (~/.config/systemd/user/)Yes, on the local display
rootsystemd system service (/etc/systemd/system/)No, headless, driven from the dashboard

The trap that pushes people to root: if you enable launch-on-boot over SSH as the operator (a shell with no login session), systemctl --user has no session bus and fails:

journalctl-error.txt
Failed to connect to bus: No medium found
systemctl --user: Failed to connect to user scope bus

The fix is not to switch to root. It is to let the operator's user services run without an interactive login. That capability is called lingering in systemd (like a machine that keeps its background routine running even when no one is at the HMI):

05-enable-linger.sh
# Operator's systemd services now start at boot with nobody logged in
sudo loginctl enable-linger operator
loginctl show-user operator --property=Linger   # -> Linger=yes

With lingering on, the non-root station starts at every boot, keeps its kiosk display, and never needs root for startup. Credentials follow the same account: run tofupilot login --token <setup-token> as the operator, not sudo.

Step 4: Verify Least Privilege

06-verify.sh
# The station process should show 'operator', not root
ps -o user,cmd -C tofupilot
# Run the check AS operator (sudo -u switches identity, it does not elevate)
sudo -u operator cat /dev/ttyUSB0   # opens via the dialout group, not "Permission denied"

Windows: Least-Privilege Setup

Same principle: the operator account is a standard user, not Administrator. Windows makes most of this easy because standard users already have the access a test runner needs.

NeedLeast-privilege grantAvoid
Read/write a COM portStandard users can open COM ports by default; no admin neededRunning as admin "to be safe"
USB instrument via vendor driverInstall the driver once as admin; runtime access is per-deviceAdmin at runtime
Write logs / resultsGrant the account write access to one folder (e.g. C:\tofupilot)Writing under C:\Program Files
Launch on bootTofuPilot registers a per-user Run entry, fires at the operator's logon, no adminA machine-wide service running as SYSTEM/admin
01-create-operator-user.ps1
# Local standard user (NOT added to Administrators)
$pw = Read-Host -AsSecureString "Password for the operator account"
New-LocalUser -Name "operator" -Password $pw -PasswordNeverExpires `
  -Description "Test station operator"
# Deliberately NOT: Add-LocalGroupMember -Group "Administrators"
02-grant-folder-access.ps1
# Give the operator account its own writable working directory
New-Item -ItemType Directory -Path "C:\tofupilot" -Force
icacls "C:\tofupilot" /grant "operator:(OI)(CI)M"

On Windows the station runs as a per-user logon process under the standard operator account, so it starts when the operator logs in, with no admin service and no SYSTEM privileges. Store the API key in the operator's user-scoped environment or a file under C:\tofupilot with permissions limited to that account, never machine-wide.

When Root Is the Pragmatic Choice

Best practice sometimes conflicts with productivity, and the person who knows the constraints, the developer at the bench during a production rush, is the one who should decide. At TofuPilot we don't block that decision: the CLI runs under root or admin at runtime if you choose to, because removing deployment friction during a rush matters more than enforcing rigidity from the outside. Running the CLI as root even installs the correct system service for you.

If you do run as root, do it deliberately and shrink the risk:

If you run as rootReduce the risk by
A vendor tool or raw I/O genuinely needs itScope it: setcap on that one binary instead of root everywhere
Hardware access is blocking a deadlineShip the root shortcut, then schedule the udev/group fix as follow-up
Headless bench, launch on bootKeep the binary in /usr/local/bin owned by root (the CLI refuses a user-writable one)
Physical machine can be stolen or sent for repairEncrypt the disk; keep the API key scoped and revocable
Operators share the stationPair root runtime with kiosk lockdown so they can't wander off the test UI

The point is not purity. It is knowing what the shortcut costs so the choice is informed, and moving to least privilege once the fire is out.

Troubleshooting

IssueCauseFix
systemctl --user: Failed to connect to user scope busEnabling launch-on-boot from a shell with no login session (SSH)sudo loginctl enable-linger operator, don't switch to root
Station doesn't start at boot, no errorUser service installed but lingering is offEnable lingering (above); confirm with loginctl show-user operator
Permission denied on /dev/ttyUSB0 after dropping rootOperator not in the device's groupusermod -aG dialout operator, then log out/in or reboot
Device works for root but not the operatorudev rule not applied, or wrong vendor/product IDRe-check IDs with lsusb, reload with udevadm control --reload-rules && udevadm trigger
CLI refuses to install a root system serviceBinary sits in a user-writable path (a root-at-boot escalation vector)Move it to /usr/local/bin owned by root, then re-run
Windows runner can't write results after dropping adminWriting to a protected pathPoint output at the operator's own folder (C:\tofupilot)

More Guides

Put this guide into practice