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
5 changes: 5 additions & 0 deletions .changeset/extra-ca-certs-deploy.md
Original file line number Diff line number Diff line change
@@ -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.

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.

🟡 Release note exposes build internals

The extraCACerts changeset describes build extensions and arguments instead of leading with the user-visible trust fix. Customers receive this implementation detail verbatim in the changelog.

Suggested change
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.
Deployed tasks now trust custom CA certificates configured with `extraCACerts`.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

6 changes: 5 additions & 1 deletion packages/cli-v3/src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
),
Comment on lines +779 to +782

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.

🔴 Native deployments ignore configured CA certificates

For native or local-bundle deployments, extraCACerts copies the certificate but never sets its image path. Both paths return before buildImage, leaving NODE_EXTRA_CA_CERTS empty in deployed tasks.

Learn more

The CLI selects its build path before this call. handleNativeBuildServerDeploy uploads source for a server-side build, while handleLocalBundleDeploy uploads the already bundled output. Both return without calling buildImage, so the new build argument only reaches the Depot and local Docker paths. The certificate is included by the new extension, but the generated Containerfile still sets NODE_EXTRA_CA_CERTS from an unset build argument.

Example: A project with extraCACerts: "./certs/corp.pem" and the native build path uploads corp.pem. Its deployed tasks start with NODE_EXTRA_CA_CERTS= rather than /app/certs/corp.pem.

Recommended fix: Carry the resolved CA image path through both native deployment requests and set NODE_EXTRA_CA_CERTS in their image-build arguments. Preserve the path through the local-bundle request and verify both build paths with an actual certificate.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

apiUrl: projectClient.client.apiURL,
apiKey: projectClient.client.accessToken!,
apiClient: projectClient.client,
Expand Down
8 changes: 8 additions & 0 deletions packages/cli-v3/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,14 @@ async function autoDetectDirs(workingDir: string): Promise<string[]> {
}

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(
Expand Down
21 changes: 21 additions & 0 deletions packages/cli-v3/src/deploy/buildImage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
TOOLCHAIN_PACKAGES,
formatCommandForDiagnostics,
generateContainerfile,
resolveExtraCACertsImagePath,
} from "./buildImage.js";
import { EMSIGN_COMPROMISE_REJECTED_INTERMEDIATES } from "./emsignRejectedIntermediates.js";

Expand Down Expand Up @@ -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({
Expand Down
24 changes: 23 additions & 1 deletion packages/cli-v3/src/deploy/buildImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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("/")}`;
Comment on lines +1324 to +1328

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.

🟡 Parent segments misroute deployed CA files

With extraCACerts: "./certs/../root.pem", resolveExtraCACertsImagePath returns /app/certs/root.pem. The file copier normalizes that path and places the certificate at /app/root.pem, so tasks cannot load it.

Learn more

The certificate copier findFilesByMatchers resolves the matched file and then calculates its destination relative to the project root. This resolves an internal .. segment before copying. The new resolver simply removes .., leaving the preceding folder in the image path.

Example: With a project root of /project and extraCACerts: "./certs/../root.pem", the copier writes /app/root.pem, while NODE_EXTRA_CA_CERTS points at /app/certs/root.pem.

Recommended fix: Normalize the relative input against workingDir with resolve, derive its project-relative destination as the copier does, then strip only leading parent segments.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

}