Commit Graph

598 Commits

Author SHA1 Message Date
Arnas Donauskas
64f7611bc5
Merge pull request #441 from ArnasDon/chore/next-16.2.12-security
chore(deps): bump next to 16.2.12 to clear 8 open security advisories
2026-07-29 09:18:56 +03:00
Arnas Donauskas
840089f8cc
Merge pull request #440 from ArnasDon/fix/ghsa-6fr5-template-header-ssrf
fix(whatsapp): guard the template image-header fetch against SSRF
2026-07-28 16:31:25 +03:00
Arnas Donauskas
6b4ddf6d6e chore(deps): bump next to 16.2.12 to clear 8 open security advisories
16.2.6 is in the vulnerable range for eight advisories, all patched in
16.2.11: SSRF in rewrites via an attacker-controlled destination
hostname, SSRF in Server Actions on custom servers, middleware/proxy
bypass in App Router, DoS in App Router Server Actions, DoS in the image
optimizer via SVG, unauthenticated disclosure of internal Server
Function endpoints, and two response-body cache-confusion issues.

`eslint-config-next` moves with it to keep the pair on one version.
Lockfile regenerated with npm 10 to match CI's Node 20 — the default
npm 11 drops next-intl's nested optional-peer `@swc/helpers` entry and
breaks `npm ci`.

Verified: lint (39 warnings, unchanged from main), typecheck, 645 tests,
and `next build` all pass on 16.2.12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 13:11:24 +03:00
Arnas Donauskas
44183bc750 fix(whatsapp): guard the template image-header fetch against SSRF
`ensureImageHeaderHandle` fetched the caller-supplied `header_media_url`
server-side with no validation, so a template submit — an action any
authenticated member can perform — could reach loopback, RFC1918 and
cloud-metadata addresses, with the response status and content-type
echoed back through the thrown error.

Wire in the project's existing `isDeliverableUrl` guard, which the two
other outbound-fetch call sites (webhook delivery, automation
`send_webhook`) already use, and reuse their `redirect: 'manual'` +
timeout so a public URL can't 3xx-bounce to an internal one. The
rejection reuses the unreachable-host message verbatim so the failure
doesn't distinguish "blocked" from "down".

Addresses GHSA-6fr5-pxw7-r3xw.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 13:07:09 +03:00
Arnas Donauskas
3180f06da6
Merge pull request #411 from ArnasDon/dependabot/github_actions/actions/setup-node-7
chore(deps): bump actions/setup-node from 6 to 7
2026-07-21 14:40:10 +03:00
dependabot[bot]
b14637c1c3
chore(deps): bump actions/setup-node from 6 to 7
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-20 10:35:02 +00:00
Arnas Donauskas
b24aa794b5
Merge pull request #399 from ArnasDon/fix/settings-nav-suspense
fix(settings): wrap useSearchParams pages in Suspense so navigation works in prod
2026-07-17 15:10:50 +03:00
Arnas Donauskas
8bbbe0a471
fix(settings): wrap useSearchParams pages in Suspense so nav works in prod
The settings page derived its active panel from `useSearchParams()` but
wasn't wrapped in a Suspense boundary. In Next 16 that fails the
production build ("missing Suspense with CSR bailout") and forces the
page to bail to full client-side rendering — shipping a settings screen
whose rail click handlers never wire up. Because the account-menu
Settings link targets `?tab=whatsapp`, users land on the WhatsApp panel
and can't navigate to any other section ("locked on WhatsApp").

Wrap the `useSearchParams` consumer in `<Suspense>` on the three
affected dashboard pages — settings, inbox, automations/new — mirroring
the split login/signup already use. Also add the missing
`Settings.sections.quick-replies` label, which was rendering as a raw
translation key in the settings rail.

