<#41 Warn when root runs osctrld with an unsafe co...
# osctrl
g
#41 Warn when root runs osctrld with an unsafe config file; root-owned macOS install Pull request opened by javuto Warn when root runs osctrld with an unsafe config file; root-owned macOS install Problem On macOS the documented launchd deployment runs
/usr/local/bin/osctrld
as root with its config in
/usr/local/etc/osctrld/
. On Intel Macs with Homebrew,
/usr/local
and the Cellar belong to the Homebrew user, and the README installed files with no
sudo
or
chown
. Anyone who owns those directories could swap the binary or the config, and launchd would run it as root. The config controls every path root writes to and the extensions directory. osctrld never checked the owner or mode of its config file. Change • Startup warning (Linux and macOS). When osctrld runs as root with a config file,
warnUnsafeConfigFile
warns, once per problem and naming only paths, if: • the file is not owned by root, or is group- or world-writable; • the file's directory is not owned by root, or is group- or world-writable; • the config is a symlink: the resolved target and both directories are checked, with duplicates skipped (macOS
/var
to
/private/var
does not double-warn). • The check is a pure function (
ownershipProblems
) plus seams for the effective user and file owner. It runs after the logger is set up, so
check-config
shows it too. It is a no-op on Windows and for non-root runs. • macOS deployment. The sample launchd plist now uses
/Library/osctrld/osctrld
and
/Library/osctrld/osctrld.yaml
, with a comment on ownership. The README macOS section installs with
sudo install -o root -g wheel
(binary 0755, config 0600, plist 0644), warns that Intel Homebrew
/usr/local
and the Cellar are user-owned, gives the commands to copy a Homebrew-installed binary to a root-owned location (re-copy after
brew upgrade
), and explains how to migrate from
/usr/local
. A new Hardening subsection describes the warning. • `ARCHITECTURE.md`: startup checks, macOS deployment, and a Known Constraints entry. Security / operational impact • Makes the root-via-config path visible on every command, including
check-config
, and fixes the documented install. • Warn only: nothing is refused and exit codes are unchanged, so existing fleets keep starting. Root-run daemons whose config is not root-owned or is group/world-writable will log warnings at every start. A config owned by a root group such as
root:wheel 0660
also warns. • Already-installed plists are untouched. Operators copying the new sample must install to
/Library/osctrld
. • Not checked: the binary itself, ancestors above the immediate directory, and intermediate symlink directories. The docs steer away from user-owned locations, but code cannot detect a swapped binary. • The flagfile, secret, certificate and extension directories have no equivalent helper and were not added. Existing checks cover the upgrade stash and the autoload file. Validation •
go test ./... -count=1 -race
,
go vet
for linux, darwin and windows,
gofmt
,
golangci-lint
(also for Windows),
plutil -lint
on the plist. • Tests run on any OS, with the real app capturing stderr: • a table test of the ownership and mode rules; • a world-writable file warns (group and world each once) and no secret appears in the output; • a good 0600 file gives no warning, and a non-root user gives none; • a file and directory owned by another user warn; • a symlink to a 0666 target warns, and a 0777 parent directory warns. • Seen failing first: a compile failure, then behavioural failures in the table test and 4 of the 6 wiring tests. The good-file and non-root tests passed trivially until the check existed. • Not verified on a macOS host: loading the new plist with launchd, running the README install commands, and the warning on an Intel Homebrew
/usr/local
install. Not in this PR • Windows secret ACL, atomic file writes, and Windows ownership checks. •
security_engineer.md
says the systemd unit runs as a dedicated
osctrld
user, but
service/linux/systemd.service
has
User=root
. A doc fix is needed there. jmpsec/osctrld