Skip to content

feat: verified-TLS to RDS from every runtime#2761

Merged
Marfuen merged 14 commits into
mainfrom
mariano/secure-rds-tls
May 6, 2026
Merged

feat: verified-TLS to RDS from every runtime#2761
Marfuen merged 14 commits into
mainfrom
mariano/secure-rds-tls

Conversation

@Marfuen
Copy link
Copy Markdown
Contributor

@Marfuen Marfuen commented May 6, 2026

Summary

Make every Postgres connection from this monorepo use verified TLS to RDS — closes the silent-MITM exposure that came from rejectUnauthorized: false defaults.

  • Commit AWS RDS global CA bundle (public file from truststore.pki.rds.amazonaws.com, ~165KB, safe to publish — same trust model as Mozilla shipping the trusted-root list)
  • Make every Prisma client strict: throws at boot rather than silently downgrading. Inline pattern in each app's prisma/client.ts; shared resolveSslConfig helper in packages/db for that package's tests
  • Skip hostname check when CA bundle is present, keep cert-chain check. Required because RDS connections traverse an AWS NLB whose hostname isn't in the RDS Proxy cert's SANs (this is what was causing the staging error)
  • Trigger.dev: caBundleExtension ships the cert into the deploy bundle and registers NODE_EXTRA_CA_CERTS via addLayer({ deploy: { env } }) — verified end-to-end with --local-build
  • Vercel: outputFileTracingIncludes in next.config.ts for app + portal so the cert is bundled into the deployed function

Why this is safer

Before: any non-localhost connection silently used { rejectUnauthorized: false }. Encrypted but unauthenticated — an attacker with on-path access could MITM with any cert.

