Severity Daily

IT and AI security incidents, checked against the primary source

passport-saml-encrypted verifies SAML signatures only when an optional cert is set, and its own README example leaves it out

PATCH NOW — passport-saml-encrypted verifies SAML signatures only when an optional cert is set, and its own README example leaves it out

Written by

in

A 9.1 authentication bypass published this afternoon covers every version the package has ever shipped, the flaw was reported by PayPal’s own security team, and there is no patched release.

What happened

VulnCheck published CVE-2026-89042 to the National Vulnerability Database at 6:18 p.m. UTC on September 10, 2026, and revised it at 7:58 p.m. UTC the same evening. The description is one sentence: “passport-saml-encrypted through 0.1.13 makes SAML signature verification conditional on an optional cert option, allowing attackers to bypass authentication by submitting unsigned SAML responses.”

The record names no fixed version. NVD’s configuration block covers passport-saml-encrypted from version 0 through 0.1.13 inclusive — every release the package has published since 0.0.1 on February 1, 2014. Version 0.1.13, the current release, reached the npm registry on August 19, 2025 at 2:48 p.m. UTC. Nothing has shipped since.

NVD’s primary score is 9.1 Critical on CVSS v3.1, vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N. A secondary v4.0 score of 9.3 sits beside it. The weakness is CWE-347, improper verification of a cryptographic signature. The record’s vulnStatus reads Deferred on the day it published, which means NVD is not queuing it for its own analysis; the assigning CNA is VulnCheck, at [email protected], not the package maintainers and not GitHub.

VulnCheck’s advisory describes the attack in operational terms: an attacker can “post forged SAML responses with arbitrary NameID and attributes to the assertion consumer service endpoint to receive authenticated profiles without valid signatures.” The credit line reads Yu Bao, PayPal Cyber Security Team.

The code

The two lines the record cites are readable in the tagged v0.1.13 source. In lib/saml.js, validateResponse() opens with this guard:

if (self.options.cert && !self.validateSignature(xml, self.options.cert)) {
  return callback(new Error('Invalid signature'), null, false);
}

The identical construction appears about 25 lines further down, guarding the decrypted-assertion path, with samlAssertion in place of xml. In both, the left operand asks whether the operator supplied a certificate. If the answer is no, the right operand — the part that actually checks the signature — never runs, and control falls straight through to the code that reads the assertion’s NameID and attributes and hands them back as an authenticated profile. There is no constructor-time check that cert was set, and no runtime warning that verification is off.

The README

The package’s own documentation is why this is more than a theoretical misconfiguration. The quickstart under “Configure strategy” — the first working example a reader copies — passes three options:

