Skip to content

fix(api-service): Validate metadata keys, use correct signature header#10792

Merged
scopsy merged 2 commits intonextfrom
minor-flow-fixes
Apr 19, 2026
Merged

fix(api-service): Validate metadata keys, use correct signature header#10792
scopsy merged 2 commits intonextfrom
minor-flow-fixes

Conversation

@scopsy
Copy link
Copy Markdown
Contributor

@scopsy scopsy commented Apr 19, 2026

Add strict validation for metadata signal keys (regex, forbidden keys, max length) and expose isValidMetadataSignalKey; update DTO validator and runtime handler to reject unsafe keys to prevent prototype pollution. Use HttpHeaderKeysEnum.NOVU_SIGNATURE when sending bridge requests so the framework can verify HMACs. Add SSRF validation for bridgeUrl/devBridgeUrl in UpdateAgent via validateUrlSsrf and assertSafeBridgeUrl to prevent pointing the bridge at internal or metadata endpoints.

What changed? Why was the change needed?

What changed

This PR hardens agent bridge handling and metadata processing to fix security gaps: it enforces strict validation on metadata signal keys (pattern, length, and forbidden prototype-related keys) to prevent prototype pollution; it switches bridge request signatures to the framework's HttpHeaderKeysEnum.NOVU_SIGNATURE so HMACs can be verified; and it adds SSRF checks for bridgeUrl/devBridgeUrl to stop agents pointing at internal or metadata endpoints. Validation is applied at DTO level and at runtime when handling agent replies.

Affected areas

api: Added and exported isValidMetadataSignalKey and tightened DTO validation for agent reply payloads; updated handle-agent-reply to reject unsafe or undefined metadata keys before merging; updated bridge-executor to use HttpHeaderKeysEnum.NOVU_SIGNATURE for outbound requests and to run per-request SSRF validation; added SSRF validation and assertSafeBridgeUrl checks in update-agent use case; updated e2e tests to cover SSRF rejection and adjusted fixture URLs.

Key technical decisions

  • Metadata keys must match ^[a-zA-Z0-9]+(?:[-_:][a-zA-Z0-9]+)*$, be ≤128 characters, and must not be proto, constructor, or prototype to block prototype-pollution vectors.
  • SSRF protection uses validateUrlSsrf (checked before sending requests and on agent update) and fails fast with BadRequest on unsafe URLs.
  • Use of HttpHeaderKeysEnum.NOVU_SIGNATURE replaces a hardcoded header name so the framework can verify HMAC signatures.

Testing

Added/updated e2e tests for bridge URL management to assert SSRF rejections and adjusted bridge URL fixtures; recommend unit tests for the new isValidMetadataSignalKey behavior (regex, length, forbidden keys) and for runtime rejection of undefined metadata values.

Screenshots

Expand for optional sections

Related enterprise PR

Special notes for your reviewer

Add strict validation for metadata signal keys (regex, forbidden keys, max length) and expose isValidMetadataSignalKey; update DTO validator and runtime handler to reject unsafe keys to prevent prototype pollution. Use HttpHeaderKeysEnum.NOVU_SIGNATURE when sending bridge requests so the framework can verify HMACs. Add SSRF validation for bridgeUrl/devBridgeUrl in UpdateAgent via validateUrlSsrf and assertSafeBridgeUrl to prevent pointing the bridge at internal or metadata endpoints.
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 19, 2026

Deploy Preview for dashboard-v2-novu-staging canceled.

Name Link
🔨 Latest commit ed2def7
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69e4bfd953c9ab0008b28b35

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 19, 2026

Hey there and thank you for opening this pull request! 👋

We require pull request titles to follow specific formatting rules and it looks like your proposed title needs to be adjusted.

Your PR title is: fix(api-service): Validate metadata keys, use correct signature header

Requirements:

  1. Follow the Conventional Commits specification
  2. As a team member, include Linear ticket ID at the end: fixes TICKET-ID or include it in your branch name

Expected format: feat(scope): Add fancy new feature fixes NOV-123

Details:

PR title must end with 'fixes TICKET-ID' (e.g., 'fixes NOV-123') or include ticket ID in branch name

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 993b355e-86b5-4c93-abbe-aaeddfca56b7

📥 Commits

Reviewing files that changed from the base of the PR and between 440282c and ed2def7.

📒 Files selected for processing (5)
  • apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
  • apps/api/src/app/agents/e2e/agents.e2e.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts

📝 Walkthrough

Walkthrough

Adds strict metadata signal key validation and rejects undefined metadata values; adds per-request SSRF validation for bridge URLs (update and runtime); and standardizes the bridge signature header constant.