After:

  • Cert chain verification is enforced (an attacker would need a cert signed by AWS RDS's CA, which requires the AWS-held private key)
  • Hostname check is bypassed for the NLB hop only because we control the network path (private VPC); skipping it doesn't admit any new attacker class beyond what chain verification already excludes
  • Misconfigured environments crash loudly at boot instead of silently MITM-able

Pre-merge required (post-deploy actions in the checklist)

See docs/plans/secure-rds-tls-deploy-checklist.md:

  1. Vercel (apps/app + apps/portal): set NODE_EXTRA_CA_CERTS=/var/task/packages/db/certs/rds-global-bundle.pem on each project. If a preview throws "Refusing to connect" or "ENOENT", swap to /vercel/path0/....
  2. Trigger.dev (api + app, staging + prod): once a real task run confirms TLS works, drop the legacy opt-in:
    bunx trigger.dev@4.4.3 envvars remove PRISMA_ALLOW_INSECURE_TLS --env <env>
    
  3. API Docker (apps/api/Dockerfile.multistage): no action — already installs the bundle.

Verification done

  • packages/db tests: 8/8 pass (covers strict throw + chain-only + insecure opt-in + localhost branches)
  • apps/api/Dockerfile.multistage build: image inspected, NODE_EXTRA_CA_CERTS=/usr/local/share/aws-rds-ca-bundle.pem, file present (165408 bytes)
  • Trigger.dev --local-build for both api and app projects: extension copies cert into bundle, env var registered in manifest. App project actually deployed to staging during verification (20260506.5, deployment izoen4nv) — the hostname-fix is live there
  • Typecheck: no new errors introduced (pre-existing errors in test fixtures and a billing-module mismatch are unrelated)

Test plan

  • Run any policy task in Trigger.dev staging — the Hostname/IP does not match certificate's altnames error should be gone
  • Vercel preview deploy of apps/app and apps/portal with NODE_EXTRA_CA_CERTS set — boot logs should not contain "Refusing to connect"
  • Real Postgres queries succeed end-to-end on each runtime
  • Drop PRISMA_ALLOW_INSECURE_TLS=1 from Trigger.dev environments and confirm tasks still run

🤖 Generated with Claude Code


Summary by cubic

Enforces verified TLS for all Postgres connections to RDS across every runtime, with lazy-initialized Prisma clients so Next.js builds don’t throw. Also fixes @trycompai/db/ssl-config types to point to dist for npm consumers.

  • New Features

    • Strict TLS in Prisma clients for apps/api, apps/app, apps/portal, and apps/framework-editor. Non-local connects require NODE_EXTRA_CA_CERTS or an explicit PRISMA_ALLOW_INSECURE_TLS=1 opt-in. Hostname check is skipped for AWS NLB; chain verification stays enforced. Clients are lazy-initialized to avoid throwing during next build.
    • AWS RDS global CA bundle at packages/db/certs/rds-global-bundle.pem. Bundled in Next.js via outputFileTracingIncludes and in Trigger.dev via a caBundleExtension that copies the cert and sets NODE_EXTRA_CA_CERTS.
    • Added resolveSslConfig in @trycompai/db with tests.
  • Migration

    • Vercel (apps/app, apps/portal): set NODE_EXTRA_CA_CERTS to /var/task/packages/db/certs/rds-global-bundle.pem or /vercel/path0/packages/db/certs/rds-global-bundle.pem.
    • Trigger.dev (api, app): the extension ships the cert and sets the env var; after verifying runs, remove PRISMA_ALLOW_INSECURE_TLS.
    • API Docker: no changes needed.

Written for commit 60a4452. Summary will update on new commits.

Marfuen and others added 12 commits May 6, 2026 13:18
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move SSL-resolution logic into a pure ssl-config.ts module so it can be
tested with bun:test (matching strip-ssl-mode.test.ts's pattern) without
importing the module-level Prisma client. Drop vitest devDependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts SSL config logic into apps/app/prisma/ssl-config.ts and
updates the Prisma client to throw at boot when connecting to a
non-local database without a verified CA bundle or explicit
PRISMA_ALLOW_INSECURE_TLS=1 opt-in.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pps/app

Add `./ssl-config` subpath export to @trycompai/db so apps/app (and
upcoming portal/framework-editor) can import the single source of truth
instead of maintaining their own copy. Widen the `env` parameter type
from `NodeJS.ProcessEnv` to `Partial<NodeJS.ProcessEnv>` (strictly more
permissive) to satisfy apps/app's strict TS config. Delete the duplicate
apps/app/prisma/ssl-config.ts and its redundant test file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ships the RDS CA bundle (packages/db/certs/rds-global-bundle.pem) into
Trigger.dev task images at /app/certs/rds-global-bundle.pem and sets
NODE_EXTRA_CA_CERTS via the deploy.env layer so Node TLS initialization
picks it up before any Prisma connection attempt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…age dependency

Drop `import { resolveSslConfig } from '@trycompai/db/ssl-config'` from
apps/app, apps/portal, and apps/framework-editor and inline the full
localhost/CA-bundle/PRISMA_ALLOW_INSECURE_TLS logic directly.
Trigger.dev pins @trycompai/db@^2.0.0 from npm which lacks the
./ssl-config subpath, causing indexer crashes at deploy time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lity)

AWS NLB → RDS Proxy connections fail TLS hostname verification because the
NLB hostname (*.elb.amazonaws.com) isn't in the RDS Proxy cert's SAN list.
Cert chain verification is preserved — an attacker still cannot present a
forged or wrong-CA cert. Only the hostname-string check is relaxed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add outputFileTracingIncludes to apps/app and apps/portal next.config.ts
so the rds-global-bundle.pem is included in Vercel's traced file output
for each deployed function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents the NODE_EXTRA_CA_CERTS values to set in Vercel (both candidate
paths), the Trigger.dev PRISMA_ALLOW_INSECURE_TLS removal commands, and
notes that API Docker needs no action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

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

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment May 6, 2026 0:41am
comp-framework-editor Ready Ready Preview, Comment May 6, 2026 0:41am
portal Ready Ready Preview, Comment May 6, 2026 0:41am

Request Review

next build imports every route handler to analyze it, which previously
triggered our strict-TLS throw at module load even though no queries run.
Wrap the client in a Proxy that constructs the real PrismaClient on first
property access. The strict check still fires — just at first use, not at
import.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 17 files

Confidence score: 4/5

  • This PR is likely safe to merge, with one moderate packaging issue rather than a runtime blocker (severity 5/10, high confidence).
  • In packages/db/package.json, the new subpath export’s types field points to source instead of emitted declarations in dist, which can break TypeScript type resolution for consumers after publish.
  • Pay close attention to packages/db/package.json - ensure the subpath types target references the published dist declaration file.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/db/package.json">

<violation number="1" location="packages/db/package.json:29">
P2: Point the new subpath export's `types` entry at the emitted declaration file in `dist`; the source file is not published.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread packages/db/package.json Outdated
cubic flagged: the subpath export's types entry pointed at ./src/ssl-config.ts,
but the published package's files array only includes dist/. Downstream npm
consumers would get broken type resolution. Workspace consumers were unaffected
because @trycompai/db resolves to source via workspace:*.
@Marfuen Marfuen merged commit 2bde7ad into main May 6, 2026
11 checks passed
@Marfuen Marfuen deleted the mariano/secure-rds-tls branch May 6, 2026 12:43
Marfuen added a commit that referenced this pull request May 6, 2026
…2767)

