feat(webhook): per-user local-webhook receiver daemon (#101)#146
Draft
defangdevs wants to merge 1 commit into
Draft
feat(webhook): per-user local-webhook receiver daemon (#101)#146defangdevs wants to merge 1 commit into
defangdevs wants to merge 1 commit into
Conversation
Wires the local-channels local-webhook plugin into agent-box as an opt-in, per-user webhook channel (services.agent-box.webhook.enable, default off): - A receiver-only daemon per web user (agent-box-webhook-<user>) runs webhook.mjs in RECEIVER_ONLY mode, owning a socket-activated UNIX ingress (0660 <user>:caddy, same isolation model as the settings socket) and fanning HMAC-verified deliveries to that user's sessions over IPC. - Caddy reverse-proxies the UNAUTHENTICATED public path /<user>/webhook to that socket (GitHub can't basic-auth; webhook.mjs's per-source HMAC is the trust boundary). More specific than /<user>/*, no basic_auth, no 401s. - Sessions load the plugin non-interactively via seeded settings.json (extraKnownMarketplaces + enabledPlugins) and get a per-session LOCAL_WEBHOOK_SESSION (+ STATE_DIR, PORT=0 pure IPC peer) exported at spawn. - local-channels pinned by rev+hash via builtins.fetchTarball; nodejs-slim added to the daemon and (when enabled) the session PATH for the plugin's node. Source edited in agent-box.nix.in; agent-box.nix regenerated (assemble --check passes). Default-off, so existing boxes are unaffected. NOT YET TESTED end-to-end: needs defangdevs/local-channels public (the pin fetch) plus a real nixos-rebuild + a live claude session to confirm the channel loads and deliveries flow. The webhook.sha256 default is a placeholder to confirm on first real fetch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wx9PVLmDbzS35JCN5N85i6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wires local-channels / local-webhook into agent-box as an opt-in, per-user webhook channel. Closes half of #101 (the other half — the receiver-daemon refactor of the plugin — is defangdevs/local-channels#2).
Design (as locked in #101)
Grain: per-user endpoint → per-user receiver daemon → per-session subscriptions (fan-out over IPC among that user's sessions).
agent-box-webhook-<user>runswebhook.mjswithLOCAL_WEBHOOK_RECEIVER_ONLY=1, owning a socket-activated UNIX ingress at/run/agent-box-webhook/<user>.sock, bound0660 <user>:caddyby a.socketunit — same isolation model as the settings socket (Settings daemon is unauthenticated on localhost: any local user can tamper with another agent's keys #49). No MCP session of its own.handle /<user>/webhook*reverse-proxies to that socket without basic_auth (GitHub can't authenticate;webhook.mjs's per-source HMAC is the trust boundary). More specific than/<user>/*, so no 401s for the fail2ban jail. Registerhttps://<domain>/<user>/webhookin the provider;strip_prefixmaps/<user>/webhook/github→/github.~/.claude/settings.json(extraKnownMarketplacesgithub source +enabledPlugins), and eachclaudesession gets a per-sessionLOCAL_WEBHOOK_SESSION(+LOCAL_WEBHOOK_STATE_DIR,LOCAL_WEBHOOK_PORT=0) exported at spawn so subscriptions never leak between sessions.local-channelspinned by rev+hash viabuiltins.fetchTarball;nodejs-slimadded for the daemon and (when enabled) the session PATH (the plugin's.mcp.jsonruns a barenode).New option:
services.agent-box.webhook.{enable,repo,rev,sha256}— default off, so existing boxes are unaffected. Requiresweb.enable.Source edited in
modules/agent-box.nix.in;modules/agent-box.nixregenerated (assemble --checkpasses,nix-instantiate --parseclean).Blocked on two things before it can be verified:
defangdevs/local-channelsmust be public for thefetchTarballpin (currently private; my token can't flip it). Thewebhook.sha256default is a placeholder to confirm on the first real fetch.nixos-rebuild+ liveclaudesession to confirm the channel loads via seeded settings (no trust prompt) and deliveries actually flow to a session.Also worth a follow-up: an assertion that
webhook.enable → web.enable.🤖 Generated with Claude Code