Verified: production build now succeeds (previously errored out during
static generation); every settings rail section navigates correctly in a
production build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 15:08:25 +03:00
Arnas Donauskas
8bbda771ce
Merge pull request #386 from Andeveling/contrib/feat-colombian-peso-currency
feat: add Colombian Peso (COP) currency option
2026-07-17 13:47:55 +03:00
Arnas Donauskas
27b0acf0d5
Merge pull request #366 from moduvoice/i18n/add-korean
feat(i18n): add Korean (ko) translation
2026-07-17 13:37:18 +03:00
Arnas Donauskas
b18b5fa8e0
Merge pull request #380 from ArnasDon/dependabot/npm_and_yarn/dev-dependencies-b44e82a8cb
chore(deps-dev): bump the dev-dependencies group with 4 updates
2026-07-17 13:36:02 +03:00
Arnas Donauskas
3e4ed3714d
Merge pull request #382 from ArnasDon/dependabot/npm_and_yarn/recharts-3.9.2
chore(deps): bump recharts from 3.8.1 to 3.9.2
2026-07-17 13:35:49 +03:00
Arnas Donauskas
3ca3ff512a
Merge pull request #383 from ArnasDon/dependabot/npm_and_yarn/next-intl-4.13.2
chore(deps): bump next-intl from 4.13.1 to 4.13.2
2026-07-17 13:35:40 +03:00
Arnas Donauskas
8725865198
fix(deps): restore nested @swc/helpers entry in lockfile
Dependabot's lockfile regeneration drops the nested
node_modules/next-intl/node_modules/@swc/helpers@0.5.23 optional-peer
entry that CI's `npm ci` (Node 20 / npm 10) requires, so the job failed
at install before lint/typecheck/test/build ran. Regenerated
package-lock.json with npm 10 to restore it; no dependency versions change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:31:54 +03:00
Arnas Donauskas
75a082cfe4
fix(deps): restore nested @swc/helpers entry in lockfile
Dependabot's lockfile regeneration drops the nested
node_modules/next-intl/node_modules/@swc/helpers@0.5.23 optional-peer
entry that CI's `npm ci` (Node 20 / npm 10) requires, so the job failed
at install before lint/typecheck/test/build ran. Regenerated
package-lock.json with npm 10 to restore it; no dependency versions change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:31:49 +03:00
Arnas Donauskas
356e198f93
fix(deps): restore nested @swc/helpers entry in lockfile
Dependabot's lockfile regeneration drops the nested
node_modules/next-intl/node_modules/@swc/helpers@0.5.23 optional-peer
entry that CI's `npm ci` (Node 20 / npm 10) requires, so the job failed
at install before lint/typecheck/test/build ran. Regenerated
package-lock.json with npm 10 to restore it; no dependency versions change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:31:44 +03:00
Arnas Donauskas
17cd16a52a
Merge pull request #391 from mooouuu/fix/missing-tags-translation
fix(i18n): add missing contact tags tab label
2026-07-17 12:48:26 +03:00
Arnas Donauskas
5413dd634a
Merge pull request #393 from mooouuu/feat/tag-added-trigger
feat(automations): dispatch tag added triggers
2026-07-17 12:48:18 +03:00
Arnas Donauskas
96e60adecf
Merge pull request #396 from opastorello/fix/cron-timing-safe
fix: use timing-safe compare for cron secret
2026-07-17 12:48:10 +03:00
Arnas Donauskas
f69c82aea0
Merge pull request #398 from ArnasDon/fix/365-login-ngrok-redirect
fix: full-page navigation after login so the session is applied (#365)
2026-07-17 12:41:47 +03:00
Arnas Donauskas
1110f1fb45
fix: full-page navigation after login so the session is applied (#365)
Login signed in client-side then soft-navigated with router.push into
/dashboard, a route the middleware gates on the Supabase session cookie.
The soft navigation can reach the protected route before the server
observes the freshly written cookies, so the middleware bounces it back
to /login -- which looks like the page "just refreshing" instead of
signing in. It surfaced over ngrok/HTTPS tunnels (added latency widens
the window) while staying hidden on localhost.

- login: navigate with window.location.href after signInWithPassword,
  mirroring the deliberate full reload the invite-accept flow already
  uses in join/[token]/page.tsx; drops the now-unused useRouter.
- next.config: allow-list common dev tunnel origins (ngrok, cloudflared,
  localtunnel) so Next 16 stops 403-ing dev-only resources over the
  tunnel, extendable via ALLOWED_DEV_ORIGINS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 12:29:01 +03:00
Nicolas Pastorello
ed4ce6b52e fix: use timing-safe compare for cron secret
Match flows cron implementation (timingSafeEqual).
2026-07-16 13:03:21 -03:00
Mauricio Martinez
292f52f903 feat(automations): dispatch tag added triggers 2026-07-16 00:26:21 -06:00
Mauricio Martinez
6a8744c003 fix(i18n): add missing contact tags tab label 2026-07-15 23:41:29 -06:00
Fabian Andres Parra Sanchez
3b48b6e3f4 feat: add Colombian Peso (COP) currency option 2026-07-14 06:55:31 -05:00
dependabot[bot]
eb3c6fdc20
chore(deps): bump next-intl from 4.13.1 to 4.13.2
Bumps [next-intl](https://github.com/amannn/next-intl) from 4.13.1 to 4.13.2.
- [Release notes](https://github.com/amannn/next-intl/releases)
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v4.13.1...v4.13.2)

---
updated-dependencies:
- dependency-name: next-intl
  dependency-version: 4.13.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 10:36:42 +00:00
dependabot[bot]
0ec5de789a
chore(deps): bump recharts from 3.8.1 to 3.9.2
Bumps [recharts](https://github.com/recharts/recharts) from 3.8.1 to 3.9.2.
- [Release notes](https://github.com/recharts/recharts/releases)
- [Changelog](https://github.com/recharts/recharts/blob/main/CHANGELOG.md)
- [Commits](https://github.com/recharts/recharts/compare/v3.8.1...v3.9.2)

---
updated-dependencies:
- dependency-name: recharts
  dependency-version: 3.9.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 10:36:33 +00:00
dependabot[bot]
d9c1f5cc4a
chore(deps-dev): bump the dev-dependencies group with 4 updates
---
updated-dependencies:
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: "@types/node"
  dependency-version: 26.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-dependencies
- dependency-name: prettier
  dependency-version: 3.9.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
- dependency-name: vitest
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 10:35:52 +00:00
moduvoice
b8a0a99bdb feat(i18n): add Korean (ko) translation
Adds messages/ko.json — full next-intl translation covering all 12
namespaces (LoginPage, Sidebar, Header, ModeToggle, Dashboard, Inbox,
Contacts, Pipelines, Broadcasts, Automations, Flows, Settings).

Scoped to the language file only, per maintainer guidance on PR #362:
no changes to the default locale, component text, or any other file.

Verified 1:1 key parity with messages/en.json (1426/1426 leaf keys)
and exact preservation of all interpolation variables and HTML-ish
tags across every message.
2026-07-12 00:06:31 +07:00
Arnas Donauskas
b867760801
Merge pull request #364 from ArnasDon/fix/duplicate-conversations
fix: stop inbox creating duplicate chats for one contact (#363)
2026-07-10 16:27:12 +03:00
Arnas Donauskas
faf0f740a4
fix: stop inbound chats fragmenting into duplicate conversations (#363)
An inbound message could create a second conversation for a contact
under a race (Meta retries a delivery, or a batch fans out to concurrent
after() runs). There was no UNIQUE index and no unique-violation
backstop on conversations, unlike contacts (migration 022). Worse, once
two conversations existed the `.single()` lookup errored on every later
message and the webhook created yet another conversation each time,
snowballing into a wall of duplicate chats.

- findOrCreateConversation now resolves oldest-first with .limit(1)
  instead of .single(), so pre-existing duplicates converge, and catches
  23505 on insert to re-resolve the winning row.
- Same hardening applied to the public-API resolver (resolve-conversation).
- Migration 036 merges existing duplicate conversations into the oldest
  thread (re-pointing messages, reactions, deals, flow_runs,
  notifications, ai_usage_log — no data loss) then adds a
  UNIQUE (account_id, contact_id) index as the authoritative guarantee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 12:27:46 +03:00
Arnas Donauskas
137760e8f8
Merge pull request #358 from ArnasDon/feat/whatsapp-interactive-messages
feat: WhatsApp interactive messages (reply buttons + lists)
2026-07-08 19:10:53 +03:00
Arnas Donauskas
78b42dfba9 feat: WhatsApp interactive messages (reply buttons + lists)
Add full support for WhatsApp Cloud API interactive messages across the
inbox, automations, and a new quick-replies feature. Extends the
interactive plumbing that previously existed only for the Flows engine.

Sending
- send-message core handles `interactive` (reply buttons ≤3, list ≤10
  rows) via the existing Meta senders; validated server-side before send.
- Inbox composer gains a "+" menu → builder dialog with live WhatsApp-style
  preview + Meta char limits (button 20, list row 24, body 1024).
- Automation "Send Buttons" / "Send List" steps in the builder.
- Public v1 /messages accepts type=interactive.

Storage / round-trip
- migration 035: messages.interactive_payload JSONB + quick_replies table.
- Outbound interactive persists the structured payload (composer,
  automations, and Flows) so the thread re-renders the buttons/rows.

Receiving
- Bubble renders outbound interactive via the shared preview; inbound taps
  (button_reply/list_reply) keep the "chosen option" affordance.

Automations
- New `interactive_reply` trigger matches on tapped button/list IDs
  (exact), enabling chained multi-step menus.

Quick replies
- New account-scoped quick_replies (text or interactive snippets), managed
  in Settings and insertable from the composer; "Save as quick reply" in
  the builder.

Shared core in src/lib/whatsapp/interactive.ts + src/components/interactive/.
Includes unit tests for the validator, send-param validation, the
interactive_reply trigger match, and the new automation step/trigger
validation. typecheck + lint clean; full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:03:15 +03:00
Arnas Donauskas
3eff5b854e
Merge pull request #357 from ArnasDon/fix/mcp-server-json
Correct MCP registry namespace casing (ArnasDon), bump to 0.1.1
2026-07-08 18:06:03 +03:00
Arnas Donauskas
21506cb9d8 Correct registry namespace casing; bump to 0.1.1
The MCP registry namespace is case-sensitive and keyed to the GitHub
login: the owner is `ArnasDon`, not `arnasdon`. Publishing as
io.github.arnasdon/wacrm-mcp is rejected 403. Fix the name in
server.json and the matching mcpName in package.json to
io.github.ArnasDon/wacrm-mcp.

Bump to 0.1.1 because the published 0.1.0 baked in the lowercase
mcpName; the registry validates the published package's mcpName
against server.json, so a fresh npm version is required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:58:20 +03:00
Arnas Donauskas
e23f954221
Merge pull request #356 from ArnasDon/fix/mcp-server-json
Fix MCP registry validation in server.json
2026-07-08 17:49:35 +03:00
Arnas Donauskas
18e4bf4d21 Fix MCP registry validation in server.json
Shorten description to the registry's 100-char limit and migrate the
$schema to the current 2025-12-11 version (2025-10-17 is deprecated).
`mcp-publisher validate` now passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:46:21 +03:00
Arnas Donauskas
9441533458
Merge pull request #355 from ArnasDon/feature/mcp-server
Add MCP server for the public API
2026-07-08 17:34:03 +03:00
Arnas Donauskas
78f2f79d3b Add MCP server for the public API
Introduce mcp-server/ — a stdio Model Context Protocol server that
wraps the /api/v1 public API so MCP clients (Claude, Cursor, etc.) can
drive a self-hosted wacrm instance in natural language.

- Thin wrapper over the existing REST API; all auth, scoping, and rate
  limiting stay server-side. No new backend surface.
- Read-only by default: write and broadcast tools are only registered
  when WACRM_ENABLE_WRITES / WACRM_ENABLE_BROADCASTS are set, layered
  on top of the API key's own scopes. send_broadcast also requires an
  explicit confirm and is marked destructive.
- Registry-ready: server.json manifest, package.json mcpName linked to
  it (io.github.arnasdon/wacrm-mcp), and glama.json for discovery.
- Docs: docs/mcp.md + README bullet + mcp-server/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 17:30:24 +03:00
Arnas Donauskas
95f6052e34
Merge pull request #353 from ArnasDon/feat/ai-autoreply-polish
feat(ai): polish auto-reply — inbox controls, real handoff, usage logging
2026-07-08 16:39:17 +03:00
Arnas Donauskas
2311b7ea35 Merge origin/main; internationalize new AI auto-reply UI
Brings in the i18n PR (#348) that landed on main and localizes the new
strings this branch added so nothing regresses the new next-intl
convention:
- ai-config.tsx: resolved import conflict; handoff-target picker strings
  moved to Settings.aiConfig (handoffTo / handoffToDesc / handoffQueue).
- message-bubble.tsx: "AI" badge + tooltip → Inbox.bubble
  (aiBadge / aiBadgeTitle).
- ai-thread-banner.tsx: take-over/resume banner + toasts → new
  Inbox.aiBanner namespace.

The AI Agents section (/agents playground, setup tabs, usage card) is
still English on main, so those strings are left as-is for a future
section-wide i18n pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:35:27 +03:00
Arnas Donauskas
eefb6179c2 fix(db): resolve duplicate migration version 031 (rename profiles-RLS fix to 034)
The security PR (#352) shipped 031_fix_profiles_update_rls.sql while
031_ai_reply_slot_grant.sql (#345) already held version 031. Supabase
keys schema_migrations on the numeric prefix, so applying both to a fresh
DB fails with `duplicate key ... (version)=(031)` (SQLSTATE 23505) — which
is what broke the Supabase Preview check on this PR.

Rename the newer, colliding file to 034 to restore unique versions. It's
idempotent and independent of the AI tables, so re-sequencing after 033 is
safe; a prod DB that already applied 031 will apply this fresh as 034
without side effects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 16:04:39 +03:00
Arnas Donauskas
077d203980
Merge pull request #348 from ivanmercedes/feat/i18n-dashboard-shell
feat(i18n): finalize localization for Dashboard, Inbox, Settings, and Automations
2026-07-08 15:39:18 +03:00
Arnas Donauskas
e132cc7a15 fix(ai): address code-review findings on auto-reply polish
Correctness:
- Resume AI now releases ANY assignment (not just the caller's own).
  A handoff-assigned thread previously stayed assigned after resume, so
  the eligibility gate silently kept the bot muted — Resume was a no-op.
- GET /api/ai/usage: guard days >= 1 (Number(null)===0 is finite, so the
  30-day default was unreachable and the window collapsed to 1 day).
- GET /api/ai/usage: align the query cutoff to the start of the oldest
  charted local day so rows in the oldest partial day no longer count in
  totals while missing from the daily series (totals now match the bars).
- ai-thread-banner: cache account AI-status per accountId and never cache
  a transient fetch failure — fixes cross-account leak and the banner
  staying hidden all session after one failed /api/ai/config call.
- /api/ai/draft: construct the service-role client inside a guard and
  fire-and-forget the usage log, so a missing service-role key (or any
  log failure) can't 500 an already-generated, already-billed draft.

Efficiency:
- Don't await usage logging on the reply hot path (auto-reply + draft);
  logAiUsage swallows its own errors, so fire-and-forget is safe and
  removes a DB round-trip from the customer-facing send.

Reuse / consistency:
- usage route reuses date-utils (daysAgoStart/lastNDayKeys/localDayKey)
  and now buckets by LOCAL day like every other dashboard chart (was UTC).
- extract formatCompactNumber (shared with formatCurrencyShort); drop the
  hand-rolled month table in favour of date-fns; extract
  fetchAccountMembers/memberLabel and use them in the AI settings picker.

Resume still resets the per-conversation reply count (a deliberate,
manual, rate-limited re-engagement — documented in the handler).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 15:35:41 +03:00
Ivan Mercedes
4690c4d929 refactor: address PR feedback for security and cleanups
- refactor: migrate dangerouslySetInnerHTML(t.raw) to t.rich() to prevent HTML injection vectors
- fix: restore per-status wording for flow activation toasts ("Flow activated.", "Archived.", "Saved as draft.")
- chore: remove unused `tComposer` in message-thread.tsx
- chore: remove unused `ROLE_DESCRIPTIONS` in invite-member-dialog.tsx
2026-07-08 07:58:33 -04:00
Arnas Donauskas
c736e7a43f feat(ai): add token-usage dashboard (AI Agents → Usage)
Surfaces the ai_usage_log data added in the previous commit:
- GET /api/ai/usage — admin-only aggregation (totals, per-mode,
  per-model, zero-filled daily series) over a 7/30/90-day window,
  mirroring the ai_usage_log SELECT policy
- AiUsageCard — stat tiles + daily token bar chart + by-model list,
  rendered under a new admin-only "Usage" tab on the AI Agents page

Counts only; no message content is stored or shown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:31:36 +03:00
Arnas Donauskas
b0194dfc27 feat(ai): polish auto-reply — inbox controls, real handoff, usage logging
Makes the AI auto-reply bot visible and controllable from the inbox,
completes the handoff, and records token spend. Builds on the existing
lib/ai engine rather than replacing it.

Track A — user-facing
- messages.ai_generated flag → small "AI" badge on bot-generated bubbles
- "Take over" / "Resume AI" banner per conversation
  (POST /api/ai/autoreply/[id]) — take-over pauses + assigns to the agent
- handoff now assigns to a configurable target (agent or unassigned
  queue) and writes an internal note summarizing the exchange; the
  existing on_conversation_assigned trigger notifies the assignee
- Settings → AI: "Hand off to" target picker

Track B — observability + safety
- providers return normalized token usage; logged per run to ai_usage_log
  (admin-readable) from both auto-reply and draft
- account-wide auto-reply rate limit (30/min) on top of the
  per-conversation cap

Migration: 033_ai_reply_polish.sql (messages.ai_generated,
ai_configs.handoff_agent_id, conversations.ai_handoff_summary,
ai_usage_log). Tests + typecheck + lint pass (607 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 14:03:33 +03:00
Arnas Donauskas
23838a9959
Merge pull request #352 from ArnasDon/fix/ghsa-8jqh-598v-rfxc-webhook-ssrf
fix(security): SSRF guard on automation send_webhook action (GHSA-8jqh-598v-rfxc)
2026-07-08 10:09:23 +03:00
Arnas Donauskas
7d1ddbfdb8 fix(security): SSRF guard on automation send_webhook action (GHSA-8jqh-598v-rfxc)
The send_webhook automation step fetched an account-controlled URL with
attacker-controlled headers and no SSRF protection, following redirects
by default — letting an agent+ reach cloud metadata (169.254.169.254),
loopback, and RFC1918 hosts from the server's network (status code
surfaces in automation logs). The separate webhook_endpoints delivery
path already guards this; the automation path did not.

Route send_webhook through the existing isDeliverableUrl guard before the
fetch, and mirror the deliver.ts hardening: redirect: 'manual' (a public
URL can't 3xx-bounce to an internal one) and a 10s timeout. Add an engine
regression test asserting a link-local target never reaches fetch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:04:11 +03:00
Arnas Donauskas
03e851bea5
Merge pull request #351 from ArnasDon/fix/ghsa-34q7-fv77-625j-role-checks
fix(security): enforce agent role on service-role write routes (GHSA-34q7-fv77-625j)
2026-07-08 09:39:41 +03:00