After the verified-TLS PR (#2761) merged, two follow-ups didn't make it in:

1. Add 'certs' to packages/db/package.json files array so the RDS CA bundle
   ships with the published @trycompai/db package. Downstream consumers (e.g.
   comp-private/apps/enterprise-api) can then reference the cert at
   node_modules/@trycompai/db/certs/rds-global-bundle.pem instead of
   committing their own copy.
2. Delete the debug-tls routes (apps/app/src/app/api/_debug-tls and
   apps/app/src/app/api/debug-tls) that were merged in via auto-PRs #2762
   and #2763 but never cleaned up. They were temporary verification
   endpoints, since used to confirm the Vercel cert path.
3. Update the deploy checklist with verified-staging notes and the
   downstream consumer pattern.
claudfuen pushed a commit that referenced this pull request May 6, 2026
# [3.44.0](v3.43.1...v3.44.0) (2026-05-06)

### Bug Fixes

* **api:** correct the total number of active members from overview scores ([ed9561f](ed9561f))
* **api:** make submission endpoints accessible as an employee ([3c96a1d](3c96a1d))
* **billing:** surface wallet credits to pentest + bg-check UIs ([05d87d4](05d87d4))
* **treatment-plan:** cap linked-work lists and treatment plan body height ([8a1c46f](8a1c46f)), closes [#36](#36) [#37](#37)
* **treatment-plan:** cap linked-work lists and treatment plan body height ([46d7e83](46d7e83)), closes [#36](#36) [#37](#37)
* **upgrade:** keep self-hosted check on the page to avoid OSS regression ([e42e6ef](e42e6ef))

### Features

* **db:** ship CA bundle with @trycompai/db, clean up debug routes ([#2767](#2767)) ([84da90c](84da90c)), closes [#2761](#2761) [#2762](#2762) [#2763](#2763)
* **integration-platform:** remove code-based jumpcloud, route via DIP ([2ab5b78](2ab5b78))
* **risks:** treatment plan as first-class + vendor AI widening + matrix polish ([1a97746](1a97746)), closes [hi#confidence](https://github.com/hi/issues/confidence) [#2671](#2671) [#2](https://github.com/trycompai/comp/issues/2) [#3](#3) [#9](#9) [#4](#4) [#5](#5) [#7](#7) [#26](#26) [#6](#6) [#1](https://github.com/trycompai/comp/issues/1) [#10](#10) [#36](#36) [#35](#35) [#39](#39) [#37](#37) [#32](#32) [#33](#33) [#34](#34) [#17](#17) [#18](#18) [#19](#19) [#20](#20) [#21](#21) [#22](#22) [#30](#30) [#31](#31) [#29](#29) [#23](#23) [#40](#40) [#28](#28) [#27](#27) [#38](#38) [#24](#24) [#2671](#2671)
* **vendors:** refine inherent risk score after research lands posture data ([#2760](#2760)) ([e999c72](e999c72))
* verified-TLS to RDS from every runtime ([#2761](#2761)) ([2bde7ad](2bde7ad))
@claudfuen
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.44.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants