Skip to content

Improve SAMLResponse validation in SSO callbacks#47463

Merged
lucasmrod merged 3 commits into
mainfrom
16371-improve-sso-callback-validation
Jun 12, 2026
Merged

Improve SAMLResponse validation in SSO callbacks#47463
lucasmrod merged 3 commits into
mainfrom
16371-improve-sso-callback-validation

Conversation

@lucasmrod

@lucasmrod lucasmrod commented Jun 11, 2026

Copy link
Copy Markdown
Member
  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • Security Enhancements
    • Enforced strict size limits for SAMLResponse payloads and rejected overly large submissions.
    • Added protections against deeply nested or excessively large SAML XML documents.
    • Applied rate limiting to SSO/authentication callback endpoints (configurable via Auth settings).
  • Tests
    • Added tests verifying SAMLResponse size and XML shape validation behavior.
  • Documentation
    • Noted these SSO validation and rate-limiting changes in the changelog.

Copilot AI review requested due to automatic review settings June 11, 2026 21:12
@lucasmrod lucasmrod requested a review from a team as a code owner June 11, 2026 21:12
@lucasmrod lucasmrod force-pushed the 16371-improve-sso-callback-validation branch from 38e0517 to c7dea28 Compare June 11, 2026 21:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens Fleet’s unauthenticated SSO callback endpoints against pre-auth resource-exhaustion attacks by adding size limits, rate limiting, and SAMLResponse XML shape validation before signature verification/canonicalization.

Changes:

  • Added XML depth/element-count validation for decoded SAMLResponse before passing it to crewjam/saml/goxmldsig.
  • Added a shared max-size constant for SSO callback payloads and enforced it at both HTTP body and form value levels (including query-string bypass).
  • Applied login-bucket rate limiting + request body size limits to the regular and MDM SSO callback routes.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
server/sso/authorization_response.go Adds SAMLResponse XML shape validation (depth + element count) prior to signature/cert processing.
server/sso/authorization_response_test.go Adds unit tests covering valid/invalid XML and depth/width limit enforcement.
server/service/sessions.go Enforces a max size on SAMLResponse form value (covers POST body and query string).
server/service/sessions_test.go Adds tests ensuring oversized query-string SAMLResponse is rejected and normal payloads pass.
server/service/handler.go Adds rate limiting + request body size limits to SSO callback endpoints; refactors limiter setup for reuse.
server/fleet/request.go Introduces MaxSSOCallbackSize constant (256 KiB) for SSO callback endpoints.
changes/improve-sso-samlresponse-validation Documents the user-visible security hardening changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR hardens SSO SAMLResponse validation across three validation layers. It introduces a 256 KiB size limit for SSO callback payloads and validates XML structure (nesting depth and element count) before signature verification to reject pathological SAML documents early. Request-level validation checks SAMLResponse parameter size in the callback decoder. HTTP enforcement adds rate limiting with separate buckets for login and MDM SSO endpoints, applies body size caps to both SSO callback routes, and refactors the rate-limiting middleware setup.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete. While it checks the changes file and testing checkboxes, it lacks critical sections like related issue, input validation details, and a comprehensive description of the changes. Add a related issue reference, provide a detailed explanation of the SAMLResponse validation improvements, and explain the rate limiting changes and configuration additions.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: improved SAMLResponse validation in SSO callbacks, which is the primary focus of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 16371-improve-sso-callback-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.00000% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.20%. Comparing base (2febf63) to head (7b5f33c).
⚠️ Report is 31 commits behind head on main.

Files with missing lines Patch % Lines
server/service/handler.go 63.15% 5 Missing and 2 partials ⚠️
cmd/fleet/serve.go 0.00% 2 Missing ⚠️
server/sso/authorization_response.go 90.90% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #47463      +/-   ##
==========================================
- Coverage   67.20%   67.20%   -0.01%     
==========================================
  Files        3350     3352       +2     
  Lines      228159   228264     +105     
  Branches    11751    11751              
==========================================
+ Hits       153342   153411      +69     
- Misses      60991    61027      +36     
  Partials    13826    13826              
