Skip to content

feat(agents): use certifi as CA fallback - #1917

Closed
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
stiles-pities-veeps
Closed

feat(agents): use certifi as CA fallback#1917
rosetta-livekit-bot[bot] wants to merge 1 commit into
mainfrom
stiles-pities-veeps

Conversation

@rosetta-livekit-bot

@rosetta-livekit-bot rosetta-livekit-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Testing

  • corepack pnpm@9.7.0 --filter @livekit/agents build
  • corepack pnpm@9.7.0 --filter @livekit/agents lint (passes with existing warnings)

Ported from livekit/agents#6231

Original PR description

LLM client uses httpx, which falls back to certifi's CA bundle, while STT/TTS plugins go through a shared aiohttp session that relied solely on the host's system trust store. On hosts without a resolvable system store (minimal containers, distroless images missing ca-certificates), aiohttp-based TLS would fail while the LLM client kept working.

The shared TCPConnector now builds its SSL context to: (1) honor SSL_CERT_FILE / SSL_CERT_DIR env overrides, (2) prefer the system trust store, and (3) fall back to certifi only when no system store is resolvable on disk. This gives consistent TLS trust roots across LLM, STT, and TTS without overriding custom system CAs when a store is present.

The fix is also applied to the worker and job process so they can both use certifi as a fallback

@changeset-bot

changeset-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0bcdc31

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 35 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hedra Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Devin Review found 2 potential issues.

Open in Devin Review

return true;
}

return systemCaFiles.some(pathIsFile) || systemCaDirs.some(pathIsDirectory);

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.

🔴 Certificate fallback skipped on minimal Docker images that have empty certificate directories

The trust-store detection treats an empty directory as proof that certificates exist (systemCaDirs.some(pathIsDirectory) at agents/src/certificates.ts:50), so the certifi fallback never activates on systems that need it.

Impact: On minimal Linux containers (e.g., Debian-slim or Alpine without ca-certificates), the fallback silently does nothing, leaving TLS errors unresolved — exactly the scenario this feature targets.

Directory-existence check matches empty dirs created by OpenSSL packages

On Debian-based or Alpine images, the openssl package creates /etc/ssl/certs/ as part of its directory structure, but the ca-certificates package is what actually populates it with certificate files. In minimal Docker images that omit ca-certificates, /etc/ssl/certs/ exists as an empty directory.

hasSystemTrustStore() at agents/src/certificates.ts:45-51 checks:

return systemCaFiles.some(pathIsFile) || systemCaDirs.some(pathIsDirectory);

The file checks (systemCaFiles.some(pathIsFile)) correctly fail because no cert bundle files like ca-certificates.crt exist. But the directory check (systemCaDirs.some(pathIsDirectory)) matches the empty /etc/ssl/certs/ directory, making the function return true.

This causes setDefaultCertEnv() at agents/src/certificates.ts:54-62 to return early without setting the certifi fallback.

For comparison, Go's crypto/x509 also lists these directories, but it attempts to load certificates from them rather than using directory existence as a boolean "has trust store" signal. The semantic difference is significant: an existing-but-empty directory means "no certificates" when loading, but means "has trust store" in this boolean check.

The file checks alone (without the directory checks) would correctly detect the absence of certificates.

Prompt for agents
The hasSystemTrustStore() function in agents/src/certificates.ts:50 uses systemCaDirs.some(pathIsDirectory) to check for system CA directories, but this matches empty directories that contain no actual certificate files. On minimal Docker images (e.g., Debian-slim or Alpine without ca-certificates), /etc/ssl/certs/ can exist as an empty directory created by the openssl package.

Two approaches to fix:

1. Remove the systemCaDirs check entirely, relying only on systemCaFiles.some(pathIsFile). The file checks already cover all major distros and are more reliable since they verify actual cert bundle files exist.

2. Enhance pathIsDirectory to verify the directory is non-empty by checking for at least one .pem or .crt file inside, e.g., using readdirSync and filtering by extension.

Approach 1 is simpler and sufficient. The directory-based check was likely ported from Go's crypto/x509, but Go uses those directories to load certificates (empty = no certs loaded), whereas here it's used as a boolean signal.
Open in Devin Review

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

Comment thread agents/package.json
"@opentelemetry/sdk-trace-node": "^1.28.0",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@types/pidusage": "^2.0.5",
"certifi": "^14.5.15",

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.

🚩 The certifi package (v14.5.15) was last published in 2018

The certifi npm package at version ^14.5.15 was last published to npm on 2018-03-23, making its CA certificate bundle approximately 8 years old. Root CA certificates are periodically rotated, and some CAs from 2018 may have expired or been distrusted since then. While this is a dependency choice rather than a code bug, it could mean the fallback bundle doesn't include newer root CAs, potentially causing TLS verification failures for services using recent certificate chains. The Python certifi package is actively maintained with frequent updates; the Node.js port appears abandoned. Consider whether an alternative like node-forge's bundled certs or a more maintained package would be more appropriate.

Open in Devin Review

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants