Skip to content

v0.6.83: redis TLS SNI override for IP-based REDIS_URL, zod schema fixes#4637

Merged
TheodoreSpeaks merged 1 commit into
mainfrom
staging
May 17, 2026
Merged

v0.6.83: redis TLS SNI override for IP-based REDIS_URL, zod schema fixes#4637
TheodoreSpeaks merged 1 commit into
mainfrom
staging

Conversation

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator

…es (#4635)

* feat(redis): allow TLS SNI override for IP-based REDIS_URL

When trigger.dev's hosted workers reach our ElastiCache via PrivateLink,
their REDIS_URL contains the VPCE-assigned IP, not a DNS name. Default
ioredis TLS verification fails because the ElastiCache cert is issued for
the cluster's DNS, not the IP.

Add REDIS_TLS_SERVERNAME env var; when REDIS_URL is rediss:// + IP host,
pass `tls: { servername }` to ioredis so cert hostname verification
matches against the DNS name instead. Throws at client construction if
REDIS_TLS_SERVERNAME is unset in this scenario (fail fast — no silent
TLS bypass).

No-op for in-VPC connections (DNS host), so the always-on Sim app keeps
using default verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(confluence-schemas): expose extendable bases before .superRefine

confluenceCommentScopedSchema and confluenceBlogPostScopedSchema were
built with .extend(...).superRefine(...). superRefine returns a
ZodEffects which has no .extend method, so the three downstream
.extend() calls (confluenceUpdateCommentBodySchema,
confluenceGetBlogPostBodySchema, confluenceUpdateBlogPostBodySchema)
threw at module-init time.

Next.js lazy-loads route code per-request and never executed this
top-level chain, hiding the issue. Trigger.dev's bundler eagerly
evaluates all task-reachable modules at startup, which is why the
trigger.dev deploy surfaced it as "confluenceCommentScopedSchema.extend
is not a function" across every background task that transitively
imports this file.

Fix: introduce un-superRefined base schemas and use them as the .extend
target downstream; apply superRefine after each .extend so validation
behavior is preserved for every consumer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(storage-transfer): use z.string().url() for Zod 3 compat

z.url() is Zod 4 top-level syntax. The hoisted node_modules/zod
resolves to v3.25.76 (despite apps/sim/package.json declaring 4.3.6 —
a workspace resolution conflict), so z.url is undefined at runtime.

Trigger.dev's bundler eagerly evaluates all task-reachable modules at
startup and hits this with `external_exports.url is not a function`.
Next.js dev only evaluates routes per-request so the call site never
fires.

Quick fix: revert to the chained .string().url() form which works on
both Zod 3 and Zod 4 (deprecated in 4 but still supported). The
underlying version-resolution conflict is a separate cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(mongodb-schemas): expose extendable base before .refine

mongoConnectionBodySchema was built with z.object(...).refine(...). Five
downstream schemas (mongodbQueryBodySchema, mongodbExecuteBodySchema,
mongodbInsertBodySchema, mongodbUpdateBodySchema, mongodbDeleteBodySchema)
.extend() that result, which threw at module-init in the trigger.dev
bundle (same root cause as the confluence and storage-transfer fixes:
.refine returns ZodEffects with no .extend method, and the resolved
zod is v3 even though package.json declares v4).

Fix: keep the un-refined mongoConnectionBaseSchema for downstream
.extend() targets. The pairing-validation refine isn't reattached
because the downstream extensions were never actually evaluating it
(module init threw before they could).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(redis,mongodb): address PR review comments

- redis.ts: move resolveTlsOptions call outside the try/catch in
  getRedisClient so config errors surface instead of being swallowed
  into a silent null return.
- mongodb.ts: re-attach mongoUsernamePasswordPaired .refine after each
  of the five downstream .extend()s. Mirrors the confluence pattern
  and restores the pairing constraint that the original chain dropped.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 16, 2026 11:47pm

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 16, 2026

PR Summary

Medium Risk
Touches Redis connection initialization (TLS options and new required env var in a specific configuration), which could affect connectivity in production if misconfigured. Remaining changes are validation/schema refactors but may impact request validation behavior for Confluence/MongoDB routes.

Overview
Adds support for connecting to Redis over rediss:// when REDIS_URL uses a bare IP by introducing REDIS_TLS_SERVERNAME and wiring a TLS servername (SNI) override into the ioredis client; it now throws a clear config error when this override is required but missing.

Refactors several Zod request schemas (Confluence comment/blogpost and MongoDB operations) to keep unrefined base schemas extendable and reattach refinements after .extend, preventing module-init crashes from extending ZodEffects. Also tightens fileDownloadBodySchema by explicitly requiring url to be a string before URL validation.

Reviewed by Cursor Bugbot for commit bd9e692. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 16, 2026

Greptile Summary

This PR introduces two groups of changes: (1) a TLS SNI override for ioredis when REDIS_URL targets a bare IPv4 address over rediss:// (necessary for ElastiCache via trigger.dev PrivateLink), and (2) a set of Zod schema correctness fixes where .superRefine() or .refine() was called before .extend(), making the resulting ZodEffects non-extendable.

  • redis.ts: Adds resolveTlsOptions which detects an IPv4 rediss:// URL, requires REDIS_TLS_SERVERNAME, and injects { tls: { servername } } into the ioredis constructor — thrown outside the try/catch so misconfiguration is never silently swallowed.
  • confluence.ts / mongodb.ts: Splits each refined schema into an un-refined base and a derived exported schema, then re-attaches the refinement on every .extend() call site — correctly replicating the validation without hitting the ZodEffects limitation.
  • storage-transfer.ts: Adds the missing .string() before .url() in the Zod chain, which is required in Zod v3.

Confidence Score: 4/5

Safe to merge; the Zod schema fixes and the Redis TLS addition are targeted and well-scoped. The IP-detection heuristic has two small gaps (invalid octet ranges, no IPv6 support) but neither affects the primary trigger.dev/ElastiCache use case.

The TLS SNI logic is sound for the IPv4 use case it targets, but the regex accepts technically-invalid octets and silently skips the SNI override for any IPv6-addressed rediss:// URL. Both gaps are low-risk given the specific infrastructure context, but they are real edge cases in the changed code.

apps/sim/lib/core/config/redis.ts — the IP-detection regex and the absence of IPv6 handling are worth a second look if IPv6 ElastiCache endpoints may be used in the future.

Important Files Changed

Filename Overview
apps/sim/lib/core/config/redis.ts Adds resolveTlsOptions for IPv4 IP-based rediss:// URLs; IPv6 detection is absent and the octet-range regex is loose, but the core logic for the intended ElastiCache/PrivateLink use case is correct.
apps/sim/lib/api/contracts/selectors/confluence.ts Correctly splits superRefined schemas into extendable bases and re-attaches the alphanumeric ID refinement on all derived schemas.
apps/sim/lib/api/contracts/tools/databases/mongodb.ts Correctly extracts username/password pairing into a shared predicate/error pair and re-applies it on all five operation schemas after .extend().
apps/sim/lib/api/contracts/storage-transfer.ts Adds missing .string() before .url(), which is the correct Zod v3 chain to produce a string URL validator.
apps/sim/lib/core/config/env.ts Adds REDIS_TLS_SERVERNAME as an optional string env var with a helpful inline comment explaining its purpose.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[getRedisClient called] --> B{redisUrl set?}
    B -- No --> C[return null]
    B -- Yes --> D[resolveTlsOptions]
    D --> E{protocol = rediss:?}
    E -- No --> F[tls = undefined]
    E -- Yes --> G{hostname is IPv4?}
    G -- No --> F
    G -- Yes --> H{REDIS_TLS_SERVERNAME set?}
    H -- No --> I[throw Error misconfiguration]
    H -- Yes --> J[tls = servername override]
    F --> K[new Redis url options]
    J --> K
    K --> L[spread tls if present]
    L --> M[Redis client ready]
Loading

Reviews (1): Last reviewed commit: "feat(redis): TLS SNI override for IP-bas..." | Re-trigger Greptile

Comment thread apps/sim/lib/core/config/redis.ts
Comment thread apps/sim/lib/core/config/redis.ts
@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator Author

Skipping both bot suggestions — noting reasons for the record:

  • Loose IPv4 regex (999.999.999.999 matches): only fires for hand-typed garbage values in REDIS_URL; ioredis would fail at connect time with a clearer error in that case anyway. Tightening the regex doesn't catch a real bug.
  • IPv6 not detected: ElastiCache doesn't expose IPv6 endpoints and trigger.dev's PrivateLink assigns IPv4 IPs, so the scenario isn't reachable. If/when AWS adds IPv6 ElastiCache endpoints (or TD changes connection model) we'll wire IPv6 detection — current failure mode would be a TLS cert-mismatch at handshake with a clear error.

Both are cosmetic against the actual use case. Merging as-is.

@TheodoreSpeaks TheodoreSpeaks merged commit dbe8e51 into main May 17, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant