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
13 changes: 4 additions & 9 deletions apps/webapp/app/components/RuntimeIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,10 @@ export function RuntimeIcon({
}: RuntimeIconProps) {
const parsedRuntime = parseRuntime(runtime);

// Default to Node.js if no runtime is specified
const effectiveRuntime = parsedRuntime || {
runtime: "node" as const,
originalRuntime: "node",
displayName: "Node.js",
};

const icon = getIcon(effectiveRuntime.runtime, className);
const formattedText = formatRuntimeWithVersion(effectiveRuntime.originalRuntime, runtimeVersion);
const icon = parsedRuntime ? getIcon(parsedRuntime.runtime, className) : <span className="text-text-dimmed">–</span>;
const formattedText = parsedRuntime
? formatRuntimeWithVersion(parsedRuntime.originalRuntime, runtimeVersion)
: "Unknown";

if (withLabel) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@ export async function action({ request }: ActionFunctionArgs) {
}

function createRunReplicationService(params: CreateRunReplicationServiceParams) {
const url = new URL(env.RUN_REPLICATION_CLICKHOUSE_URL);
// Remove secure param to prevent Unknown URL parameters error
url.searchParams.delete("secure");

const clickhouse = new ClickHouse({
url: env.RUN_REPLICATION_CLICKHOUSE_URL,
url: url.toString(),
name: params.name,
keepAlive: {
enabled: params.keepAliveEnabled,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ export function ConnectGitHubRepoModal({
<TextLink
target="_blank"
rel="noreferrer noopener"
to={`https://github.com/settings/installations/${selectedInstallation?.appInstallationId}`}
to={`https://github.com/apps/trigger-dev-app/installations/${selectedInstallation?.appInstallationId}`}
>
GitHub
</TextLink>
Expand Down Expand Up @@ -632,9 +632,9 @@ export function ConnectedGitHubRepoForm({
useEffect(() => {
const hasChanges =
gitSettingsValues.productionBranch !==
(connectedGitHubRepo.branchTracking?.prod?.branch || "") ||
(connectedGitHubRepo.branchTracking?.prod?.branch || "") ||
gitSettingsValues.stagingBranch !==
(connectedGitHubRepo.branchTracking?.staging?.branch || "") ||
(connectedGitHubRepo.branchTracking?.staging?.branch || "") ||
gitSettingsValues.previewDeploymentsEnabled !== connectedGitHubRepo.previewDeploymentsEnabled;
setHasGitSettingsChanges(hasChanges);
}, [gitSettingsValues, connectedGitHubRepo]);
Expand Down Expand Up @@ -898,6 +898,6 @@ export function GitHubSettingsPanel({
</Hint>
)}
</div>

);
}
3 changes: 3 additions & 0 deletions apps/webapp/app/services/clickhouseInstance.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ function initializeQueryClickhouseClient() {

const url = new URL(env.QUERY_CLICKHOUSE_URL);

// Remove secure param
url.searchParams.delete("secure");

return new ClickHouse({
url: url.toString(),
name: "query-clickhouse",
Expand Down
1 change: 1 addition & 0 deletions apps/webapp/app/services/emailAuth.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const emailStrategy = new EmailLinkStrategy(
secret,
callbackURL: "/magic",
sessionMagicLinkKey: "triggerdotdev:magiclink",
validateSession: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

🚩 validateSession: false reduces magic link security

Adding validateSession: false at apps/webapp/app/services/emailAuth.server.tsx:20 disables the check that the browser session requesting the magic link is the same one verifying it. This means magic links can be used from any browser/device, which is convenient but also means intercepted links (e.g., by email proxies, link preview bots, or attackers) can be used to authenticate. This is a common trade-off in magic link implementations and is likely intentional to fix cross-device/browser issues, but it does reduce the security posture of the auth flow.

Open in Devin Review

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

},
async ({
email,
Expand Down
6 changes: 5 additions & 1 deletion apps/webapp/app/services/runsReplicationInstance.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ function initializeRunsReplicationInstance() {

console.log("🗃️ Runs replication service enabled");

const url = new URL(env.RUN_REPLICATION_CLICKHOUSE_URL);
// Remove secure param to prevent Unknown URL parameters error
url.searchParams.delete("secure");

const clickhouse = new ClickHouse({
url: env.RUN_REPLICATION_CLICKHOUSE_URL,
url: url.toString(),
name: "runs-replication",
keepAlive: {
enabled: env.RUN_REPLICATION_KEEP_ALIVE_ENABLED === "1",
Expand Down
12 changes: 9 additions & 3 deletions internal-packages/clickhouse/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
FROM golang


RUN go install github.com/pressly/goose/v3/cmd/goose@latest


WORKDIR /app
COPY ./schema ./schema
COPY ./cmd ./cmd
COPY ./migrate.sh ./migrate.sh

RUN go build -o /usr/local/bin/transform ./cmd/transform/main.go
RUN chmod +x ./migrate.sh

ENV GOOSE_DRIVER=clickhouse
ENV GOOSE_DBSTRING="tcp://default:password@clickhouse:9000"
ENV GOOSE_MIGRATION_DIR=./schema
CMD ["goose", "up"]

ENTRYPOINT ["./migrate.sh"]
CMD ["up"]
16 changes: 10 additions & 6 deletions packages/build/src/extensions/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,22 +317,26 @@ class PlaywrightExtension implements BuildExtension {

Array.from(browsersToInstall).forEach((browser) => {
instructions.push(
`RUN grep -A5 -m1 "browser: ${browser}" /tmp/browser-info.txt > /tmp/${browser}-info.txt`,
// Extract the block for the specific browser.
// We look for a line starting with "browser: {browser}" OR "{browser} v" (legacy)
// Then we collect lines until the next block starts (line starting with browser: or certain chars) or an empty line.
`RUN awk '/^browser: ${browser}|^${browser} v/{flag=1; print; next} /^(browser:|[a-z-]+ v)/{flag=0} flag' /tmp/browser-info.txt > /tmp/${browser}-info.txt`,

`RUN INSTALL_DIR=$(grep "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs) && \
`RUN INSTALL_DIR=$(grep -i "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs) && \
DIR_NAME=$(basename "$INSTALL_DIR") && \
if [ -z "$DIR_NAME" ]; then echo "Failed to extract installation directory for ${browser}"; exit 1; fi && \
if [ -z "$DIR_NAME" ]; then echo "Failed to extract installation directory for ${browser}. Content of /tmp/${browser}-info.txt:"; cat /tmp/${browser}-info.txt; exit 1; fi && \
MS_DIR="/ms-playwright/$DIR_NAME" && \
mkdir -p "$MS_DIR"`,

`RUN DOWNLOAD_URL=$(grep "Download url:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs | sed "s/mac-arm64/linux/g" | sed "s/mac-15-arm64/ubuntu-20.04/g") && \
`RUN DOWNLOAD_URL=$(grep -i "Download url:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs | sed "s/mac-arm64/linux/g" | sed "s/mac-15-arm64/ubuntu-20.04/g") && \
if [ -z "$DOWNLOAD_URL" ]; then echo "Failed to extract download URL for ${browser}"; exit 1; fi && \
echo "Downloading ${browser} from $DOWNLOAD_URL" && \
curl -L -o /tmp/${browser}.zip "$DOWNLOAD_URL" && \
if [ $? -ne 0 ]; then echo "Failed to download ${browser}"; exit 1; fi && \
unzip -q /tmp/${browser}.zip -d "/ms-playwright/$(basename $(grep "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs))" && \
INSTALL_LOCATION=$(grep -i "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs) && \
unzip -q /tmp/${browser}.zip -d "/ms-playwright/$(basename "$INSTALL_LOCATION")" && \
if [ $? -ne 0 ]; then echo "Failed to extract ${browser}"; exit 1; fi && \
chmod -R +x "/ms-playwright/$(basename $(grep "Install location:" /tmp/${browser}-info.txt | cut -d':' -f2- | xargs))" && \
chmod -R +x "/ms-playwright/$(basename "$INSTALL_LOCATION")" && \
rm /tmp/${browser}.zip`
);
});
Expand Down
89 changes: 49 additions & 40 deletions packages/cli-v3/src/deploy/buildImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,47 +486,57 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
};
}

const [credentialsError, credentials] = await tryCatch(
getDockerUsernameAndPassword(apiClient, deploymentId)
);
let credentials;
if (cloudRegistryHost.endsWith("amazonaws.com")) {
const [credentialsError, result] = await tryCatch(
getDockerUsernameAndPassword(apiClient, deploymentId)
);

if (credentialsError) {
return {
ok: false as const,
error: `Failed to get docker credentials: ${credentialsError.message}`,
logs: "",
};
if (credentialsError) {
return {
ok: false as const,
error: `Failed to get docker credentials: ${credentialsError.message}`,
logs: "",
};
}
credentials = result;
}

logger.debug(`Logging in to docker registry: ${cloudRegistryHost}`);
if (credentials) {
logger.debug(`Logging in to docker registry: ${cloudRegistryHost}`);

const loginProcess = x(
"docker",
["login", "--username", credentials.username, "--password-stdin", cloudRegistryHost],
{
nodeOptions: {
cwd: options.cwd,
},
}
);
const loginProcess = x(
"docker",
["login", "--username", credentials.username, "--password-stdin", cloudRegistryHost],
{
nodeOptions: {
cwd: options.cwd,
},
}
);

loginProcess.process?.stdin?.write(credentials.password);
loginProcess.process?.stdin?.end();
loginProcess.process?.stdin?.write(credentials.password);
loginProcess.process?.stdin?.end();

for await (const line of loginProcess) {
errors.push(line);
logger.debug(line);
}
for await (const line of loginProcess) {
errors.push(line);
logger.debug(line);
}

if (loginProcess.exitCode !== 0) {
return {
ok: false as const,
error: `Failed to login to registry: ${cloudRegistryHost}`,
logs: extractLogs(errors),
};
}
if (loginProcess.exitCode !== 0) {
return {
ok: false as const,
error: `Failed to login to registry: ${cloudRegistryHost}`,
logs: extractLogs(errors),
};
}

options.onLog?.(`Successfully logged in to the remote registry`);
options.onLog?.(`Successfully logged in to the remote registry`);
} else {
logger.debug(
`Skipping automatic registry login for ${cloudRegistryHost}. Please ensure you are logged in locally.`
);
}
Comment on lines +489 to +539
Copy link
Contributor

Choose a reason for hiding this comment

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

🚩 Non-AWS registries now skip server-side authentication entirely

The buildImage.ts change at lines 489-539 now only fetches credentials from the Trigger.dev API and performs docker login for registries ending in amazonaws.com. For all other registries (GCR, GHCR, Azure CR, custom registries), the code logs a debug message and relies on the user being logged in locally. This is a behavioral change from the previous code which attempted API-based authentication for ALL registries when push && options.authenticateToRegistry. If getDockerUsernameAndPassword previously returned valid credentials for non-AWS registries (e.g., via a generic credential proxy), those deployments would now fail with authentication errors during push. The change appears intentional for self-hosted non-AWS scenarios, but the impact on existing users of non-AWS cloud registries should be verified.

Open in Devin Review

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

}

const projectCacheRef = getProjectCacheRefFromImageTag(imageTag);
Expand All @@ -550,13 +560,12 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
options.noCache ? "--no-cache" : undefined,
...(useRegistryCache
? [
"--cache-to",
`type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}${
cacheCompression === "zstd" ? ",compression=zstd" : ""
}`,
"--cache-from",
`type=registry,ref=${projectCacheRef}`,
]
"--cache-to",
`type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${projectCacheRef}${cacheCompression === "zstd" ? ",compression=zstd" : ""
}`,
"--cache-from",
`type=registry,ref=${projectCacheRef}`,
]
: []),
"--output",
outputOptions.join(","),
Expand Down
12 changes: 10 additions & 2 deletions packages/core/src/v3/apiClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1366,20 +1366,28 @@ export class ApiClient {
});
}

async appendToStream<TBody extends BodyInit>(
async appendToStream<TBody>(
runId: string,
target: string,
streamId: string,
part: TBody,
requestOptions?: ZodFetchOptions
) {
// Serialize object payloads to JSON to prevent [object Object] coercion by fetch
const body =
typeof part === "string" || part instanceof ArrayBuffer || part instanceof Blob ||
part instanceof FormData || part instanceof URLSearchParams ||
(typeof ReadableStream !== "undefined" && part instanceof ReadableStream)
? (part as BodyInit)
: JSON.stringify(part);

return zodfetch(
AppendToStreamResponseBody,
`${this.baseUrl}/realtime/v1/streams/${runId}/${target}/${streamId}/append`,
{
method: "POST",
headers: this.#getHeaders(false),
body: part,
body,
},
mergeRequestOptions(this.defaultRequestOptions, requestOptions)
);
Expand Down
Loading