formachines / errata

Errata / PyPI / pyopenssl

X509Extension changed in pyopenssl 26.4.0

In our sandbox runs up to 2026-10-27, claude-sonnet-5-5 and gemini-3.8-flash wrote basic_constraints = crypto.X509Extension( for pyopenssl. It fails on 26.4.0 with AttributeError (changed between 26.0.0 and 26.4.0). Rewrite the block as shown below.

What to do
rewrite the block
Output
same on one example
Patch
incomplete: old names remain
Auto-apply
not allowed
Seen in
2 models
Worked / failed
8 / 0
Last verified
2026-10-27

What to do

Not for automatic application — after the patch the example still uses basic_constraints, which the verified example does not: the patch covers part of the change. Show this correction to the user as a lead. Rewrite the block as shown; the failing call does not appear in the new version of the block.

Automatic application: not allowed — the patch gives incomplete when applied word for word. Automatic application is allowed only when proof.equivalent is true and proof.applicability.status is applies; otherwise show the correction to the user as a lead and do not apply it on your own. The change itself is described by kind (changed); the checks are in proof; neither follows from the other.

How to tell this applies to your code

Old and new code

basic_constraints = crypto.X509Extension(
b"basicConstraints", True, b"CA:TRUE"
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None), critical=True

Error on 26.4.0:

AttributeError: module 'OpenSSL.crypto' has no attribute 'X509Extension'

Other verified fixes

The fix above passed 4 times. These also passed in the sandbox for the same error; pick the one that matches what your code needs.

Replace the line · passed 1 time · claude-sonnet-5-5 · patch: syntax error when applied word for word

basic_constraints = crypto.X509Extension(
count = len(cert.extensions)

Not for automatic application — applied word for word, the patch does not parse: IndentationError: unexpected indent (line 26). Show this correction to the user as a lead. Replace the old line with the new one; the rest of the code can stay.

Rewrite the block · passed 1 time · gemini-3.8-flash · patch: fails when applied word for word

ext = crypto.X509Extension(b"basicConstraints", True, b"CA:TRUE")
cert.add_extensions([ext])
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None), critical=True
)

Not for automatic application — the patched example does not run on 26.4.0: exit code 1, NameError: name 'builder' is not defined. Show this correction to the user as a lead. Rewrite the block as shown; the failing call does not appear in the new version of the block.

Rewrite the block · passed 1 time · claude-sonnet-5-5 · patch: fails when applied word for word

bc = crypto.X509Extension(b"basicConstraints", True, b"CA:FALSE")
cert.add_extensions([bc])
builder = builder.add_extension(
x509.BasicConstraints(ca=False, path_length=None), critical=True
)

Not for automatic application — the patched example does not run on 26.4.0: exit code 1, UnboundLocalError: cannot access local variable 'builder' where it is not associated with a value. Show this correction to the user as a lead. Rewrite the block as shown; the failing call does not appear in the new version of the block.

Alternatives are leads for the user, not for automatic application: Automatic application is allowed only when proof.equivalent is true and proof.applicability.status is applies; otherwise show the correction to the user as a lead and do not apply it on your own.

Sandbox proof
Scriptpyopenssl versionResult
Code the models wrote26.0.0works
Code the models wrote26.4.0fails: AttributeError
Verified example below26.4.0works
Output of the verified example26.4.0same as the original's on 26.0.0 (observed stdout of one example; not a proof of equivalent behaviour)
Patch applied word for word to the example26.4.0applied word for word, the patch leaves old names in use: it covers part of the change — after the patch the example still uses basic_constraints, which the verified example does not: the patch covers part of the change

Verified example for pyopenssl 26.4.0

import datetime
import warnings

from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.x509.oid import NameOID
from OpenSSL import crypto

# X509Extension is flagged as deprecated in newer pyOpenSSL releases, but it is
# still the pyOpenSSL-native way to add extensions. Suppress the warning noise.
warnings.filterwarnings("ignore", category=DeprecationWarning)


def _ext_value_str(ext):
    value = ext.value
    if isinstance(value, x509.BasicConstraints):
        s = "CA:TRUE" if value.ca else "CA:FALSE"
        if value.path_length is not None:
            s += f", pathlen:{value.path_length}"
        return s
    return str(value)


def main():
    # Generate a 2048-bit RSA key
    key = crypto.PKey()
    key.generate_key(crypto.TYPE_RSA, 2048)
    crypto_key = key.to_cryptography_key()

    # Build the certificate
    subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "test.local")])
    now = datetime.datetime.now(datetime.timezone.utc)
    builder = (
        x509.CertificateBuilder()  # X.509 v3
        .serial_number(1000)
        .subject_name(subject)
        .issuer_name(subject)  # self-signed
        .not_valid_before(now)
        .not_valid_after(now + datetime.timedelta(seconds=365 * 24 * 60 * 60))
        .public_key(crypto_key.public_key())
    )

    # Attach basicConstraints extension marking the cert as a CA
    builder = builder.add_extension(
        x509.BasicConstraints(ca=True, path_length=None), critical=True
    )

    # Self-sign
    cert = builder.sign(crypto_key, hashes.SHA256())

    # Print extension info
    extensions = list(cert.extensions)
    count = len(extensions)
    print("Extension count:", count)
    for i in range(count):
        ext = extensions[i]
        short_name = ext.oid._name
        if isinstance(short_name, bytes):
            short_name = short_name.decode("ascii")
        print(f"Extension {i}: {short_name} = {_ext_value_str(ext)}")


if __name__ == "__main__":
    main()

Source

No release-note sentence matched this change; the evidence is the sandbox check above.

Seen in: claude-sonnet-5-5, gemini-3.8-flash · First seen 2026-10-23 · Last verified 2026-10-27 · All corrections for pyopenssl

For agents