Commit Graph

3 Commits

Author SHA1 Message Date
justin 4ef8d20578 Skip lpDesktop=winsta0\default for SpecificUser launches
Setting lpDesktop on STARTUPINFO forces the child to open that desktop;
the LogonUser-derived token in SpecificUser mode usually cannot, since
winsta0\default's DACL only grants the currently-logged-in user. The
result was STATUS_DLL_INIT_FAILED (exit 0xC0000142) with empty stdio.

Only InteractiveUser mode needs the explicit interactive desktop -
that whole point of the mode is to land in the user's session. For
SpecificUser, leaving lpDesktop null lets the child inherit our
service desktop, which works for headless batch tasks (AD reads, file
ops, etc.).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:18:59 -04:00
justin 8b855ec9b9 Route SpecificUser through LogonUser+CreateProcessAsUser, not psi.UserName
CreateProcessWithLogonW (which ProcessStartInfo.UserName/Password uses
under the hood) refuses to run when the caller is LocalSystem - which
is exactly the scenario every hook hits, since the service runs as
SYSTEM by default. The hook just got "Access is denied" with no useful
context.

Switch SpecificUser to the same LogonUser + DuplicateTokenEx +
CreateProcessAsUser path that InteractiveUser already uses. The
launcher tries LOGON32_LOGON_INTERACTIVE first, falling back to
LOGON32_LOGON_BATCH for accounts without interactive-logon rights
(typical for service-only users). Domain "." is normalized to the
machine name so ".\justin" works.

The launcher's two public entry points - LaunchAsActiveConsoleUser
and LaunchAsSpecificUser - share the same LaunchWithToken core, so
stdio capture and environment-block construction stay identical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:17:07 -04:00
justin 24d8701b65 Per-endpoint RunAs: Service / InteractiveUser / SpecificUser
Native per-endpoint identity instead of the schtasks bridge:
- Service (default) keeps the existing path - hooks inherit the service
  account (SYSTEM by default, or whatever you installed under).
- SpecificUser binds ProcessStartInfo.UserName / Password / Domain so
  the hook runs in a batch logon session as the named account. Useful
  for AD-write hooks that should NOT run as SYSTEM.
- InteractiveUser uses WTSQueryUserToken(WTSGetActiveConsoleSessionId)
  + DuplicateTokenEx + CreateProcessAsUser to drop the child into the
  logged-in user's session with their environment block. This is the
  real fix for "calc.exe should pop up on my desktop" - no Task
  Scheduler bridge required. Stdio is captured via inheritable
  anonymous pipes so the hook still returns stdout/stderr to the
  caller normally.

Implementation:
- New RunAsMode enum + RunAsConfig model on EndpointConfig
- ConfigStore round-trips RunAs.Password through DPAPI alongside
  bearer/HMAC/PFX secrets
- AdminPipeServer's secret-merge logic preserves the encrypted blob
  when the GUI saves an endpoint without re-typing the password
- New WebhookServer.Core.Execution.Native namespace with NativeMethods
  (P/Invoke) and InteractiveProcessLauncher (token-based launcher)
- ProcessExecutor branches on RunAs.Mode; the Service/SpecificUser
  paths share .NET's Process; InteractiveUser uses the launcher
- GUI editor gets a "Run as" section: dropdown + conditional
  username/password/load-profile fields under SpecificUser

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:08:08 -04:00