Flag Coverage Δ
backend 68.83% <78.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/fleet/serve.go`:
- Around line 1024-1026: The SSORateLimitPerMinute check currently skips
creating the SSO limiter when config.Auth.SSORateLimitPerMinute == 0; update the
logic so a limiter is applied by default: either change the condition in
serve.go to treat 0 as “use login rate limit” or >= 0 and add fallback logic to
compute the effective rate (e.g., use the login rate-limit bucket) before
calling service.WithSsoRateLimit(throttled.PerMin(...)), or set the default in
the config (config.Auth.SSORateLimitPerMinute) to 10 in config.go; locate the
check around config.Auth.SSORateLimitPerMinute and the call to
service.WithSsoRateLimit/throttled.PerMin to implement the chosen fix and ensure
0 triggers the fallback to the login rate limit.

In `@server/config/config.go`:
- Around line 1390-1391: Set the registered default for
auth.sso_rate_limit_per_minute to 10 and update its help text to accurately
reflect that the default is 10 (rather than saying it falls back to the login
rate limit); specifically change the call to man.addConfigInt for
"auth.sso_rate_limit_per_minute" to use default 10 and revise the help string,
and also update any docs that state the default
(docs/Configuration/fleet-server-configuration.md) to match; keep the existing
logic in cmd/fleet/serve.go that only applies a limiter when
config.Auth.SSORateLimitPerMinute > 0 so behavior is consistent with the new
default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6cd15dd8-0cea-4545-8f86-6e0728494e5d

📥 Commits

Reviewing files that changed from the base of the PR and between c7dea28 and 0965221.

⛔ Files ignored due to path filters (1)
  • docs/Configuration/fleet-server-configuration.md is excluded by !**/*.md
📒 Files selected for processing (3)
  • cmd/fleet/serve.go
  • server/config/config.go
  • server/service/handler.go

Comment thread cmd/fleet/serve.go
Comment thread server/config/config.go
// has no traversal limit of its own.
func validateSAMLResponseShape(samlResponse []byte) error {
doc := etree.NewDocument()
if err := doc.ReadFromBytes(samlResponse); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one minor note here is that this loads the whole payload into memory, but given the current guards at the middleware layer, an attack would consume at most 256 KB * 10 = ~2.5 MB per minute which should be ok.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, both the rate limit + size cap should keep things safe.

Comment thread server/service/handler.go
// bucket) so a flood on the unauthenticated callback can't exhaust the
// rate-limit budget that legitimate password logins depend on. The rate
// defaults to the login rate unless explicitly overridden.
ssoRateLimit := loginRateLimit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to do the same thing for the mdmSsoLimiter? Right now both the logic and mdm sso limiter share the same bucket

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep this new rate limiting separate to reduce breaking other endpoints (this new rate limit will be separate from login/MDM-SSO endpoints)

juan-fdz-hawa
juan-fdz-hawa previously approved these changes Jun 12, 2026

@juan-fdz-hawa juan-fdz-hawa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! I left two comments, but they’re just minor suggestions rather than roadblocks.:

  1. If we think memory consumption might be an validateSAMLResponseShape, perhaps consider implementing the validation using a stream reader:
        dec := xml.NewDecoder(bytes.NewReader(samlResponse))
        depth, count := 0, 0
        for {
                tok, err := dec.Token()
                if err == io.EOF {
                        return nil
                }
                if err != nil {
                        return fmt.Errorf("parsing SAMLResponse XML: %w", err)
                }
                switch tok.(type) {
                case xml.StartElement:
                        depth++
                        count++
                        if depth > maxSAMLResponseDepth {
                                return fmt.Errorf("SAMLResponse exceeds maximum nesting depth of %d", maxSAMLResponseDepth)
                        }
                        if count > maxSAMLResponseElements {
                                return fmt.Errorf("SAMLResponse exceeds maximum element count of %d", maxSAMLResponseElements)
                        }
                case xml.EndElement:
                        depth--
                }
        }
  1. Consider maybe defining a separate bucket for the MDM SSO limiter to keep things consistent.

@lucasmrod lucasmrod merged commit ac6aa73 into main Jun 12, 2026
34 checks passed
@lucasmrod lucasmrod deleted the 16371-improve-sso-callback-validation branch June 12, 2026 13:53
georgekarrv added a commit that referenced this pull request Jun 12, 2026
…47516)

Cherry-pick of #47463 into the RC branch.

Co-authored-by: Lucas Manuel Rodriguez <lucas@fleetdm.com>
lucasmrod added a commit that referenced this pull request Jun 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants