@@ -100,6 +100,24 @@ export interface HttpRequestOptions {
100100 * ```
101101 */
102102 body ?: Buffer | string | undefined
103+ /**
104+ * Custom CA certificates for TLS connections.
105+ * When provided, these certificates are combined with the default trust
106+ * store via an HTTPS agent. Useful when SSL_CERT_FILE is set but
107+ * NODE_EXTRA_CA_CERTS was not available at process startup.
108+ *
109+ * @example
110+ * ```ts
111+ * import { rootCertificates } from 'node:tls'
112+ * import { readFileSync } from 'node:fs'
113+ *
114+ * const extraCerts = readFileSync('/path/to/cert.pem', 'utf-8')
115+ * await httpRequest('https://api.example.com', {
116+ * ca: [...rootCertificates, extraCerts]
117+ * })
118+ * ```
119+ */
120+ ca ?: string [ ] | undefined
103121 /**
104122 * Whether to automatically follow HTTP redirects (3xx status codes).
105123 *
@@ -334,6 +352,12 @@ export interface HttpResponse {
334352 * Configuration options for file downloads.
335353 */
336354export interface HttpDownloadOptions {
355+ /**
356+ * Custom CA certificates for TLS connections.
357+ * When provided, these certificates are used for the download request.
358+ * See `HttpRequestOptions.ca` for details.
359+ */
360+ ca ?: string [ ] | undefined
337361 /**
338362 * Whether to automatically follow HTTP redirects (3xx status codes).
339363 * This is essential for downloading from services that use CDN redirects,
@@ -608,6 +632,11 @@ export function parseChecksums(text: string): Checksums {
608632 * Options for fetching checksums from a URL.
609633 */
610634export interface FetchChecksumsOptions {
635+ /**
636+ * Custom CA certificates for TLS connections.
637+ * See `HttpRequestOptions.ca` for details.
638+ */
639+ ca ?: string [ ] | undefined
611640 /**
612641 * HTTP headers to send with the request.
613642 */
@@ -649,12 +678,16 @@ export async function fetchChecksums(
649678 url : string ,
650679 options ?: FetchChecksumsOptions | undefined ,
651680) : Promise < Checksums > {
652- const { headers = { } , timeout = 30_000 } = {
681+ const {
682+ ca,
683+ headers = { } ,
684+ timeout = 30_000 ,
685+ } = {
653686 __proto__ : null ,
654687 ...options ,
655688 } as FetchChecksumsOptions
656689
657- const response = await httpRequest ( url , { headers, timeout } )
690+ const response = await httpRequest ( url , { ca , headers, timeout } )
658691
659692 if ( ! response . ok ) {
660693 throw new Error (
@@ -675,6 +708,7 @@ async function httpDownloadAttempt(
675708 options : HttpDownloadOptions ,
676709) : Promise < HttpDownloadResult > {
677710 const {
711+ ca,
678712 followRedirects = true ,
679713 headers = { } ,
680714 maxRedirects = 5 ,
@@ -687,7 +721,7 @@ async function httpDownloadAttempt(
687721 const isHttps = parsedUrl . protocol === 'https:'
688722 const httpModule = isHttps ? getHttps ( ) : getHttp ( )
689723
690- const requestOptions = {
724+ const requestOptions : Record < string , unknown > = {
691725 headers : {
692726 'User-Agent' : 'socket-registry/1.0' ,
693727 ...headers ,
@@ -699,6 +733,11 @@ async function httpDownloadAttempt(
699733 timeout,
700734 }
701735
736+ // Pass custom CA certificates for TLS connections.
737+ if ( ca && isHttps ) {
738+ requestOptions [ 'ca' ] = ca
739+ }
740+
702741 const { createWriteStream } = getFs ( )
703742
704743 let fileStream : ReturnType < typeof createWriteStream > | undefined
@@ -739,6 +778,7 @@ async function httpDownloadAttempt(
739778
740779 resolve (
741780 httpDownloadAttempt ( redirectUrl , destPath , {
781+ ca,
742782 followRedirects,
743783 headers,
744784 maxRedirects : maxRedirects - 1 ,
@@ -850,6 +890,7 @@ async function httpRequestAttempt(
850890) : Promise < HttpResponse > {
851891 const {
852892 body,
893+ ca,
853894 followRedirects = true ,
854895 headers = { } ,
855896 maxRedirects = 5 ,
@@ -862,7 +903,7 @@ async function httpRequestAttempt(
862903 const isHttps = parsedUrl . protocol === 'https:'
863904 const httpModule = isHttps ? getHttps ( ) : getHttp ( )
864905
865- const requestOptions = {
906+ const requestOptions : Record < string , unknown > = {
866907 headers : {
867908 'User-Agent' : 'socket-registry/1.0' ,
868909 ...headers ,
@@ -874,6 +915,11 @@ async function httpRequestAttempt(
874915 timeout,
875916 }
876917
918+ // Pass custom CA certificates for TLS connections.
919+ if ( ca && isHttps ) {
920+ requestOptions [ 'ca' ] = ca
921+ }
922+
877923 /* c8 ignore start - External HTTP/HTTPS request */
878924 const request = httpModule . request (
879925 requestOptions ,
@@ -903,6 +949,7 @@ async function httpRequestAttempt(
903949 resolve (
904950 httpRequestAttempt ( redirectUrl , {
905951 body,
952+ ca,
906953 followRedirects,
907954 headers,
908955 maxRedirects : maxRedirects - 1 ,
@@ -1055,6 +1102,7 @@ export async function httpDownload(
10551102 options ?: HttpDownloadOptions | undefined ,
10561103) : Promise < HttpDownloadResult > {
10571104 const {
1105+ ca,
10581106 followRedirects = true ,
10591107 headers = { } ,
10601108 logger,
@@ -1103,6 +1151,7 @@ export async function httpDownload(
11031151 try {
11041152 // eslint-disable-next-line no-await-in-loop
11051153 const result = await httpDownloadAttempt ( url , tempPath , {
1154+ ca,
11061155 followRedirects,
11071156 headers,
11081157 maxRedirects,
@@ -1296,6 +1345,7 @@ export async function httpRequest(
12961345) : Promise < HttpResponse > {
12971346 const {
12981347 body,
1348+ ca,
12991349 followRedirects = true ,
13001350 headers = { } ,
13011351 maxRedirects = 5 ,
@@ -1312,6 +1362,7 @@ export async function httpRequest(
13121362 // eslint-disable-next-line no-await-in-loop
13131363 return await httpRequestAttempt ( url , {
13141364 body,
1365+ ca,
13151366 followRedirects,
13161367 headers,
13171368 maxRedirects,
0 commit comments