robrichards/xmlseclibs
xmlseclibs is a PHP library for XML Encryption and Signatures, offering safe-by-default verification, pinned keys, algorithm allowlists, RSA-OAEP/AES-GCM support, and low-level primitives for legacy XML security workflows.
Architecture fit. xmlseclibs is a cryptographic primitive library, not an application-framework component. In a Laravel codebase it belongs beneath an adapter boundary we own — a small XmlSec service bound in the container — and must never be called directly from controllers, jobs, or third-party protocol packages. It has no service provider, no publishable config, no facade, and no opinion about HTTP, storage, or key management. That is the appeal (zero framework conflict, no global state, Octane-safe if instances are per-request) and the cost (we build and own the seam, including the config surface for pinned keys and per-peer algorithm policy). Strong fit where we own the protocol edge: SAML SP/IdP, WS-Security SOAP clients, e-invoice/UBL signing, AS4/Peppol. Weak fit if the expectation was a turnkey Laravel integration.
A note on the metadata. 413 stars and "Dependents: 0" badly understate reality. The README names SimpleSAMLphp, LightSAML, and OneLogin php-saml as consumers, so this is already a transitive dependency in most PHP SAML stacks. The strategic question is therefore not "should we adopt it" but "which version are we pinned to transitively, and who owns the upgrade" — consolidating on one XMLDSig implementation beats adding a second.
Integration feasibility. composer require robrichards/xmlseclibs is trivial. Hard constraints: PHP 8.0+, phpseclib/phpseclib ~3.0, DOM/libxml, ext-openssl now optional (which can simplify base images). PHP 8.0+ aligns with Laravel 9+; Laravel 10/11 (8.1/8.2) are comfortable. The realistic friction is the phpseclib ~3.0 pin colliding with older SSH/SFTP packages still on 2.x.
Technical risk.
beta2 — APIs and defaults may still change. But 4.0 is where all the hardening lives: safe-by-default verifyDocument(), DOCTYPE rejection on verify, XPath transform rejection, RSA-1.5 denial, fail-closed references. 3.1 is stable but gives none of that, meaning we re-implement the hardened defaults ourselves and own the bugs. Taking a beta at a security boundary needs an explicit, written sign-off.verify() === 1 (-1 is truthy), operate only on getValidatedNodes(), never derive the key from KeyInfo. These are precisely the mistakes that produce signature-wrapping exploits.Key questions before committing.
loadKey()?->verify() calls that we need to consolidate?Stack fit. Laravel 10/11, PHP 8.2+, Composer. Add our own config/xmlsec.php holding pinned certificates per peer, allowlisted signature/digest/data/key algorithms, clock skew, and legacy-interop flags. The library is a stateless value-in/value-out dependency; it does not participate in Eloquent, queues, or events, and should not be wrapped in a facade that hides the pinned-key requirement.
Migration path.
composer why robrichards/xmlseclibs across services and images. If present transitively, the work is upgrade-plus-wrapper, not greenfield.App\Support\XmlSec) exposing exactly three entry points: verify(string $xml, TrustedKey $key): ValidatedDocument, sign(...), decrypt(...). Everything else in the codebase is forbidden from importing RobRichards\XMLSecLibs\* directly — enforceable with a PHPStan/architecture rule.Compatibility. PHP 8.0+ floor is fine unless a Laravel 8 / PHP 7.4 service is in scope. Audit for phpseclib 2.x pins before requiring 4.0. Parse untrusted input ourselves with DTD/entity loading disabled (LIBXML_NONET, no LIBXML_DTDLOAD) before handing a DOMDocument to the library — that is our responsibility, not its.
Sequencing.
enableLegacyMode() and per-peer widenings as peers modernize, tracked as tech debt with an owner and a date.Maintenance. Low code churn, real ongoing obligation. Composer advisory monitoring, a defined patch SLA (e.g. 7 days for any security release touching our constraint), and a named owner. Pin exact versions — a caret range on a beta is not a plan. Watch for 4.0.0 final, which may shift defaults again and will require a full re-run of the fixture suite.
Support. Community only: GitHub issues plus a Google Groups mailing list, one primary maintainer, no SLA and no vendor escalation path. Mitigations: keep the wrapper thin enough to swap implementations, and document a vendor-fork fallback so a critical unpatched bug doesn't leave us blocked. For an identity or payment boundary that is a genuine business risk to accept explicitly.
Scaling. Crypto cost is small and predictable — verification is microseconds to low milliseconds. Real drivers are (1) canonicalization on large documents (bulk e-invoices, SOAP payloads), (2) the pre-auth XPath DoS that 4.0 closes by default, (3) DOM memory for large bodies. Guidance: verify at the edge before hydrating domain models, cap request body size, avoid re-verifying the same document in multiple layers, and push batch signing into queue workers rather than the request cycle. No shared state means horizontal scaling and Octane are non-issues provided instances are per-request.
Failure modes to design for.
verifyDocument() throws → map to a single opaque 400/401; log detail internally, never leak which check failed.verify() === 1 trap (-1 is truthy) → eliminate by making the wrapper the only callable surface; lint for raw ->verify(.KeyInfo → resolve the pinned key from our config by peer/issuer before verification, always.NotOnOrAfter → explicit configurable tolerance, not a hardcoded guess.Ramp-up. The library's API is a day; XML Signature semantics — canonicalization, transforms, reference resolution, signature wrapping — are weeks. Budget one engineer to become the internal owner and to write the wrapper and fixtures; do not spread shallow usage across teams. Onboarding requirement: the README's security notes verbatim, with each "don't do this" encoded as either a wrapper constraint or a failing test.
How can I help you explore Laravel packages today?