passport.use(new SamlStrategy(
  {
    path: '/login/callback',
    entryPoint: 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php',
    issuer: 'passport-saml'
  },
  ...

No cert. A deployment built from that example accepts any SAML response posted to its callback URL. Signature checking appears much later in the file, under a “Security and signatures” heading, in language that presents it as advice rather than a requirement: “It is a good idea to validate the incoming SAML Responses. For this, you can provide the Identity Provider’s certificate using the cert confguration key” — the misspelling is the README’s. Only the Active Directory Federation Services sample further down includes cert in its option block.

The README also carries a note at the very top: “Not under active developement, but I will accept PR’s.” That has been the project’s stated posture for years. The package describes itself as “largely a fork of https://github.com/bergie/passport-saml / Which seems to be dormant.”

The public issue

This did not arrive without warning. GitHub issue 29 on krakenjs/passport-saml-encrypted, titled “passport-saml-encrypted authentication bypass missing cert,” was opened by a user named August829 on August 11, 2026. It sets out the same guard, the same README problem and the same 9.1 figure, and proposes the obvious fix: throw at initialization if cert is absent, warn in the documentation, add a regression test. As of this afternoon it is still open, with no maintainer reply on the page.

So the gap between a public, correct, unanswered bug report and a CVE is thirty days. The gap between the CVE and a fix is still open.

Why it matters

SAML has one load-bearing property. The identity provider signs an assertion, the service provider verifies that signature, and everything downstream — the user’s identity, their group memberships, their administrative rights — is trusted because the verification passed. Strip the check and a SAML response is just XML a browser posted to a URL. The attacker need not touch the identity provider, guess a password, or defeat a second factor. They write the assertion they want, name themselves whatever the application’s authorization layer respects, and post it.

What makes this bug worth reading past the score is the shape of the guard. if (self.options.cert && ...) is not a check that failed; it is a check whose enforcement was made conditional on the presence of configuration. The library treats “the operator did not give me a key” and “the operator does not want verification” as the same statement. They are very different statements, and only one of them is ever true in practice.

That pattern generalizes badly. Any control written as if (configured) { enforce() } can be switched off by omission — silently, with no error, no log line and no observable difference on a normal login. The application works. Users sign in. Tests pass, because the test identity provider signs its responses and both paths end at the same success callback. The failure is visible only to somebody who sends a response the library was supposed to reject — which is to say, only to an attacker.

The documentation ordering compounds it. An option that is dangerous to omit belongs in the first example, not in a later section framed as good practice. Severity Daily has repeatedly covered guards that test the wrong condition; this is the same family moved a layer out, where what gets tested is not a property of the message but a property of the config file.

There is an institutional detail here too, worth stating carefully because it is easy to overstate. The npm package lists nine maintainers, among them krakenjs-user and imbrian-paypal; the CVE record’s GitHub references point at the krakenjs organization, PayPal’s open-source home, while the repository field in the published package.json still points at the original author’s personal fork at lmarkus/passport-saml-encrypted. The credited reporter is from PayPal’s Cyber Security Team. That adds up to a flaw found by a security team inside the organization whose GitHub org hosts the code, with a public issue open for a month and no release. It does not add up to a claim that PayPal maintains this library — the README says plainly that nobody does.

Finally, the affected range. A CVE reading “through 0.1.13” against a package whose newest release is 0.1.13 is not a patch-gap story: there is no far side to the gap. A scanner will flag the installation, the remediation column will be empty, and the only honest entry an operator can put in a risk register is a configuration change.

What to do

Set cert. If passport-saml-encrypted is in a running application, pass the identity provider’s signing certificate in the strategy’s options. That is not a workaround; per the code above it is the switch that turns verification on at both the response and the decrypted-assertion path. Confirm it by posting an unsigned response to the callback URL and checking that the strategy returns Invalid signature.

Find it. Search dependency trees for the exact package name — it is distinct from passport-saml and @node-saml/passport-saml, and a scanner keyed to those will not match it. Then grep your own configuration for the strategy’s option object rather than trusting that cert is set somewhere.

Plan the move. With no patched release and no maintainer response, this is not a wait-for-the-fix situation. The upstream project this package forked, passport-saml, carries an npm deprecation notice on its final 3.2.4 release of October 22, 2022 pointing at the scoped package; @node-saml/passport-saml is at 5.1.0, published July 24, 2025. That is the maintained line.

Do not read the score as a ceiling. The v3.1 vector marks availability impact as none, which is why this is 9.1 and not 9.8. Nothing crashes — and an attacker forging an administrative assertion does not need anything to crash.

Sourcing note

Checked directly: the NVD record for CVE-2026-89042 (published September 10, 2026 at 6:18 p.m. UTC, last modified 7:58 p.m. UTC, vulnStatus Deferred, CNA VulnCheck) for the description, both vectors, CWE-347 and the affected range; VulnCheck’s own advisory for the attack description and the credit to Yu Bao of the PayPal Cyber Security Team; the tagged v0.1.13 source of lib/saml.js, README.md and package.json, read from raw.githubusercontent.com, for the two guards, the quickstart example, the “good idea” wording and the repository field; the npm registry for the release history of passport-saml-encrypted, passport-saml and @node-saml/passport-saml, including the deprecation notice; and GitHub issue 29 for the August 11, 2026 report.

Could not reach: api.npmjs.org, the download-statistics endpoint, is not reachable from this publication’s container, so no install or download figure is given here and none should be inferred. The real deployed scale of this package is unknown to us. api.github.com is likewise unavailable, so issue 29 was read as a rendered page; a maintainer reply that the page did not render would not have been seen.

Unresolved: whether any fork or vendored copy carries a fix, whether the maintainers intend to release one, and whether the krakenjs organization considers the package in scope for its own security process. No exploitation of this flaw has been reported by anyone, and this story does not claim any.