Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐍
🐍 PyPI
Not in CISA KEV
HIGH severity

GHSA-cfh6-pv5c-38jv lemur

HIGHFix: Netflix/lemur@2868745

GHSA-cfh6-pv5c-38jv is a high-severity (CVSS 8.1) CWE-639 vulnerability in lemur. A fix is available for lemur — see the affected versions and patch details below.

Lemur: Unchecked `replaces[]` lets any user silence notifications and hijack auto-rotation for arbitrary certificates

Also known asCVE-2026-71308PYSEC-2026-3675
Published
Aug 18, 2026
Updated
Aug 19, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 21, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for GHSA-cfh6-pv5c-38jv.

EPSS Exploitation Probability

via FIRST.org ↗
0.2%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs12th percentile — riskier than 12% of all scored CVEsHighest risk

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

GHSA-cfh6-pv5c-38jv plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 377,333 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected
🐍lemur

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

Description

Summary

Repo under test: https://github.com/Netflix/lemur

The certificate create and upload endpoints accept a replaces[] (alias replacements) array that is resolved to live Certificate ORM objects with no ownership or CertificatePermission check on the referenced certificates. The SQLAlchemy Certificate.replaces append listener then immediately sets victim.notify = False and populates victim.replaced. From that point the victim certificate is excluded from auto-reissue, its expiration notifications are silenced, and the periodic certificate_rotate Celery task deploys the attacker's certificate (endpoint.certificate.replaced[0]) onto every endpoint serving the victim certificate.

Any authenticated non-read-only user can therefore silently substitute their own certificate onto production load balancers and Kubernetes secrets they hold no role on, while suppressing the legitimate certificate's lifecycle automation.

Affected route

POST /api/1/certificates POST /api/1/certificates/upload PUT /api/1/certificates/<id>

Affected code

Impact

An authenticated insider or holder of a stolen low-privilege token can, without holding any role on a target certificate:

  1. Upload a self-signed or attacker-minted certificate listing arbitrary high-value production certificate IDs in replaces.
  2. Immediately disable expiration notifications and auto-reissue for those production certificates.
  3. On the next scheduled certificate_rotate Celery run, have the attacker's certificate pushed to every endpoint (AWS ELB/CloudFront/ACM, Kubernetes, SFTP, etc.) currently serving the victim certificate, while the legitimate certificate is detached.

Minimum impact is fleet-wide TLS denial of service equivalent to mass revocation. Where internal clients trust the substituted chain (or combined with the sub-CA finding LEMUR-BUG-07), it escalates to TLS interception. This directly violates the invariant that a user may only modify or revoke a certificate if they are its owner, a member of an owning role, or an administrator.

Root cause

AssociatedCertificateSchema.get_object calls fetch_objects(Certificate, data) and returns the ORM rows verbatim. No caller on the create/upload/edit path iterates the resolved replaces list to enforce CertificatePermission before the model assigns them, and the Certificate.replaces append event listener mutates the victim row (notify = False) as a side effect of ORM collection assignment. The direct revoke endpoint does enforce CertificatePermission, but this replaces path achieves an equivalent or worse outcome while bypassing it entirely.

Validated evidence

Static trace, confirmed by code inspection (validation status: CONFIRMED):

  • replaces is accepted in CertificateInputSchema / CertificateUploadInputSchema and resolved via fetch_objects(Certificate, ...) with no per-object authorization.
  • grep -n CertificatePermission lemur/certificates/views.py shows the check is applied to PUT/DELETE/revoke/export paths but never to the replaces payload of POST /certificates or POST /certificates/upload.
  • The Celery certificate_rotate task and cli.rotate() consume Endpoint.replaced.any() unconditionally and deploy replaced[0] with commit=True.

Proof of concept / reproducer

Status: reconstructed from source report (static control-flow trace; not executed against a live CA).

Preconditions: attacker is an authenticated Lemur user holding any role other than read-only (default StrictRolePermission config). <VICTIM_CERT_ID> is any certificate id readable via GET /api/1/certificates.

# 1. Upload an attacker-controlled cert that "replaces" the victim
curl -sS -X POST "<TARGET_BASE_URL>/api/1/certificates/upload" \
  -H "Authorization: Bearer <AUTH_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "attacker-replacement",
        "owner": "[email protected]",
        "body": "-----BEGIN CERTIFICATE-----\n<ATTACKER_CERT_PEM>\n-----END CERTIFICATE-----",
        "privateKey": "-----BEGIN PRIVATE KEY-----\n<ATTACKER_KEY_PEM>\n-----END PRIVATE KEY-----",
        "replaces": [{"id": <VICTIM_CERT_ID>}]
      }'

# 2. Observe victim.notify is now false and victim is queued for rotation
curl -sS "<TARGET_BASE_URL>/api/1/certificates/<VICTIM_CERT_ID>" \
  -H "Authorization: Bearer <AUTH_TOKEN>" | jq '.notify, .replaced'

# 3. On the next certificate_rotate Celery beat tick, the attacker cert is
#    deployed to every endpoint that was serving <VICTIM_CERT_ID>.

Static-trace validation command from the source report:

grep -n 'replaces' lemur/certificates/schemas.py lemur/certificates/views.py lemur/schemas.py \
  && grep -n 'CertificatePermission' lemur/certificates/views.py

Source artifact: audit/harnesses/public-repo-threat-model-harness/results/netflix-lemur-100run-mythos-20260627T051129Z/findings.jsonl (run_083, finding cluster lemur-replaces-unauth, 7/100 runs).

Suggested fix

Before persisting replaces/replacements on certificate create, upload, and edit, iterate each referenced certificate and enforce the same CertificatePermission(owner_role, cert.roles) check used by the revoke endpoint (views.py:1677-1685); reject with 403 if the caller is not creator/owner/role-member/admin for any target. Additionally, move the value.notify = False side effect out of the SQLAlchemy append listener so an authorization failure cannot leave a victim certificate partially mutated, and emit an audit_log entry whenever a certificate is marked as replaced.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐍PyPIlemur0.5.0&&< 1.9.31.9.3pip install --upgrade 'lemur==1.9.3'

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for lemur, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update lemur to 1.9.3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-cfh6-pv5c-38jv is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-cfh6-pv5c-38jv can be triaged on real exposure rather than presence alone.

Tailored to GHSA-cfh6-pv5c-38jv. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary Repo under test: https://github.com/Netflix/lemur The certificate create and upload endpoints accept a `replaces[]` (alias `replacements`) array that is resolved to live `Certificate` ORM objects with no ownership or `CertificatePermission` check on the referenced certificates. The SQLAlchemy `Certificate.replaces` append listener then immediately sets `victim.notify = False` and populates `victim.replaced`. From that point the victim certificate is excluded from auto-reissue, its expiration notifications are silenced, and the periodic `certificate_rotate` Celery task deploys the
O3 Security · Impact-Aware SCA

Is GHSA-cfh6-pv5c-38jv in your dependencies?

O3 Security finds GHSA-cfh6-pv5c-38jv across PyPI dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.

GHSA-cfh6-pv5c-38jv: lemur DoS (High 8.1) | O3 Security