ServerConfig grows two fields:
- BindAddresses: list of IPs Kestrel binds to (empty = all interfaces,
current behavior). Listening only on a subset is useful when the host
has multiple NICs and the webhook should not be reachable on all of
them.
- DisplayHost: the hostname/IP the GUI splices into the URL column and
Copy URL button. Cosmetic; doesn't affect what the server accepts.
Server Settings dialog gains a "Network" section: a checkbox for "all
interfaces" plus per-NIC checkboxes auto-detected via NetworkInterface.
GetAllNetworkInterfaces, and an editable ComboBox for the display host
pre-populated with detected IPs and the machine name.
Listener restart fires on BindAddresses change but not on DisplayHost
change (cosmetic).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hook runs were silently dropping their result into the void after
returning the HTTP response. For sync runs the body went to the
caller but nothing was logged; for async runs the result vanished
unless a callback was configured. That made debugging RunAs
failures (logon errors, missing executables) effectively
impossible since the service log only showed the 202.
Now every run emits one log line at INF (success) or WRN
(non-zero exit / timeout / launch error) with runId, slug, exit
code, duration, and truncated stdout/stderr.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
GUI:
- URL column in endpoint grid + Copy URL toolbar button so the full
http://host:port/hook/<slug> is one click away
- Double-click a row to open the edit dialog
- Bearer/HMAC sections in the editor hide when the auth mode doesn't
use them, and reappear with previously-entered values when switched
back
- Log panel auto-scroll checkbox (default on) plus 3s polling so log
entries stream in without manual refresh
- Secret fields are now plain text with a Copy button. Anyone who can
open the admin-pipe-ACL'd GUI is already SYSTEM-equivalent on the
host, so masking the value just made recovery harder. PFX password
in Server Settings gets the same treatment.
Service:
- Admin pipe ops log info-level lines on every mutation
(create/update/delete/enable/disable/update-config/bind-https) so
GUI activity is visible in the Serilog file
- /hook/{slug} accepts GET as well as POST so a browser smoke-test
works without curl
- /favicon.ico returns 204 so browser hits don't pollute logs with 404s
- AdminPipeServer no longer strips plaintext secrets when sending
config to the GUI; the pipe ACL already restricts to SYSTEM/Admins
Scripts:
- New deploy.ps1: stops + republishes + copies binaries to
C:\Program Files\WebhookServer + (re)installs the Windows Service
- install-service.ps1 now uses sc.exe argv splatting consistently for
both create and config paths
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the .NET 8 solution scaffolded against PLAN.md. Three projects share
WebhookServer.Core (models, auth, execution, storage, IPC, callbacks)
and WebhookServer.Service hosts an embedded Kestrel listener plus the
named-pipe admin server. WebhookServer.Gui is a thin MVVM client over
the pipe. Includes 25 unit tests covering HMAC verification, bearer
auth, IP allowlist parsing, arg-template rendering, DPAPI round-trip,
and the encrypt-on-save config store.
Install/uninstall PowerShell scripts default to LocalSystem and accept
a domain user or gMSA via -ServiceAccount.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>