Skip to content

fix(api-service,dashboard,shared): support agent file delivery fixes NV-7457#10945

Merged
scopsy merged 7 commits intonextfrom
nv-7457-sending-files-doesnt-work-502-error-on-all-platforms
May 1, 2026
Merged

fix(api-service,dashboard,shared): support agent file delivery fixes NV-7457#10945
scopsy merged 7 commits intonextfrom
nv-7457-sending-files-doesnt-work-502-error-on-all-platforms

Conversation

@scopsy
Copy link
Copy Markdown
Contributor

@scopsy scopsy commented May 1, 2026

Summary

  • Normalize agent file replies through the API so Slack and Teams receive real file buffers, with URL fetching, SSRF redirect validation, and size limits.
  • Allow framework users to pass Buffer, Uint8Array, ArrayBuffer, Blob, base64 data, or URL file refs with clear delivery errors instead of silent text-only fallback.
  • Add Slack files:write scope and a dashboard smart-paste flow for Slack app credentials during agent setup.
sequenceDiagram
  participant App as Agent app
  participant Framework as Framework client
  participant API as Novu API
  participant Chat as Chat adapter
  participant Slack as Slack or Teams
  App->>Framework: ctx.reply(files)
  Framework->>API: JSON file refs
  API->>API: validate, fetch URLs, decode base64
  API->>Chat: FileUpload with Buffer data
  Chat->>Slack: upload file and post message
Loading

Test plan

  • pnpm --filter @novu/framework exec vitest run --typecheck src/resources/agent/agent.test.ts
  • pnpm --filter @novu/api-service exec cross-env TS_NODE_PROJECT=tsconfig.spec.json TS_NODE_TRANSPILE_ONLY=true NODE_ENV=test NOVU_ENTERPRISE=true CLERK_ENABLED=true NODE_OPTIONS=--no-experimental-strip-types mocha --timeout 15000 --require ts-node/register --exit src/app/agents/services/chat-sdk.service.spec.ts
  • pnpm --filter @novu/dashboard exec tsc -b --pretty
  • pnpm --filter @novu/shared build && pnpm --filter @novu/framework build && pnpm --filter @novu/api-service build
  • pnpm --filter @novu/shared exec biome check src/consts/slack-agent-oauth-scopes.ts
  • pnpm --filter @novu/dashboard exec biome check src/components/integrations/components/parse-slack-credentials-block.ts src/components/integrations/components/slack-credentials-paste.tsx src/components/integrations/components/use-slack-credentials-paste-fallback.ts src/components/integrations/components/integration-settings.tsx src/components/agents/slack-setup-guide.tsx
  • pnpm --filter @novu/framework exec biome check src/resources/agent/agent.context.ts src/resources/agent/agent.types.ts src/resources/agent/agent.errors.ts src/resources/agent/agent.test.ts
  • pnpm --filter @novu/api-service exec biome check src/bootstrap.ts src/app/agents/dtos/agent-reply-payload.dto.ts src/app/agents/services/chat-sdk.service.ts src/app/agents/services/chat-sdk.service.spec.ts

What changed

Agents can now deliver files reliably: the framework accepts inline binary (Buffer/Uint8Array/ArrayBuffer/Blob), base64, or URL references and encodes/validates them; the API normalizes file refs, fetches URL files with SSRF-safe DNS/IP checks, bounded redirects, timeouts, and size limits, and materializes Buffer payloads for chat adapters so Slack and Teams receive real file uploads. The dashboard adds a smart-paste flow to auto-fill Slack app credentials. This prevents silent text-only fallbacks and surfaces clear attachment delivery errors.

Affected areas

  • api: Normalizes and validates agent reply file refs, fetches URL files with SSRF validation (DNS + private-IP blocking), redirect bounds, timeouts, content-length and streamed size caps, per-file and per-message limits, and returns attachment_failed errors on failures; also enforces an 8MB JSON body limit for the agents endpoint.
  • dashboard: Adds SlackCredentialsPaste component, a parser and paste-fallback hook to detect/paste Slack “App Credentials” during onboarding, and updates Slack setup UI copy.
  • framework: Serializes agent replies asynchronously, normalizes FileRef inputs (supports binary types), converts inline data to base64, enforces per-file and inline-aggregate size limits, and surfaces upstream delivery error details via improved AgentDeliveryError messages.
  • shared: Adds Slack OAuth scope 'files:write' and exposes SSRF IP helper for centralized validation.

