handleSAMLSLO was an unauthenticated force-logout DoS: any cross-origin
GET or POST to /api/saml/{id}/slo cleared the user's session and
returned a 302 to /?logout=success. No SAML payload was required, no
signature validation, no IdP-identity check.
Add ValidateLogoutResponse to SAMLService wrapping crewjam/saml's
ServiceProvider.ValidateLogoutResponseRequest, which validates the
XML-DSig against the configured IdP certificate and the standard
LogoutResponse temporal and target invariants.
In handleSAMLSLO:
- extract and validate the provider ID
- require either a SAMLResponse (response to our SLO request) or a
SAMLRequest (IdP-initiated SLO) parameter — reject bare requests
with 400 and do not clear session
- reject IdP-initiated LogoutRequest with 400: we don't track
in-flight request IDs to bind one to yet, and silently treating it
as a force-logout signal is the same bug we are closing
- on SAMLResponse, call service.ValidateLogoutResponse. On failure
return 403 and do not clear session; audit-log the failure
- only when validation passes do we clear the session and redirect
Regression tests:
- TestHandleSAMLSLO_RejectsEmptyPayload (400, no redirect, no session
state mutation)
- TestHandleSAMLSLO_RejectsInvalidLogoutResponse (403 on garbage
SAMLResponse value)
- TestHandleSAMLSLO_RejectsIDPInitiatedLogoutRequest (400 on
SAMLRequest-only payload)
The prior TestHandleSAMLSLO_Redirects test, which asserted the unsafe
"clear session and 302 on any GET" behaviour, is replaced by the
three above.
Keep infrastructure, workloads, and storage summaries page-scoped during contextual row focus, and restore the missing mock and SAML contract drift required by the governed commit hook path.