Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions apps/api/prisma/client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import type { PeerCertificate } from 'node:tls';

const globalForPrisma = global as unknown as { prisma?: PrismaClient };

const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);

const isRdsHostname = (n: string): boolean =>
n.endsWith('.rds.amazonaws.com') || n.endsWith('.rds.amazonaws.com.cn');

// Connections traverse an AWS NLB → RDS Proxy. The cert presented is the
// Proxy's, whose SAN list contains the proxy hostname but NOT the NLB hostname
// we dialed. Default hostname check fails. Instead of disabling identity
// verification entirely, assert the cert is for an AWS RDS endpoint.
function rdsServerIdentity(_host: string, cert: PeerCertificate): Error | undefined {
const sans = (cert.subjectaltname ?? '')
.split(',')
.map((s) => s.trim().replace(/^DNS:/, ''));
const cn = (cert.subject as { CN?: string } | undefined)?.CN ?? '';
if (isRdsHostname(cn) || sans.some(isRdsHostname)) return undefined;

@cubic-dev-ai cubic-dev-ai Bot May 6, 2026 •

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.

P2: The custom checkServerIdentity is too permissive: it accepts any AWS RDS certificate and does not bind certificate identity to the intended endpoint host.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/prisma/client.ts, line 21:

<comment>The custom `checkServerIdentity` is too permissive: it accepts any AWS RDS certificate and does not bind certificate identity to the intended endpoint host.</comment>

<file context>
@@ -1,10 +1,29 @@
+    .split(',')
+    .map((s) => s.trim().replace(/^DNS:/, ''));
+  const cn = (cert.subject as { CN?: string } | undefined)?.CN ?? '';
+  if (isRdsHostname(cn) || sans.some(isRdsHostname)) return undefined;
+  return new Error(
+    `TLS hostname check: cert is not for an AWS RDS endpoint (CN=${cn}, SANs=${sans.join(',')})`,
</file context>
Fix with Cubic

return new Error(
`TLS hostname check: cert is not for an AWS RDS endpoint (CN=${cn}, SANs=${sans.join(',')})`,
);
}

function stripSslMode(connectionString: string): string {
const url = new URL(connectionString);
url.searchParams.delete('sslmode');
Expand Down Expand Up @@ -42,16 +61,17 @@ function createPrismaClient(): PrismaClient {
const allowInsecure = process.env.PRISMA_ALLOW_INSECURE_TLS === '1';
let ssl:
| undefined
| { checkServerIdentity: () => undefined }
| { checkServerIdentity: (host: string, cert: PeerCertificate) => Error | undefined }
| { rejectUnauthorized: false };
if (isLocalhost) {
ssl = undefined;
} else if (hasCABundle) {
// Verified TLS: rely on Node's TLS context (NODE_EXTRA_CA_CERTS adds the AWS
// RDS CA to the trust store). Skip hostname check because connections may
// traverse an AWS NLB whose hostname isn't in the RDS Proxy cert's SAN list.
// The chain check still rejects forged or wrong-CA certs.
ssl = { checkServerIdentity: () => undefined };
// RDS CA to the trust store). Replace the default hostname check with one
// that asserts the cert is for an AWS RDS endpoint, since the NLB hostname
// we dial isn't in the RDS Proxy cert's SAN list. The chain check still
// rejects forged or wrong-CA certs.
ssl = { checkServerIdentity: rdsServerIdentity };
} else if (allowInsecure) {
ssl = { rejectUnauthorized: false };
} else {
Expand Down
37 changes: 31 additions & 6 deletions apps/app/prisma/client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import { type PeerCertificate, rootCertificates } from 'node:tls';

import { RDS_CA_BUNDLE } from './rds-ca-bundle';

const globalForPrisma = global as unknown as { prisma?: PrismaClient };

const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);

// `ssl.ca` *replaces* Node's trust store rather than augmenting it. Our
// RDS bundle only contains regional RDS CAs; AWS RDS Proxy chains terminate
// at Amazon Root CA 1, which lives in Node's default Mozilla bundle.
const COMBINED_CA = [RDS_CA_BUNDLE, ...rootCertificates];

const isRdsHostname = (n: string): boolean =>
n.endsWith('.rds.amazonaws.com') || n.endsWith('.rds.amazonaws.com.cn');

// Connections traverse an AWS NLB → RDS Proxy. The cert presented is the
// Proxy's, whose SAN list contains the proxy hostname but NOT the NLB
// hostname we dialed. Default hostname check fails. Instead of disabling
// identity verification entirely, assert the cert is for an AWS RDS
// endpoint — preserves protection against cert substitution while
// accepting the NLB hostname mismatch.
function rdsServerIdentity(_host: string, cert: PeerCertificate): Error | undefined {
const sans = (cert.subjectaltname ?? '')
.split(',')
.map((s) => s.trim().replace(/^DNS:/, ''));
const cn = (cert.subject as { CN?: string } | undefined)?.CN ?? '';
if (isRdsHostname(cn) || sans.some(isRdsHostname)) return undefined;
return new Error(
`TLS hostname check: cert is not for an AWS RDS endpoint (CN=${cn}, SANs=${sans.join(',')})`,
);
}

function stripSslMode(connectionString: string): string {
const url = new URL(connectionString);
url.searchParams.delete('sslmode');
Expand All @@ -30,18 +56,17 @@ function createPrismaClient(): PrismaClient {

let ssl:
| undefined
| { ca: string; checkServerIdentity: () => undefined }
| {
ca: string[];
checkServerIdentity: (host: string, cert: PeerCertificate) => Error | undefined;
}
| { rejectUnauthorized: false };
if (isLocalhost) {
ssl = undefined;
} else if (allowInsecure) {
ssl = { rejectUnauthorized: false };
} else {
// Verified TLS using the inlined AWS RDS CA bundle. Skip hostname check
// because connections may traverse an AWS NLB whose hostname isn't in the
// RDS Proxy cert's SAN list. The chain check still rejects forged or
// wrong-CA certs.
ssl = { ca: RDS_CA_BUNDLE, checkServerIdentity: () => undefined };
ssl = { ca: COMBINED_CA, checkServerIdentity: rdsServerIdentity };
}

const url = ssl !== undefined ? stripSslMode(rawUrl) : rawUrl;
Expand Down
24 changes: 22 additions & 2 deletions apps/framework-editor/prisma/client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import { type PeerCertificate, rootCertificates } from 'node:tls';

import { RDS_CA_BUNDLE } from './rds-ca-bundle';

const globalForPrisma = global as unknown as { prisma?: PrismaClient };

const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);

const COMBINED_CA = [RDS_CA_BUNDLE, ...rootCertificates];

const isRdsHostname = (n: string): boolean =>
n.endsWith('.rds.amazonaws.com') || n.endsWith('.rds.amazonaws.com.cn');

function rdsServerIdentity(_host: string, cert: PeerCertificate): Error | undefined {
const sans = (cert.subjectaltname ?? '')
.split(',')
.map((s) => s.trim().replace(/^DNS:/, ''));
const cn = (cert.subject as { CN?: string } | undefined)?.CN ?? '';
if (isRdsHostname(cn) || sans.some(isRdsHostname)) return undefined;
return new Error(
`TLS hostname check: cert is not for an AWS RDS endpoint (CN=${cn}, SANs=${sans.join(',')})`,
);
}

function stripSslMode(connectionString: string): string {
const url = new URL(connectionString);
url.searchParams.delete('sslmode');
Expand All @@ -30,14 +47,17 @@ function createPrismaClient(): PrismaClient {

let ssl:
| undefined
| { ca: string; checkServerIdentity: () => undefined }
| {
ca: string[];
checkServerIdentity: (host: string, cert: PeerCertificate) => Error | undefined;
}
| { rejectUnauthorized: false };
if (isLocalhost) {
ssl = undefined;
} else if (allowInsecure) {
ssl = { rejectUnauthorized: false };
} else {
ssl = { ca: RDS_CA_BUNDLE, checkServerIdentity: () => undefined };
ssl = { ca: COMBINED_CA, checkServerIdentity: rdsServerIdentity };
}

const url = ssl !== undefined ? stripSslMode(rawUrl) : rawUrl;
Expand Down
24 changes: 22 additions & 2 deletions apps/portal/prisma/client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
import { PrismaClient } from '../src/generated/prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import { type PeerCertificate, rootCertificates } from 'node:tls';

import { RDS_CA_BUNDLE } from './rds-ca-bundle';

const globalForPrisma = global as unknown as { prisma?: PrismaClient };

const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);

const COMBINED_CA = [RDS_CA_BUNDLE, ...rootCertificates];

const isRdsHostname = (n: string): boolean =>
n.endsWith('.rds.amazonaws.com') || n.endsWith('.rds.amazonaws.com.cn');

function rdsServerIdentity(_host: string, cert: PeerCertificate): Error | undefined {
const sans = (cert.subjectaltname ?? '')
.split(',')
.map((s) => s.trim().replace(/^DNS:/, ''));
const cn = (cert.subject as { CN?: string } | undefined)?.CN ?? '';
if (isRdsHostname(cn) || sans.some(isRdsHostname)) return undefined;
return new Error(
`TLS hostname check: cert is not for an AWS RDS endpoint (CN=${cn}, SANs=${sans.join(',')})`,
);
}

function stripSslMode(connectionString: string): string {
const url = new URL(connectionString);
url.searchParams.delete('sslmode');
Expand All @@ -30,14 +47,17 @@ function createPrismaClient(): PrismaClient {

let ssl:
| undefined
| { ca: string; checkServerIdentity: () => undefined }
| {
ca: string[];
checkServerIdentity: (host: string, cert: PeerCertificate) => Error | undefined;
}
| { rejectUnauthorized: false };
if (isLocalhost) {
ssl = undefined;
} else if (allowInsecure) {
ssl = { rejectUnauthorized: false };
} else {
ssl = { ca: RDS_CA_BUNDLE, checkServerIdentity: () => undefined };
ssl = { ca: COMBINED_CA, checkServerIdentity: rdsServerIdentity };
}

const url = ssl !== undefined ? stripSslMode(rawUrl) : rawUrl;
Expand Down
2 changes: 1 addition & 1 deletion packages/db/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@trycompai/db",
"description": "Database package with Prisma client and schema for Comp AI",
"version": "2.2.0",
"version": "2.2.1",
"dependencies": {
"@prisma/adapter-pg": "7.6.0",
"@prisma/client": "7.6.0",
Expand Down
67 changes: 46 additions & 21 deletions packages/db/src/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, it, expect } from 'bun:test';
import { resolveSslConfig } from './ssl-config';
import { resolveSslConfig, rdsServerIdentity } from './ssl-config';
import type { PeerCertificate } from 'node:tls';

describe('resolveSslConfig', () => {
it('returns undefined for localhost', () => {
Expand All @@ -14,14 +15,14 @@ describe('resolveSslConfig', () => {
expect(resolveSslConfig('postgresql://u:p@[::1]:5432/x', {})).toBeUndefined();
});

it('returns checkServerIdentity-noop when NODE_EXTRA_CA_CERTS is set', () => {
const result = resolveSslConfig('postgresql://u:p@db.prod.example.com:5432/x', {
NODE_EXTRA_CA_CERTS: '/etc/ssl/certs/ca-certificates.crt',
});
it('returns combined-CA + custom rdsServerIdentity for remote URLs', () => {
const result = resolveSslConfig('postgresql://u:p@db.prod.example.com:5432/x', {});
expect(result).toBeDefined();
expect(typeof (result as { checkServerIdentity: unknown }).checkServerIdentity).toBe('function');
// The function returns undefined (no error → identity accepted)
expect((result as { checkServerIdentity: () => undefined }).checkServerIdentity()).toBeUndefined();
if (!result || !('ca' in result)) throw new Error('expected ca branch');
expect(Array.isArray(result.ca)).toBe(true);
// Combined trust includes our pinned RDS bundle plus Node's defaults.
expect((result.ca as string[]).length).toBeGreaterThan(1);
expect(typeof result.checkServerIdentity).toBe('function');
});

it('returns rejectUnauthorized:false when PRISMA_ALLOW_INSECURE_TLS=1', () => {
Expand All @@ -32,22 +33,46 @@ describe('resolveSslConfig', () => {
).toEqual({ rejectUnauthorized: false });
});

it('throws on remote URL with neither env var set', () => {
expect(() => resolveSslConfig('postgresql://u:p@db.prod.example.com:5432/x', {})).toThrow(
/Refusing to connect/,
);
it('treats malformed URLs as remote (defensive)', () => {
const result = resolveSslConfig('not-a-valid-url', {});
expect(result).toBeDefined();
expect((result as { ca: unknown }).ca).toBeDefined();
});
});

it('treats malformed URLs as remote (defensive)', () => {
expect(() => resolveSslConfig('not-a-valid-url', {})).toThrow(/Refusing to connect/);
describe('rdsServerIdentity', () => {
const make = (cn: string, sans: string[] = []): PeerCertificate =>
({
subject: { CN: cn },
subjectaltname: sans.map((s) => `DNS:${s}`).join(', '),
}) as unknown as PeerCertificate;

it('accepts a cert whose CN is an RDS endpoint', () => {
const cert = make('my-proxy.proxy-abc.us-east-1.rds.amazonaws.com');
expect(rdsServerIdentity('any-host.example.com', cert)).toBeUndefined();
});

it('prefers verified TLS over insecure opt-in when both are set', () => {
const result = resolveSslConfig('postgresql://u:p@db.prod.example.com:5432/x', {
NODE_EXTRA_CA_CERTS: '/etc/ssl/certs/ca-certificates.crt',
PRISMA_ALLOW_INSECURE_TLS: '1',
});
expect(result).toBeDefined();
expect(typeof (result as { checkServerIdentity: unknown }).checkServerIdentity).toBe('function');
it('accepts a cert whose SAN includes an RDS endpoint', () => {
const cert = make('something.example.com', [
'my-proxy.proxy-abc.us-east-1.rds.amazonaws.com',
]);
expect(rdsServerIdentity('any-host.example.com', cert)).toBeUndefined();
});

it('accepts the .cn region suffix', () => {
const cert = make('proxy.proxy-abc.cn-north-1.rds.amazonaws.com.cn');
expect(rdsServerIdentity('any-host.example.com', cert)).toBeUndefined();
});

it('rejects a cert that is not for an RDS endpoint', () => {
const cert = make('attacker.example.com', ['evil.example.com']);
const err = rdsServerIdentity('any-host.example.com', cert);
expect(err).toBeInstanceOf(Error);
expect(err?.message).toMatch(/not for an AWS RDS endpoint/);
});

it('rejects when CN/SAN are empty', () => {
const cert = make('', []);
expect(rdsServerIdentity('any-host.example.com', cert)).toBeInstanceOf(Error);
});
});
42 changes: 36 additions & 6 deletions packages/db/src/ssl-config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { type PeerCertificate, rootCertificates } from 'node:tls';
import { RDS_CA_BUNDLE } from './rds-ca-bundle';

export type SslConfig =
| undefined
| { ca: string; checkServerIdentity: () => undefined }
| {
ca: string | string[];
checkServerIdentity: (host: string, cert: PeerCertificate) => Error | undefined;
}
| { rejectUnauthorized: false };

const LOCAL_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1']);
Expand All @@ -19,15 +23,41 @@ function isLocalhostUrl(connectionString: string): boolean {
}
}

// `ssl.ca` *replaces* Node's trust store rather than augmenting it. Our
// `rds-global-bundle.pem` only contains the 108 RDS-specific regional CAs;
// AWS RDS Proxy chains terminate at Amazon Root CA 1, which lives in Node's
// default Mozilla bundle. Combine so both direct-instance and Proxy paths
// validate.
const COMBINED_CA = [RDS_CA_BUNDLE, ...rootCertificates];

const isRdsHostname = (n: string): boolean =>
n.endsWith('.rds.amazonaws.com') || n.endsWith('.rds.amazonaws.com.cn');

// We connect via an AWS NLB (TCP passthrough) → RDS Proxy. The cert presented
// is the Proxy's, whose SAN list contains the proxy hostname (e.g.
// `*.proxy-XXX.us-east-1.rds.amazonaws.com`) but NOT the NLB hostname
// (`*.elb.amazonaws.com`) we dialed. Default hostname check fails. Instead of
// disabling identity verification entirely (which would let an attacker
// substitute a chain-valid cert for any host), assert the cert is for an AWS
// RDS endpoint. Combined with the pinned trust store + chain validation, an
// attacker would need a forged or wrong-CA cert for an RDS hostname, both of
// which still fail.
export function rdsServerIdentity(_host: string, cert: PeerCertificate): Error | undefined {
const sans = (cert.subjectaltname ?? '')
.split(',')
.map((s) => s.trim().replace(/^DNS:/, ''));
const cn = (cert.subject as { CN?: string } | undefined)?.CN ?? '';
if (isRdsHostname(cn) || sans.some(isRdsHostname)) return undefined;
return new Error(
`TLS hostname check: cert is not for an AWS RDS endpoint (CN=${cn}, SANs=${sans.join(',')})`,
);
}

export function resolveSslConfig(
databaseUrl: string,
env: Partial<NodeJS.ProcessEnv> = process.env,
): SslConfig {
if (isLocalhostUrl(databaseUrl)) return undefined;
if (env.PRISMA_ALLOW_INSECURE_TLS === '1') return { rejectUnauthorized: false };
// Verified TLS using the inlined AWS RDS CA bundle. Skip the hostname check
// because connections may traverse an AWS NLB whose hostname isn't in the
// RDS Proxy cert's SAN list. The chain check still rejects forged or
// wrong-CA certs.
return { ca: RDS_CA_BUNDLE, checkServerIdentity: () => undefined };
return { ca: COMBINED_CA, checkServerIdentity: rdsServerIdentity };
}
Loading