ADR-0017: Service security model
Date: 2026-06-11 / Status: Accepted
Decision
fmf-service runs as LocalSystem and, at install time, strips privileges to a minimal set via
SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO (SCM removes undeclared privileges from the token — docs/RESEARCH.md). The pipe has
4-layer defense — (1) explicit SDDL (SYSTEM + only the user SID captured at install time) (2) PIPE_REJECT_REMOTE_CLIENTS
(3) FILE_FLAG_FIRST_PIPE_INSTANCE (4) token check on connect (ImpersonateNamedPipeClient) —
guaranteeing "same user only, reject remote, reject anonymous". %ProgramData%\find-my-files gets a
protective DACL at install time (SYSTEM+Administrators; user read only on the logs subdirectory).
The standing threat-model document is docs/SECURITY.md (this ADR records the decision only).
Rationale
- Adopt LocalSystem / reject a dedicated low-privilege account + SeBackupPrivilege: the verified fact only goes as far as "opening a volume handle (\.\C:) requires administrator". There is no documented guarantee that SeBackupPrivilege grants raw volume reads (docs/RESEARCH.md — only describes ACL bypass for regular files). Rather than bet on an unverified privilege configuration, narrow the attack surface with the verified SYSTEM + privilege stripping + zero network capability + minimal pipe-surface opcodes.
- Name the user SID / reject Authenticated Users: Authenticated Users RW lets other users on a multi-user machine search every file name (a name leak that bypasses ACLs). Allowing Administrators also fails: in a UAC-filtered token the Administrators SID becomes SE_GROUP_USE_FOR_DENY_ONLY and is not used in allow ACEs (docs/RESEARCH.md). So store the install-running user's individual SID in service.json and use it in both the SDDL and the token check.
- Handling OTS elevation (elevating with a different administrator account): when a standard user enters a different
administrator credential at UAC, the install-running user (= the admin used to elevate) != the everyday user, and the everyday
user can no longer connect to their own service. The non-elevated UI forwards its own SID to install via
--owner-sid, and install validates it viavalidate_user_sid(accepting only SIDs for which LookupAccountSid returns SidTypeUser) before recording it alongside. The validation defends against threat 7 (injecting an arbitrary SID so someone else reads all file names) — install requires elevation and already has sc.exe-equivalent rights, but unresolvable / non-user-type SIDs are silently dropped (install itself does not fail). - Applying
authorized_sidsrequires a restart: the service reads service.json once at startup and bakes that value into both DACL construction and the connect-time token check (immutable while running). So to add a SID to a running instance,install(idempotent append) must be followed byfmf-service restart(stop->start) —startalone is a no-op with ERROR_SERVICE_ALREADY_RUNNING and keeps rejecting with the old allow list (the root cause of the regression that appeared as repeatedpipe client token rejectedon real hardware). The app's registration flow runs install->restart consecutively. - Reason for defense in depth: a mistake building the SDDL string is the accident pattern of "silently wide open". Pin the structure of the build function with a non-elevated unit test, and place the connect-accept token check independently. Blocking anonymous access is primarily defended by the explicit DACL (no anonymous ACE = default deny) — do not rely on NullSessionPipes defaults, which are machine-type/policy-dependent (docs/RESEARCH.md).
- Protective DACL on %ProgramData%: under the default ACL a general user can directly read .fmfidx (which contains every file name) — no matter how hard the pipe is locked down, it leaks from the side. Leave user read only on logs (to keep the non-elevated F12 "copy diagnostic info" flow working).
Consequences
- In addition to SCM registration, install atomically does SID capture -> service.json, the directory DACL, privilege stripping,
and explicit
SERVICE_PRESHUTDOWN_INFO(current Windows' default grace is only 10 seconds) -> not expressible via sc.exe, so thefmf-service installsubcommand is the only choice (making the logic unit-testable). - uninstall keeps data by default (
--purge-datadeletes .fmfidx/logs/service.json). The leftover artifacts are documented in README and SECURITY.md. - Client-connection prerequisites (verified on real hardware with the non-elevated UI): (1) the client opens the pipe at
Identification level (C#
TokenImpersonationLevel.Identification/ RustSECURITY_SQOS_PRESENT) — at the default anonymous level the server'sImpersonateNamedPipeClientgets an anonymous token, and the connect-time SID check rejects even authorized users entirely. (2) The client-side fake-server check (threat 4) is done not by SYSTEM-token comparison but by PID comparison of the SCM-registered service (QueryServiceStatusEx) — the non-elevated UI cannot open a SYSTEM process's token (ACCESS_DENIED) and cannot get the session-0 identity. Both were blind spots not exposed in console-mode tests whereauthorized_sidsis empty and the token check is skipped; they only appear with the installed service. - "reject other users" and "reject remote" cannot be auto-verified on the dev machine/CI (they need another user's token / another machine) -> substituted by structure-pinning the SDDL build function + the manual checklist in SECURITY.md. Do not create a pipe-creation code path that bypasses the build function (review point).
- Residual risk (accepted): an authorized user can also search the "name and path" of files invisible under their own ACL (a structural property of a file-name-only index; contents are unreadable). Documented in SECURITY.md.
Re-examination triggers
- If a documented demonstration of a low-privilege indexer appears (e.g. a raw volume read means equivalent to FSCTL_READ_UNPRIVILEGED_USN_JOURNAL) -> re-evaluate demoting LocalSystem.
- SERVICE_SID_TYPE_RESTRICTED + an explicit ACE on the index directory (a v2.1 hardening candidate).
- Real demand for multi-user machines (UX for registering multiple authorized SIDs).