Key technical decisions

  • Defense-in-depth: perform both client-side (framework) validation and server-side (API) validation/fetching for safety and consistency.
  • Secure fetching: enforce DNS resolution checks, reject private/reserved IPs, limit redirects, apply request timeouts, and use content-length plus streaming byte caps to bound downloads.
  • Platform policy: only Slack/Teams receive file uploads; Email/WhatsApp attachments are dropped with a logged warning to avoid unsupported delivery or silent degradation.
  • API surface: agents endpoint JSON body size tightened to reduce attack surface.

Testing

New and extended unit tests in api (chat-sdk.service.spec.ts) and framework cover inline/binary encoding, base64 validation, URL fetch success/failure modes, SSRF rejection, redirect and size enforcement, per-message limits, platform-specific behavior, and AgentDeliveryError messaging; CI typechecks and builds included per package.

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 1, 2026

@netlify
Copy link
Copy Markdown

netlify Bot commented May 1, 2026

Deploy preview added

Name Link
🔨 Latest commit edfe161
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69f4a8fc3aa5cb00087daacc
😎 Deploy Preview https://deploy-preview-10945.dashboard-v2.novu-staging.co
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds attachment preparation and delivery: enforces per-file and aggregate size/count limits, decodes/normalizes inline binary data, securely fetches URL attachments with SSRF/DNS/redirect/timeouts and size checks, drops attachments on unsupported platforms, and introduces Slack credentials paste UX and related tests.

Changes

