Prevent OS certificate store fallback when CA pinning is enabled#54
Merged
Conversation
Set CURLOPT_CAPATH to a non-existent directory (/dev/null/<random>) when CA pinning is active, preventing the TLS library from falling back to the OS certificate store. Fail closed with a RuntimeException if the option cannot be applied.
The curl extension is already a hard requirement in composer.json, making the file_get_contents fallback transport unreachable. Removing it simplifies the codebase and eliminates a transport that cannot provide the same CA pinning guarantees as CurlRequester.
Move the legacy ca="IGNORE" handling out of CurlRequester and into Client::setRequesterOption(), where it sets disable_ca_pinning=true and removes the ca option. This ensures consistent behavior: the User-Agent correctly reports ca_pinning=disabled, and CurlRequester no longer needs awareness of the IGNORE magic string.
jeffreyparker
marked this pull request as ready for review
July 16, 2026 15:32
AaronAtDuo
approved these changes
Jul 21, 2026
jeffreyparker
added a commit
that referenced
this pull request
Jul 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CURLOPT_CAPATHto a non-existent randomized directory (/dev/null/<random>), preventing the TLS library from falling back to system-installed CA certificates. Fails closed with aRuntimeExceptionifcurl_setoptcannot apply the option (e.g., unsupported TLS backend).file_get_contentstransport cannot provide equivalent CA pinning guarantees andext-curlis already a hard requirement incomposer.json, making it unreachable. Removing it eliminates a transport that could bypass pinning if injected.ca=IGNOREtodisable_ca_pinning: The legacyca="IGNORE"magic string is now normalized at the Client layer into thedisable_ca_pinningflag, ensuring the User-Agent accurately reportsca_pinning=disabledand CurlRequester doesn't need awareness of the IGNORE convention.Design Decisions
/dev/null/<random>path: Uses a randomized suffix to prevent an attacker with filesystem write access from pre-creating the directory. On POSIX,/dev/nullis a character device (subdirectories cannot exist), so this is belt-and-suspenders.CURLOPT_CAPATHcannot be set (exotic TLS backends that don't support it), the client throws rather than silently proceeding without the fallback protection.ca="IGNORE"checks throughout CurlRequester, the Client entry point converts it to the canonicaldisable_ca_pinningflag. This keeps the requester logic simple and ensures reporting is always accurate.Breaking Changes
DuoAPI\FileRequesterclass has been removed. Users who directly constructed and injected this class will need to switch toCurlRequester(which was already the only reachable path via the default constructor sinceext-curlis required).Test plan
CURLOPT_CAPATHis unsupported to confirm RuntimeExceptionca="IGNORE"correctly reportsca_pinning=disabledin User-Agent