From 4f03f71674c3b1c2be9363999cc493794eb3870a Mon Sep 17 00:00:00 2001 From: Prince Jain Date: Fri, 23 Oct 2026 00:19:19 +0530 Subject: [PATCH] fix(cli): apply extraCACerts on deploy resolvedConfig.extraCACerts was never passed to buildImage(), so the generated Containerfile set NODE_EXTRA_CA_CERTS to an empty value and Node ignored the configured CA bundle at runtime. The CA file is now copied into the build output via the additionalFiles extension, and the deploy command passes its image path (resolved the same way additionalFiles maps files under /app) as the NODE_EXTRA_CA_CERTS build arg. Fixes #5028 --- .changeset/extra-ca-certs-deploy.md | 5 ++++ packages/cli-v3/src/commands/deploy.ts | 6 ++++- packages/cli-v3/src/config.ts | 8 +++++++ packages/cli-v3/src/deploy/buildImage.test.ts | 21 ++++++++++++++++ packages/cli-v3/src/deploy/buildImage.ts | 24 ++++++++++++++++++- 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 .changeset/extra-ca-certs-deploy.md diff --git a/.changeset/extra-ca-certs-deploy.md b/.changeset/extra-ca-certs-deploy.md new file mode 100644 index 00000000000..f86854b078a --- /dev/null +++ b/.changeset/extra-ca-certs-deploy.md @@ -0,0 +1,5 @@ +--- +"trigger.dev": patch +--- + +Fix `extraCACerts` in `trigger.config.ts` having no effect on deployed tasks. The configured CA file is now copied into the deployment image via the `additionalFiles` build extension, and the deploy command passes its image path to the `NODE_EXTRA_CA_CERTS` build argument, so self-signed or corporate certificates are trusted at runtime. diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 02f1a0bfcc4..987c042e564 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -50,7 +50,7 @@ import { } from "../cli/common.js"; import { loadConfig } from "../config.js"; import { authenticateForDeploy, userIdForDeploy } from "../deploy/auth.js"; -import { buildImage } from "../deploy/buildImage.js"; +import { buildImage, resolveExtraCACertsImagePath } from "../deploy/buildImage.js"; import { type DeployEnvironment, resolveDeployEnvironment } from "../deploy/environment.js"; import { checkLogsForErrors, @@ -776,6 +776,10 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { externalBuildProjectId: deployment.externalBuildData?.projectId, projectId: projectClient.id, projectRef: resolvedConfig.project, + extraCACerts: resolveExtraCACertsImagePath( + resolvedConfig.extraCACerts, + resolvedConfig.workingDir + ), apiUrl: projectClient.client.apiURL, apiKey: projectClient.client.accessToken!, apiClient: projectClient.client, diff --git a/packages/cli-v3/src/config.ts b/packages/cli-v3/src/config.ts index a34138cd4c1..eb098e84043 100644 --- a/packages/cli-v3/src/config.ts +++ b/packages/cli-v3/src/config.ts @@ -301,6 +301,14 @@ async function autoDetectDirs(workingDir: string): Promise { } function validateConfig(config: TriggerConfig, warn = true) { + if (config.extraCACerts) { + // The CA file must be inside the build output so the image can point + // NODE_EXTRA_CA_CERTS at it + config.build ??= {}; + config.build.extensions ??= []; + config.build.extensions.push(additionalFiles({ files: [config.extraCACerts] })); + } + if (config.additionalFiles && config.additionalFiles.length > 0) { warn && prettyWarning( diff --git a/packages/cli-v3/src/deploy/buildImage.test.ts b/packages/cli-v3/src/deploy/buildImage.test.ts index a136c4c95a7..7c57a9e2b38 100644 --- a/packages/cli-v3/src/deploy/buildImage.test.ts +++ b/packages/cli-v3/src/deploy/buildImage.test.ts @@ -12,6 +12,7 @@ import { TOOLCHAIN_PACKAGES, formatCommandForDiagnostics, generateContainerfile, + resolveExtraCACertsImagePath, } from "./buildImage.js"; import { EMSIGN_COMPROMISE_REJECTED_INTERMEDIATES } from "./emsignRejectedIntermediates.js"; @@ -56,6 +57,26 @@ const images: Array<[BuildRuntime, string, string]> = [ ], ]; +describe("resolveExtraCACertsImagePath", () => { + it("returns undefined when no CA file is configured", () => { + expect(resolveExtraCACertsImagePath(undefined, "/project")).toBeUndefined(); + }); + + it("maps a project-relative path into /app", () => { + expect(resolveExtraCACertsImagePath("./certs/ca.crt", "/project")).toBe("/app/certs/ca.crt"); + }); + + it("strips leading parent segments the same way additionalFiles does", () => { + expect(resolveExtraCACertsImagePath("../shared/ca.crt", "/project")).toBe("/app/shared/ca.crt"); + }); + + it("maps an absolute path inside the project into /app", () => { + expect(resolveExtraCACertsImagePath("/project/certs/ca.crt", "/project")).toBe( + "/app/certs/ca.crt" + ); + }); +}); + describe("generateContainerfile", () => { it.each(images)("uses the pinned published base images for %s", async (runtime, base, build) => { const containerfile = await generateContainerfile({ diff --git a/packages/cli-v3/src/deploy/buildImage.ts b/packages/cli-v3/src/deploy/buildImage.ts index c7f300b5060..061f535c91e 100644 --- a/packages/cli-v3/src/deploy/buildImage.ts +++ b/packages/cli-v3/src/deploy/buildImage.ts @@ -3,7 +3,7 @@ import { depot } from "@depot/cli"; import { x } from "tinyexec"; import type { BuildManifest, BuildRuntime } from "@trigger.dev/core/v3/schemas"; import { networkInterfaces } from "os"; -import { join } from "path"; +import { isAbsolute, join, relative } from "path"; import { safeReadJSONFile } from "../utilities/fileSystem.js"; import { readFileSync } from "fs"; @@ -1305,3 +1305,25 @@ function getOutputOptions({ return outputOptions; } + +// The extraCACerts config option is project-relative, but NODE_EXTRA_CA_CERTS +// must point at the file inside the deployed image. additionalFiles copies it +// into the build output root, which becomes /app in the image. +export function resolveExtraCACertsImagePath( + extraCACerts: string | undefined, + workingDir: string +): string | undefined { + if (!extraCACerts) { + return undefined; + } + + const projectRelativePath = isAbsolute(extraCACerts) + ? relative(workingDir, extraCACerts) + : extraCACerts; + + const segments = projectRelativePath + .split(/[\\/]+/) + .filter((segment) => segment && segment !== "." && segment !== ".."); + + return `/app/${segments.join("/")}`; +}