Changes

Cohort / File(s) Summary
Metadata Signal Validation
apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts, apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
Added exported isValidMetadataSignalKey(key) (alphanumeric with -, _, : separators, ≤128 chars, disallowing __proto__, constructor, prototype). IsValidSignal.validate() and executeMetadataSignals() now require keys pass this validator and reject undefined values; metadata merge typed as Record<string, unknown>.
Bridge URL SSRF Validation
apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
Added assertSafeBridgeUrl() and pre-checks calling validateUrlSsrf(url) for bridgeUrl and devBridgeUrl, returning HTTP 400 on blocked/unsafe URLs before repo operations.
Bridge Execution & Header
apps/api/src/app/agents/services/bridge-executor.service.ts
Added per-try SSRF validation inside fireWithRetries (calls validateUrlSsrf(url) before each fetch) and replaced literal 'x-novu-signature' with HttpHeaderKeysEnum.NOVU_SIGNATURE for outbound signature header.
Tests (e2e)
apps/api/src/app/agents/e2e/agents.e2e.ts
Updated example bridge URLs and added negative SSRF-style tests: PATCH bridgeUrl to http://localhost:4000/... and PUT devBridgeUrl to http://169.254.169.254/... must return 400 and include the relevant field in the response body.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • #10754: Touches the same metadata signal validation logic and the IsValidSignal validator in the DTO—directly related.

Suggested labels

@novu/api-service

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 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 scope 'api-service', uses lowercase imperative description, and clearly summarizes the main changes (metadata key validation and signature header correction).

✏️ 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.

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/usecases/update-agent/update-agent.usecase.ts (1)

130-132: Format the early return per the project rule.

if (!url) return; violates the required blank line before return statements. As per coding guidelines, “Include a blank line before every return statement.”

🎨 Proposed formatting adjustment
-    if (!url) return;
+    if (!url) {
+
+      return;
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts` around
lines 130 - 132, In the assertSafeBridgeUrl method, adjust the early-return
formatting to satisfy the project's rule by inserting a blank line immediately
before the return statement; locate the private async assertSafeBridgeUrl(url:
string | undefined | null, field: string) function and add a blank line before
the existing "return" so the method follows "Include a blank line before every
return statement" style guideline.
🤖 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/dtos/agent-reply-payload.dto.ts`:
- Around line 61-62: Update the defaultMessage() in AgentReplyPayloadDto (method
defaultMessage) so the metadata key error text precisely reflects the regex:
keys must be 1–128 characters long, consist of letters, digits and the
separators "-", "_" and ":", must not begin or end with a separator, and must
not contain consecutive separators; keep the note that trigger signals require
workflowId. Make the new message concise but include these exact constraints so
rejected keys are clearer to users.

In
`@apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts`:
- Around line 275-287: The runtime path currently validates only signal.key but
allows signal.value to be undefined which gets assigned into merged and later
omitted by JSON.stringify; update the logic in handle-agent-reply.usecase.ts
around the signals loop to mirror the DTO by rejecting undefined metadata
values: when iterating signals (same loop that calls isValidMetadataSignalKey),
check signal.value !== undefined and throw BadRequestException with a clear
message for invalid/undefined values, and only assign merged[signal.key] =
signal.value if the value passes this check.

In `@apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts`:
- Around line 39-44: The update-time SSRF checks (assertSafeBridgeUrl for
bridgeUrl and devBridgeUrl) are insufficient because
BridgeExecutorService.fireWithRetries performs the actual fetch without runtime
validation; add an immediate SSRF validation call inside
BridgeExecutorService.fireWithRetries just before any fetch() (reusing
assertSafeBridgeUrl or an exported validator) to re-validate the URL(s) being
fetched (bridgeUrl and devBridgeUrl) and fail fast if invalid, ensuring the
fetch cannot be invoked on DNS-rebound or pre-existing unsafe addresses.

---

Nitpick comments:
In `@apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts`:
- Around line 130-132: In the assertSafeBridgeUrl method, adjust the
early-return formatting to satisfy the project's rule by inserting a blank line
immediately before the return statement; locate the private async
assertSafeBridgeUrl(url: string | undefined | null, field: string) function and
add a blank line before the existing "return" so the method follows "Include a
blank line before every return statement" style guideline.
🪄 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: 60de1d1c-80a0-4ea3-8c14-918870590d63

📥 Commits

Reviewing files that changed from the base of the PR and between 8389bc4 and 440282c.

📒 Files selected for processing (4)
  • apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts

Comment thread apps/api/src/app/agents/dtos/agent-reply-payload.dto.ts Outdated
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.

1 participant