Skip to content

Commit 90ba839

Browse files
committed
fix: Improve CA certificate detection with fallback chain
The previous implementation incorrectly assumed empty get_ca_certs() meant broken SSL, causing false failures in GitHub Codespaces and other directory-based cert systems where certificates exist but aren't pre-loaded. It would then attempt to import certifi as a workaround, but certifi wasn't listed in requirements.txt, causing the fallback to fail with ImportError even though the system certificates would have worked fine. This commit replaces the naive check with a layered fallback approach that checks multiple certificate sources. First it checks for pre-loaded system certs (file-based systems). Then it verifies system cert paths exist (directory-based systems like Ubuntu/Debian/Codespaces). Finally it attempts to use certifi as an optional fallback only if needed. This approach eliminates hard dependencies (certifi is now optional), works in GitHub Codespaces without any setup, and fails gracefully with clear hints for resolution when SSL is actually broken rather than failing with ModuleNotFoundError. Fixes josegonzalez#444
1 parent 7b78f06 commit 90ba839

File tree

2 files changed

+26
-16
lines changed

2 files changed

+26
-16
lines changed

github_backup/github_backup.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,33 @@
3737
FILE_URI_PREFIX = "file://"
3838
logger = logging.getLogger(__name__)
3939

40+
# Setup SSL context with fallback chain
4041
https_ctx = ssl.create_default_context()
41-
if not https_ctx.get_ca_certs():
42-
import warnings
43-
44-
warnings.warn(
45-
"\n\nYOUR DEFAULT CA CERTS ARE EMPTY.\n"
46-
+ "PLEASE POPULATE ANY OF:"
47-
+ "".join(
48-
["\n - " + x for x in ssl.get_default_verify_paths() if type(x) is str]
49-
)
50-
+ "\n",
51-
stacklevel=2,
52-
)
53-
import certifi
54-
55-
https_ctx = ssl.create_default_context(cafile=certifi.where())
42+
if https_ctx.get_ca_certs():
43+
# Layer 1: Certificates pre-loaded from system (file-based)
44+
pass
45+
else:
46+
paths = ssl.get_default_verify_paths()
47+
if (paths.cafile and os.path.exists(paths.cafile)) or (
48+
paths.capath and os.path.exists(paths.capath)
49+
):
50+
# Layer 2: Cert paths exist, will be lazy-loaded on first use (directory-based)
51+
pass
52+
else:
53+
# Layer 3: Try certifi package as optional fallback
54+
try:
55+
import certifi
56+
57+
https_ctx = ssl.create_default_context(cafile=certifi.where())
58+
except ImportError:
59+
# All layers failed - no certificates available anywhere
60+
sys.exit(
61+
"\nERROR: No CA certificates found. Cannot connect to GitHub over SSL.\n\n"
62+
"Solutions you can explore:\n"
63+
" 1. pip install certifi\n"
64+
" 2. Alpine: apk add ca-certificates\n"
65+
" 3. Debian/Ubuntu: apt-get install ca-certificates\n\n"
66+
)
5667

5768

5869
def logging_subprocess(

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-

0 commit comments

Comments
 (0)