Cohort / File(s) Summary
API — Reply DTO & Delivery
apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts, apps/api/src/app/agents/services/chat-sdk.service.ts, apps/api/src/app/agents/services/chat-sdk.service.spec.ts, apps/api/src/bootstrap.ts
Introduce prepareContentForDelivery; validate max files and inline base64 length; decode inline data to Buffers; fetch remote url contents with SSRF/DNS/private-IP checks, redirect/timeouts, and per-file streaming/content-length limits; enforce message-level caps; drop attachments for Email/WhatsApp with logged warning; set /v1/agents JSON body limit to 8mb.
Framework — Agent serialization & errors
packages/framework/src/resources/agent/agent.context.ts, packages/framework/src/resources/agent/agent.types.ts, packages/framework/src/resources/agent/agent.errors.ts, packages/framework/src/resources/agent/agent.test.ts
Make MessageContent serialization async and validate options.files; accept `Uint8Array
Dashboard — Slack paste UX
apps/dashboard/src/components/integrations/components/parse-slack-credentials-block.ts, apps/dashboard/src/components/integrations/components/slack-credentials-paste.tsx, apps/dashboard/src/components/integrations/components/use-slack-credentials-paste-fallback.ts, apps/dashboard/src/components/integrations/components/integration-settings.tsx, apps/dashboard/src/components/agents/slack-setup-guide.tsx
Add Slack credential block parser, SlackCredentialsPaste component, and useSlackCredentialsPasteFallback hook; wire paste fallback into IntegrationSettings during Slack onboarding; update Slack setup guide copy and template-literal construction.
Shared — SSRF & Slack scopes
packages/shared/src/utils/ssrf-url-validation.ts, libs/application-generic/src/utils/ssrf-url-validation.ts, packages/shared/src/consts/slack-agent-oauth-scopes.ts, packages/shared/src/utils/ssrf-url-validation.spec.ts
Export isPrivateIp and broaden IPv6 link-local regex coverage; add tests for SSRF/private-IP detection; add 'files:write' to Slack OAuth scopes.
Tests — API & Framework
apps/api/src/app/agents/services/chat-sdk.service.spec.ts, packages/framework/src/resources/agent/agent.test.ts
Add extensive tests covering inline/URL file handling, base64 decoding, SSRF blocking, redirects/timeouts, per-file and aggregate size limits, platform-specific dropping behavior, and AgentDeliveryError message parsing.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ChatSdkService
    participant ValidatorFetcher as Validator/Fetcher
    participant SSRF as SSRF/URL Validator
    participant ChatAdapter as Chat SDK Adapter

    Client->>ChatSdkService: send ReplyContentDto (may include files)
    ChatSdkService->>ChatSdkService: prepareContentForDelivery(content, platform, agentId)

    alt Inline file (data)
        ChatSdkService->>ValidatorFetcher: validate & decode inline data (string/Uint8Array/ArrayBuffer/Blob)
        ValidatorFetcher-->>ChatSdkService: Buffer (≤5 MB) or error
    else Remote file (url)
        ChatSdkService->>SSRF: validateUrlSsrf(url) + DNS/private-IP check
        SSRF-->>ChatSdkService: safe/blocked
        alt safe
            ChatSdkService->>ValidatorFetcher: fetch URL (timeout, redirect limit)
            ValidatorFetcher->>ValidatorFetcher: check content-length / stream limit (per-file cap)
            ValidatorFetcher-->>ChatSdkService: Buffer + mimeType or error
        end
    end

    ChatSdkService->>ChatSdkService: enforce max files & aggregate size limits
    ChatSdkService->>ChatSdkService: drop attachments for Email/WhatsApp (log warning)
    ChatSdkService->>ChatAdapter: post message with prepared files[]
    ChatAdapter-->>ChatSdkService: response
    ChatSdkService-->>Client: return result
Loading
sequenceDiagram
    participant User
    participant SlackPaste as SlackCredentialsPaste
    participant Parser
    participant Form as react-hook-form

    User->>SlackPaste: paste credentials block
    SlackPaste->>Parser: parseSlackCredentialsBlock(text)
    Parser-->>SlackPaste: {values, matched, invalid, unknownLines}
    alt matched >= 1
        SlackPaste->>Form: setValue for matched fields (mark dirty/touched, trigger validation)
        Form-->>SlackPaste: fields updated
        SlackPaste->>User: show summary of filled/replaced/invalid fields
    else
        SlackPaste->>User: show parse results (no useful fields)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits format with valid type (fix) and scopes (api-service, dashboard, shared). Description is lowercase and imperative. Linear ticket reference (NV-7457) is properly appended.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-authored-by: Dima Grossman <dima@grossman.io>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 1, 2026

Open in StackBlitz

npm i https://pkg.pr.new/novuhq/novu/@novu/framework@10945
npm i https://pkg.pr.new/novuhq/novu@10945
npm i https://pkg.pr.new/novuhq/novu/@novu/providers@10945
npm i https://pkg.pr.new/novuhq/novu/@novu/shared@10945

commit: edfe161

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/framework/src/resources/agent/agent.context.ts (1)

186-205: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject attachments on card replies instead of discarding them.

validateFiles() runs here, but the JSX/Card paths never use validFiles, so ctx.reply(Card(...), { files }) silently loses the attachments. This should fail fast with the same “files only allowed with markdown” contract the API already enforces.

Suggested change
 async function serializeContent(content: MessageContent, files?: FileRef[]): Promise<ReplyContent> {
   const validFiles = await validateFiles(files);
+
+  if (validFiles && typeof content !== 'string') {
+    throw new Error('Files can only be sent with markdown content.');
+  }
 
   if (typeof content === 'string') {
     return validFiles ? { markdown: content, files: validFiles } : { markdown: content };
   }

As per coding guidelines packages/framework/**/*.{ts,tsx,js,jsx}: packages/framework defines the code-first workflow SDK and serves as the interface between user-defined workflows and Novu's engine; write clear, minimal abstractions as changes affect the developer-facing API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/framework/src/resources/agent/agent.context.ts` around lines 186 -
205, serializeContent currently validates files but discards them for JSX/Card
replies; update serializeContent to reject attachments when content is not a
string by checking the result of validateFiles(files) and throwing an error
(same contract/message used elsewhere, e.g., "files are only allowed with
markdown replies") if validFiles exist for JSX or CardElement branches; modify
the JSX path (isJSX -> toCardElement) and the isCardElement branch in
serializeContent to throw when files were provided so attachments fail fast
instead of being silently dropped.
🧹 Nitpick comments (2)
apps/dashboard/src/components/integrations/components/slack-credentials-paste.tsx (1)

200-202: ⚡ Quick win

Remove nested ternary in summary copy

This expression uses a nested ternary and is hard to scan. Extract the suffix/segment to a variable before JSX.

💡 Suggested refactor
+  const overwrittenSuffix = outcome.overwritten.length === 1 ? '' : 's';
+  const overwrittenText =
+    outcome.overwritten.length > 0
+      ? ` · replaced ${outcome.overwritten.length} existing value${overwrittenSuffix}`
+      : '';
@@
-            {outcome.overwritten.length > 0
-              ? ` · replaced ${outcome.overwritten.length} existing value${outcome.overwritten.length === 1 ? '' : 's'}`
-              : ''}
+            {overwrittenText}

As per coding guidelines: "Do not use nested ternaries".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/components/integrations/components/slack-credentials-paste.tsx`
around lines 200 - 202, Extract the nested ternary into a precomputed variable
in the component body: compute a pluralSuffix variable (e.g., const pluralSuffix
= outcome.overwritten.length === 1 ? '' : 's') and then compute a single
replacedText string (e.g., const replacedText = outcome.overwritten.length > 0 ?
` · replaced ${outcome.overwritten.length} existing value${pluralSuffix}` : '')
and use {replacedText} in the JSX instead of the nested ternary; place these
consts in the Slack credentials paste component scope (near where outcome is
available) so the JSX line becomes simply {replacedText}.
apps/api/src/app/agents/services/chat-sdk.service.ts (1)

52-60: 🏗️ Heavy lift

Consolidate the attachment limits in shared constants.

These caps now live in framework serialization, DTO validation, API delivery, and bootstrap. The new /v1/agents parser limit has already drifted close enough to create a boundary bug, so keeping one source of truth would make the SDK and API fail consistently.

As per coding guidelines packages/@(shared|framework|js|react)/**: Use shared packages: packages/shared, packages/framework, packages/js, packages/react for shared code and utilities.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/chat-sdk.service.ts` around lines 52 - 60,
The attachment limit constants (BASE64_REGEX, MAX_INLINE_FILE_BYTES,
MAX_INLINE_AGGREGATE_FILE_BYTES, MAX_FILE_BYTES, MAX_FILES_PER_MESSAGE,
MAX_AGGREGATE_FILE_BYTES, MAX_INLINE_FILE_BASE64_CHARS, FILE_FETCH_TIMEOUT_MS,
MAX_FILE_FETCH_REDIRECTS) are duplicated across layers; extract these constants
into a single shared module (e.g., packages/shared or packages/framework) and
replace the local definitions in chat-sdk.service.ts with imports from that
shared constants module so all validators/parsers/SDKs use the same source of
truth; update any references to the renamed import identifiers if needed and run
tests to ensure parity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/app/agents/services/chat-sdk.service.ts`:
- Around line 223-231: Remove logging of raw attachment filenames in the
UNSUPPORTED_FILE_PLATFORMS branch: update the logger.warn call in
chat-sdk.service.ts (the block checking
UNSUPPORTED_FILE_PLATFORMS.has(platform)) to only include agentId, platform, and
droppedCount (e.g., content.files.length), and remove any reference to
content.files.map(...) or filenames so user-controlled filenames are not
emitted; keep the existing message string unchanged.

In `@apps/api/src/bootstrap.ts`:
- Line 117: The body-parser limit for the /v1/agents route is too small
(currently '7mb') and causes premature 413 errors before the attachment
validator (agentRawBodyBuffer) runs; update the bodyParser.json call for the
'/v1/agents' route to raise the limit to a safer value (e.g., '8mb' or '10mb')
or replace the magic string with a named constant, ensuring the new limit
accounts for base64 expansion and JSON envelope so the attachment validator can
return attachment_failed as intended.

In
`@apps/dashboard/src/components/integrations/components/parse-slack-credentials-block.ts`:
- Around line 98-103: The loop advances the index by a fixed +1 after calling
consumeNextValue, but consumeNextValue can read several lines ahead, so the
consumed lines get reprocessed as unknownLines; update consumeNextValue (or add
a helper) to return both the parsed value and the index it consumed (e.g., {
value, nextIndex } or similar) and then set index = nextIndex (instead of index
+= 1) after assigning matchedField/value in the block that uses matchesLabel,
and apply the same change to the other identical block that handles matchedField
(the second occurrence referenced in the review).

---

Outside diff comments:
In `@packages/framework/src/resources/agent/agent.context.ts`:
- Around line 186-205: serializeContent currently validates files but discards
them for JSX/Card replies; update serializeContent to reject attachments when
content is not a string by checking the result of validateFiles(files) and
throwing an error (same contract/message used elsewhere, e.g., "files are only
allowed with markdown replies") if validFiles exist for JSX or CardElement
branches; modify the JSX path (isJSX -> toCardElement) and the isCardElement
branch in serializeContent to throw when files were provided so attachments fail
fast instead of being silently dropped.

---

Nitpick comments:
In `@apps/api/src/app/agents/services/chat-sdk.service.ts`:
- Around line 52-60: The attachment limit constants (BASE64_REGEX,
MAX_INLINE_FILE_BYTES, MAX_INLINE_AGGREGATE_FILE_BYTES, MAX_FILE_BYTES,
MAX_FILES_PER_MESSAGE, MAX_AGGREGATE_FILE_BYTES, MAX_INLINE_FILE_BASE64_CHARS,
FILE_FETCH_TIMEOUT_MS, MAX_FILE_FETCH_REDIRECTS) are duplicated across layers;
extract these constants into a single shared module (e.g., packages/shared or
packages/framework) and replace the local definitions in chat-sdk.service.ts
with imports from that shared constants module so all validators/parsers/SDKs
use the same source of truth; update any references to the renamed import
identifiers if needed and run tests to ensure parity.

In
`@apps/dashboard/src/components/integrations/components/slack-credentials-paste.tsx`:
- Around line 200-202: Extract the nested ternary into a precomputed variable in
the component body: compute a pluralSuffix variable (e.g., const pluralSuffix =
outcome.overwritten.length === 1 ? '' : 's') and then compute a single
replacedText string (e.g., const replacedText = outcome.overwritten.length > 0 ?
` · replaced ${outcome.overwritten.length} existing value${pluralSuffix}` : '')
and use {replacedText} in the JSX instead of the nested ternary; place these
consts in the Slack credentials paste component scope (near where outcome is
available) so the JSX line becomes simply {replacedText}.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d903a308-c017-467e-9175-d3bac347cee0

📥 Commits

Reviewing files that changed from the base of the PR and between 8085a46 and 0ef9f71.

📒 Files selected for processing (14)
  • apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
  • apps/api/src/app/agents/services/chat-sdk.service.spec.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • apps/api/src/bootstrap.ts
  • apps/dashboard/src/components/agents/slack-setup-guide.tsx
  • apps/dashboard/src/components/integrations/components/integration-settings.tsx
  • apps/dashboard/src/components/integrations/components/parse-slack-credentials-block.ts
  • apps/dashboard/src/components/integrations/components/slack-credentials-paste.tsx
  • apps/dashboard/src/components/integrations/components/use-slack-credentials-paste-fallback.ts
  • packages/framework/src/resources/agent/agent.context.ts
  • packages/framework/src/resources/agent/agent.errors.ts
  • packages/framework/src/resources/agent/agent.test.ts
  • packages/framework/src/resources/agent/agent.types.ts
  • packages/shared/src/consts/slack-agent-oauth-scopes.ts

Comment thread apps/api/src/app/agents/services/chat-sdk.service.ts
Comment thread apps/api/src/bootstrap.ts Outdated
Co-authored-by: Dima Grossman <dima@grossman.io>
…es NV-7457

Co-authored-by: Dima Grossman <dima@grossman.io>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/api/src/app/agents/services/chat-sdk.service.ts (1)

255-264: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not log raw attachment filenames in this warning.

filenames is user-controlled input and can easily contain names, ticket numbers, or other PII. agentId, platform, and droppedCount should be enough for this branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/chat-sdk.service.ts` around lines 255 - 264,
Remove logging of raw attachment filenames to avoid recording
user-controlled/PII data: in the branch that checks UNSUPPORTED_FILE_PLATFORMS
(the block calling this.logger.warn) stop passing filenames:
content.files.map(...) and only include agentId, platform and droppedCount (or a
sanitized fileCount) in the logger metadata; keep the existing warning message
but ensure any mention of file details is limited to the numeric count (use
droppedCount = content.files.length) and do not log content.files or filenames
anywhere in chat-sdk.service.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/app/agents/services/chat-sdk.service.ts`:
- Around line 104-125: The isPrivateIp function's IPv6 link-local regexes
(currently /^fe[89ab][0-9a-f]{2}:/i and /^::ffff:fe[89ab][0-9a-f]{2}:/i) don't
match valid link-local addresses like fe80::1, allowing SSRF bypass; update
those two patterns to accept 1–3 hex digits after the initial nibble (for
example use /^fe(?:8|9|a|b)[0-9a-f]{0,2}:/i and
/^::ffff:fe(?:8|9|a|b)[0-9a-f]{0,2}:/i) so IPv6 link-local addresses are
correctly detected in isPrivateIp.

---

Duplicate comments:
In `@apps/api/src/app/agents/services/chat-sdk.service.ts`:
- Around line 255-264: Remove logging of raw attachment filenames to avoid
recording user-controlled/PII data: in the branch that checks
UNSUPPORTED_FILE_PLATFORMS (the block calling this.logger.warn) stop passing
filenames: content.files.map(...) and only include agentId, platform and
droppedCount (or a sanitized fileCount) in the logger metadata; keep the
existing warning message but ensure any mention of file details is limited to
the numeric count (use droppedCount = content.files.length) and do not log
content.files or filenames anywhere in chat-sdk.service.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 53277179-a3ea-4ebf-b540-5d44d26d7ab5

📥 Commits

Reviewing files that changed from the base of the PR and between 219b402 and 94c99a4.

📒 Files selected for processing (2)
  • apps/api/src/app/agents/services/chat-sdk.service.spec.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts

Comment thread apps/api/src/app/agents/services/chat-sdk.service.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/api/src/app/agents/services/chat-sdk.service.ts (1)

120-121: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix IPv6 link-local regexes; current patterns miss valid addresses.

Line 120 and Line 121 currently require an overlong first hextet, so valid link-local addresses (for example fe80::1) won’t match and can bypass the private-IP SSRF block.

Suggested patch
-    /^fe[89ab][0-9a-f]{2}:/i,
-    /^::ffff:fe[89ab][0-9a-f]{2}:/i,
+    /^fe(?:8|9|a|b)[0-9a-f]{0,2}:/i,
+    /^::ffff:fe(?:8|9|a|b)[0-9a-f]{0,2}:/i,
#!/bin/bash
# Verify current regex behavior and confirm Line 120-121 patterns miss valid link-local samples.
cat -n apps/api/src/app/agents/services/chat-sdk.service.ts | sed -n '116,123p'

node - <<'NODE'
const buggy = [/^fe[89ab][0-9a-f]{2}:/i, /^::ffff:fe[89ab][0-9a-f]{2}:/i];
const fixed = [/^fe(?:8|9|a|b)[0-9a-f]{0,2}:/i, /^::ffff:fe(?:8|9|a|b)[0-9a-f]{0,2}:/i];
const samples = ['fe80::1', 'fe8a::1', 'fe9f::1', 'febf::1'];

console.log('Buggy patterns:');
for (const re of buggy) {
  console.log(re.toString(), samples.map((s) => `${s}:${re.test(s)}`).join(' | '));
}
console.log('\nFixed patterns:');
for (const re of fixed) {
  console.log(re.toString(), samples.map((s) => `${s}:${re.test(s)}`).join(' | '));
}
NODE
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/chat-sdk.service.ts` around lines 120 - 121,
Update the IPv6 link-local regexes used in the SSRF/private-IP check in
chat-sdk.service (the patterns currently /^fe[89ab][0-9a-f]{2}:/i and
/^::ffff:fe[89ab][0-9a-f]{2}:/i) because they require an overly long first
hextet and miss valid addresses like "fe80::1"; replace them with
/^fe(?:8|9|a|b)[0-9a-f]{0,2}:/i and /^::ffff:fe(?:8|9|a|b)[0-9a-f]{0,2}:/i
respectively so link-local addresses with 0–2 trailing hex digits in the first
hextet are correctly matched.
🧹 Nitpick comments (1)
apps/api/src/app/agents/services/chat-sdk.service.ts (1)

94-102: Use interface for backend object type definitions in this file.

Lines 94–97 introduce backend object-shape definitions as type aliases; backend files must use interface for type definitions per repo guidelines.

Suggested patch
-type ChatSdkFile = Omit<FileRef, 'data'> & { data?: Buffer };
-type ChatSdkReplyContent = Omit<ReplyContentDto, 'files'> & { files?: ChatSdkFile[] };
-type MaterializedFile = ChatSdkFile & { size: number; source: 'data' | 'url' };
-type PinnedFileResponse = {
+interface ChatSdkFile extends Omit<FileRef, 'data'> {
+  data?: Buffer;
+}
+interface ChatSdkReplyContent extends Omit<ReplyContentDto, 'files'> {
+  files?: ChatSdkFile[];
+}
+interface MaterializedFile extends ChatSdkFile {
+  size: number;
+  source: 'data' | 'url';
+}
+interface PinnedFileResponse {
   status: number;
   statusText: string;
   headers: http.IncomingHttpHeaders;
   data: Buffer;
-};
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/chat-sdk.service.ts` around lines 94 - 102,
The file defines backend object shapes using type aliases; replace the type
aliases ChatSdkFile, ChatSdkReplyContent, MaterializedFile, and
PinnedFileResponse with equivalent interface declarations (keeping the same
property names, optional markers, and unions like 'data?: Buffer' and 'source:
"data" | "url"') so the backend follows the repo guideline to use interface for
object type definitions; update any imports/uses if necessary to reference the
new interfaces.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/dashboard/src/components/integrations/components/parse-slack-credentials-block.ts`:
- Around line 166-169: matchesLabel currently only compares exact text so
headings with a trailing colon (e.g., "App ID:") don't match; update
matchesLabel to normalize trailing colons by stripping any trailing colon and
surrounding whitespace from the input line (and/or from each label/alias) before
doing case-insensitive comparison. Specifically, inside matchesLabel (function
name) normalize line with something like line.replace(/:\s*$/, '') and compare
against field.label and field.aliases similarly normalized so labels with or
without a trailing colon both match.

---

Duplicate comments:
In `@apps/api/src/app/agents/services/chat-sdk.service.ts`:
- Around line 120-121: Update the IPv6 link-local regexes used in the
SSRF/private-IP check in chat-sdk.service (the patterns currently
/^fe[89ab][0-9a-f]{2}:/i and /^::ffff:fe[89ab][0-9a-f]{2}:/i) because they
require an overly long first hextet and miss valid addresses like "fe80::1";
replace them with /^fe(?:8|9|a|b)[0-9a-f]{0,2}:/i and
/^::ffff:fe(?:8|9|a|b)[0-9a-f]{0,2}:/i respectively so link-local addresses with
0–2 trailing hex digits in the first hextet are correctly matched.

---

Nitpick comments:
In `@apps/api/src/app/agents/services/chat-sdk.service.ts`:
- Around line 94-102: The file defines backend object shapes using type aliases;
replace the type aliases ChatSdkFile, ChatSdkReplyContent, MaterializedFile, and
PinnedFileResponse with equivalent interface declarations (keeping the same
property names, optional markers, and unions like 'data?: Buffer' and 'source:
"data" | "url"') so the backend follows the repo guideline to use interface for
object type definitions; update any imports/uses if necessary to reference the
new interfaces.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: adee62d3-0409-46cb-a54f-e4ee7b5b50fc

📥 Commits

Reviewing files that changed from the base of the PR and between 94c99a4 and f5d0bd7.

📒 Files selected for processing (3)
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • apps/api/src/bootstrap.ts
  • apps/dashboard/src/components/integrations/components/parse-slack-credentials-block.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/api/src/bootstrap.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
apps/api/src/app/agents/services/chat-sdk.service.ts (1)

101-109: ⚡ Quick win

Prefer interface for these backend transport shapes.

These are structural contracts in apps/api, so keeping them as interfaces matches the repo’s backend TypeScript convention and makes later extension/implementation cleaner.

As per coding guidelines, **/*.{ts,tsx}: On the backend: use interface for type definitions; on the frontend: use type for type definitions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/services/chat-sdk.service.ts` around lines 101 - 109,
Change the four backend transport shapes from type aliases to interfaces:
replace the type declarations for ChatSdkFile, ChatSdkReplyContent,
MaterializedFile, and PinnedFileResponse with interface declarations that
preserve the same properties (including optional fields and unions like { data?:
Buffer } and source: 'data' | 'url'), so they follow the backend convention of
using interface for structural contracts and remain extendable/implementable by
other modules.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/app/agents/services/chat-sdk.service.ts`:
- Around line 178-185: prepareContentForDelivery() returns deliveryContent with
.card and materialized .files, but the thread.post call in this branch calls the
card-only overload and drops attachments; update the logic around
deliveryContent handling so that when deliveryContent.card and
deliveryContent.files are both present you either (A) reject/throw a clear error
indicating "card+files not supported" from the caller (or in
prepareContentForDelivery), or (B) call the appropriate thread.post overload (or
construct a payload) that preserves both card and files and forwards
deliveryContent.files along with deliveryContent.card; change the call sites
using thread.post (the branches that currently do
thread.post(deliveryContent.card) and the similar branch later) to handle the
card+files combination consistently.

In `@libs/application-generic/src/utils/ssrf-url-validation.ts`:
- Around line 65-66: The two IPv6 regex literals /^fe(?:8|9|a|b)[0-9a-f]{0,2}:/i
and /^::ffff:fe(?:8|9|a|b)[0-9a-f]{0,2}:/i are too permissive because the tail
is optional; make the last hex nibble mandatory by changing the quantifier to
require 1–2 hex digits (e.g. {1,2}) so only fe80–febf matches; apply the same
change to the duplicate regex in the shared package's ssrf-url-validation
implementation and update the corresponding spec/test expectations to reflect
the corrected matches.

In `@packages/shared/src/utils/ssrf-url-validation.spec.ts`:
- Around line 47-52: The test for validateUrlSsrf should use a unique throwaway
hostname to avoid DNS-cache coupling with the module-scoped LRU memoization;
replace 'https://example.com/file.txt' with a unique '.invalid' host (e.g.,
'https://unique-host.invalid/file.txt') so the mocked dns.promises.lookup call
is always exercised and the assertion against the resolved address (fe80::1)
remains deterministic.

---

Nitpick comments:
In `@apps/api/src/app/agents/services/chat-sdk.service.ts`:
- Around line 101-109: Change the four backend transport shapes from type
aliases to interfaces: replace the type declarations for ChatSdkFile,
ChatSdkReplyContent, MaterializedFile, and PinnedFileResponse with interface
declarations that preserve the same properties (including optional fields and
unions like { data?: Buffer } and source: 'data' | 'url'), so they follow the
backend convention of using interface for structural contracts and remain
extendable/implementable by other modules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a755673b-1cf0-4fd5-bc99-56add518999a

📥 Commits

Reviewing files that changed from the base of the PR and between f5d0bd7 and b5cbfe6.

📒 Files selected for processing (5)
  • apps/api/src/app/agents/services/chat-sdk.service.spec.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • libs/application-generic/src/utils/ssrf-url-validation.ts
  • packages/shared/src/utils/ssrf-url-validation.spec.ts
  • packages/shared/src/utils/ssrf-url-validation.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/api/src/app/agents/services/chat-sdk.service.spec.ts

Comment thread apps/api/src/app/agents/services/chat-sdk.service.ts
Comment on lines +65 to +66
/^fe(?:8|9|a|b)[0-9a-f]{0,2}:/i,
/^::ffff:fe(?:8|9|a|b)[0-9a-f]{0,2}:/i,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The widened IPv6 pattern now matches addresses outside fe80::/10.

fe8::1 expands to 0fe8::1, and feb::1 expands to 0feb::1; neither is in the fe80–febf first-hextet range. Making the tail optional with {0,2} turns those forms into false positives, so the last hex nibble should stay mandatory here. Please mirror the same correction in packages/shared/src/utils/ssrf-url-validation.ts and its new spec expectations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@libs/application-generic/src/utils/ssrf-url-validation.ts` around lines 65 -
66, The two IPv6 regex literals /^fe(?:8|9|a|b)[0-9a-f]{0,2}:/i and
/^::ffff:fe(?:8|9|a|b)[0-9a-f]{0,2}:/i are too permissive because the tail is
optional; make the last hex nibble mandatory by changing the quantifier to
require 1–2 hex digits (e.g. {1,2}) so only fe80–febf matches; apply the same
change to the duplicate regex in the shared package's ssrf-url-validation
implementation and update the corresponding spec/test expectations to reflect
the corrected matches.

Comment thread packages/shared/src/utils/ssrf-url-validation.spec.ts
Detect and ignore Slack's masked secret glyphs when parsing pasted "App Credentials" blocks so users don't accidentally paste bullet characters into fields. parse-slack-credentials-block: add MASK_CHAR_REGEX/isMaskedValue, track masked fields, and count masked fields toward a valid Slack block. slack-credentials-paste.tsx: redesign paste affordance into a collapsible card with preview GIF + skeleton, improve outcome messaging/icons, keep paste box open when masked values are present, and surface masked-field guidance. use-slack-credentials-paste-fallback.ts: show toasts for filled and masked fields and prevent filling when secrets are masked. Also add the preview GIF asset.
@scopsy scopsy merged commit a85d0fd into next May 1, 2026
37 checks passed
@scopsy scopsy deleted the nv-7457-sending-files-doesnt-work-502-error-on-all-platforms branch May 1, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants