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
28 changes: 26 additions & 2 deletions src/workerd/api/http.c++
Original file line number Diff line number Diff line change
Expand Up @@ -1687,11 +1687,35 @@ jsg::Promise<jsg::Ref<Response>> fetchImplNoOutputLock(jsg::Lock& js,
return ioContext.awaitIo(js,
AbortSignal::maybeCancelWrap(js, signal, kj::mv(KJ_ASSERT_NONNULL(nativeRequest).response))
.catch_([](kj::Exception&& exception) -> kj::Promise<kj::HttpClient::Response> {
if (exception.getDescription().startsWith("invalid Content-Length header value")) {
auto desc = exception.getDescription();

if (desc.startsWith("invalid Content-Length header value")) {
return JSG_KJ_EXCEPTION(FAILED, Error, exception.getDescription());
} else if (exception.getDescription().contains("NOSENTRY script not found")) {
} else if (desc.contains("NOSENTRY script not found")) {
return JSG_KJ_EXCEPTION(FAILED, Error, "Worker not found.");
}
// Handle TLS certificate validation errors with helpful messages
else if (desc.contains("TLS peer's certificate is not trusted")) {
return JSG_KJ_EXCEPTION(FAILED, Error,
"Network connection failed: The destination server's TLS certificate could not be "
"verified. If connecting to a server with a self-signed certificate in local "
"development, ensure NODE_EXTRA_CA_CERTS is set to a file containing the CA "
"certificate.");
} else if (desc.contains("TLS peer provided no certificate")) {
return JSG_KJ_EXCEPTION(FAILED, Error,
"Network connection failed: The destination server did not provide a TLS "
"certificate.");
} else if (desc.contains("peer disconnected without gracefully ending TLS session")) {
return JSG_KJ_EXCEPTION(FAILED, Error,
"Network connection failed: The connection was closed unexpectedly during TLS "
"handshake.");
} else if (desc.contains("OpenSSL error") || desc.contains("SSL_")) {
// Generic TLS/SSL error
return JSG_KJ_EXCEPTION(FAILED, Error,
"Network connection failed due to a TLS/SSL error. This may indicate a certificate "
"configuration issue. In local development, ensure the destination server's CA "
"certificate is trusted via NODE_EXTRA_CA_CERTS.");
}
return kj::mv(exception);
}),
[fetcher = kj::mv(fetcher), jsRequest = kj::mv(jsRequest), urlList = kj::mv(urlList),
Expand Down
39 changes: 39 additions & 0 deletions src/workerd/api/tests/fetch-tls-error-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2024 Cloudflare, Inc.
// Licensed under the Apache 2.0 license found in the LICENSE file or at:
// https://opensource.org/licenses/Apache-2.0

import assert from 'node:assert';

// Test that TLS errors produce helpful error messages rather than opaque "internal error"
export const tlsCertificateErrorMessage = {
async test(ctrl, env, ctx) {
// Attempt to fetch from a server with an untrusted certificate.
// The test server uses a self-signed cert that is NOT in the trustedCertificates list
// for the internet service, so this should fail with a TLS error.

try {
await fetch(`https://localhost:${env.UNTRUSTED_TLS_PORT}/`);
assert.fail('Expected fetch to fail due to TLS certificate error');
} catch (err) {
// Verify we get a helpful error message, not an opaque internal error
assert.ok(
!err.message.includes('internal error; reference ='),
`Expected helpful TLS error message, got opaque internal error: ${err.message}`
);

// The error should mention TLS/certificate issues and be actionable
assert.ok(
err.message.includes('TLS') ||
err.message.includes('certificate') ||
err.message.includes('Network connection failed'),
`Expected error message to be helpful about TLS/certificate issues, got: ${err.message}`
);

// Should mention NODE_EXTRA_CA_CERTS as the workaround
assert.ok(
err.message.includes('NODE_EXTRA_CA_CERTS'),
`Expected error message to mention NODE_EXTRA_CA_CERTS workaround, got: ${err.message}`
);
}
},
};
34 changes: 34 additions & 0 deletions src/workerd/api/tests/fetch-tls-error-test.wd-test
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (c) 2024 Cloudflare, Inc.
# Licensed under the Apache 2.0 license found in the LICENSE file or at:
# https://opensource.org/licenses/Apache-2.0

using Workerd = import "/workerd/workerd.capnp";

const config :Workerd.Config = (
services = [
(
name = "main",
worker = (
modules = [
(name = "worker.js", esModule = embed "fetch-tls-error-test.js")
],
compatibilityFlags = ["nodejs_compat"],
bindings = [
(name = "UNTRUSTED_TLS_PORT", fromEnvironment = "UNTRUSTED_TLS_PORT"),
],
)
),
# Internet service WITHOUT the test server's CA in trustedCertificates.
# This simulates the real-world scenario where a certificate is not trusted,
# which should trigger the TLS error handling we added.
( name = "internet",
network = (
allow = ["private"],
tlsOptions = (
# Intentionally NOT including any trusted certificates to trigger TLS error
trustedCertificates = [],
),
)
),
],
);
Loading