Skip to content

Comments

Reject oversized request parameters at authorization endpoint with HTTP 414#322

Merged
oharsta merged 11 commits intomainfrom
copilot/check-parameter-size-issue
Feb 17, 2026
Merged

Reject oversized request parameters at authorization endpoint with HTTP 414#322
oharsta merged 11 commits intomainfrom
copilot/check-parameter-size-issue

Conversation

Copy link
Contributor

Copilot AI commented Feb 17, 2026

Production incident: authorization requests with malformed parameters (e.g., scope=openid+openid+openid... repeated 1000+ times) proceeded through authentication but failed post-auth with HeadersTooLargeException due to response headers exceeding buffer limits.

Changes

  • New exception: UriTooLongException returns HTTP 414 status
  • Validation: Request parameter byte-length checked against configurable limit (default: 1024 bytes) at authorization endpoint entry, before authentication
  • URL-encoded size: Calculates the URL-encoded size of parameters to accurately match the actual byte size when transmitted in URLs or response headers
  • Configuration: max-query-param-size property in application.yml
  • Support: Works for both GET (query parameters) and POST (form body) authorization requests

Implementation

void validateQueryParamSize(MultiValueMap<String, String> parameters) {
    if (parameters != null && !parameters.isEmpty()) {
        // Calculate the URL-encoded size of all parameters without building the full string
        // This matches the actual byte size when parameters are transmitted in URLs or response headers
        int totalSize = 0;
        boolean first = true;
        
        for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
            String encodedKey = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8);
            for (String value : entry.getValue()) {
                if (!first) {
                    totalSize += 1; // for '&' separator
                }
                first = false;
                
                totalSize += encodedKey.getBytes(StandardCharsets.UTF_8).length;
                totalSize += 1; // for '=' separator
                totalSize += URLEncoder.encode(value, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8).length;
                
                // Early exit if we've already exceeded the limit
                if (totalSize > maxQueryParamSize) {
                    throw new UriTooLongException(
                            String.format("Request parameter size (%d bytes) exceeds maximum allowed size (%d bytes)", 
                                    totalSize, maxQueryParamSize));
                }
            }
        }
    }
}

Validation occurs at the start of AuthorizationEndpoint.authorize(), rejecting malformed requests before SAML authentication. The implementation validates the @RequestParam MultiValueMap<String, String> parameters directly, ensuring both GET and POST authorization requests are protected. The URL-encoded size calculation ensures accurate measurement of the actual transmitted size, including expansion of special characters (e.g., space → %20), which is what causes the HeadersTooLargeException in production. The incremental calculation with early exit optimization prevents heap issues from the validation itself.

Original prompt

This section details on the original issue you should resolve

<issue_title>Check parameter size to avoid heap problems</issue_title>
<issue_description>In production, we saw the following authn request:

GET https://connect.surfconext.nl/oidc/authorize?client_id=example.com&redirect_uri=https:%2F%2Fwww.example.com%2Fopenid_connect_login&scope=openid+profile+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+openid+ope...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes OpenConext/OpenConext-oidcng#321

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for you](https://github.com/OpenConext/OpenConext-oidcng/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 4 commits February 17, 2026 08:53
Co-authored-by: oharsta <530657+oharsta@users.noreply.github.com>
Co-authored-by: oharsta <530657+oharsta@users.noreply.github.com>
… and remove reflection from tests

Co-authored-by: oharsta <530657+oharsta@users.noreply.github.com>
Co-authored-by: oharsta <530657+oharsta@users.noreply.github.com>
Copilot AI changed the title [WIP] Check parameter size to avoid heap problems Reject oversized query parameters at authorization endpoint with HTTP 414 Feb 17, 2026
Copilot AI requested a review from oharsta February 17, 2026 08:59
@oharsta
Copy link
Member

oharsta commented Feb 17, 2026

@copilot The validation must also take place for a POST authorization request. Change the validateQueryParamSize to check the size of the @RequestParam MultiValueMap<String, String> parameters instead of only the query string.

…both GET and POST requests

Co-authored-by: oharsta <530657+oharsta@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 17, 2026

@copilot The validation must also take place for a POST authorization request. Change the validateQueryParamSize to check the size of the @RequestParam MultiValueMap<String, String> parameters ins...

Updated the validation to check the parameters MultiValueMap instead of the query string. This now supports both GET and POST authorization requests. The change is in commit ccd27f8.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • build.openconext.org
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java -classpath /tmp/semmleTempDir2905513954245360548/apache-maven-3.8.8/boot/plexus-classworlds-2.6.0.jar -Dclassworlds.conf=/tmp/semmleTempDir2905513954245360548/apache-maven-3.8.8/bin/m2.conf -Dmaven.home=/tmp/semmleTempDir2905513954245360548/apache-maven-3.8.8 -Dlibrary.jansi.path=/tmp/semmleTempDir2905513954245360548/apache-maven-3.8.8/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/OpenConext-oidcng/OpenConext-oidcng org.codehaus.plexus.classworlds.launcher.Launcher -f pom.xml -B -V -e -Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Dspotbugs.skip -Denforcer.skip -Dmaven.javadoc.skip -DskipTests (dns block)
  • build.shibboleth.net
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java -classpath /tmp/semmleTempDir2905513954245360548/apache-maven-3.8.8/boot/plexus-classworlds-2.6.0.jar -Dclassworlds.conf=/tmp/semmleTempDir2905513954245360548/apache-maven-3.8.8/bin/m2.conf -Dmaven.home=/tmp/semmleTempDir2905513954245360548/apache-maven-3.8.8 -Dlibrary.jansi.path=/tmp/semmleTempDir2905513954245360548/apache-maven-3.8.8/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/OpenConext-oidcng/OpenConext-oidcng org.codehaus.plexus.classworlds.launcher.Launcher -f pom.xml -B -V -e -Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Dspotbugs.skip -Denforcer.skip -Dmaven.javadoc.skip -DskipTests (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

…cation

Co-authored-by: oharsta <530657+oharsta@users.noreply.github.com>
Copilot AI changed the title Reject oversized query parameters at authorization endpoint with HTTP 414 Reject oversized request parameters at authorization endpoint with HTTP 414 Feb 17, 2026
@oharsta
Copy link
Member

oharsta commented Feb 17, 2026

@copilot What is your opinion on making the following change?

int totalSize = parameters.entrySet().stream()
    .flatMap(e -> e.getValue().stream()
        .map(v -> URLEncoder.encode(e.getKey(), StandardCharsets.UTF_8)
               + "=" +
               URLEncoder.encode(v, StandardCharsets.UTF_8)))
    .collect(Collectors.joining("&"))
    .getBytes(StandardCharsets.UTF_8)
    .length;

Co-authored-by: oharsta <530657+oharsta@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 17, 2026

@copilot What is your opinion on making the following change?

int totalSize = parameters.entrySet().stream()

...

Good suggestion! Using URL-encoded size is more accurate since it matches the actual byte size when parameters are transmitted in URLs or included in response headers (which is what caused the original HeadersTooLargeException). Implemented in commit 46e0017.

The URL encoding accounts for special characters that expand (e.g., space → %20), giving a more realistic size measurement for preventing the production issue.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • build.openconext.org
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java -classpath /tmp/semmleTempDir13823907451473464824/apache-maven-3.8.8/boot/plexus-classworlds-2.6.0.jar -Dclassworlds.conf=/tmp/semmleTempDir13823907451473464824/apache-maven-3.8.8/bin/m2.conf -Dmaven.home=/tmp/semmleTempDir13823907451473464824/apache-maven-3.8.8 -Dlibrary.jansi.path=/tmp/semmleTempDir13823907451473464824/apache-maven-3.8.8/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/OpenConext-oidcng/OpenConext-oidcng org.codehaus.plexus.classworlds.launcher.Launcher -f pom.xml -B -V -e -Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Dspotbugs.skip -Denforcer.skip -Dmaven.javadoc.skip -DskipTests (dns block)
  • build.shibboleth.net
    • Triggering command: /usr/lib/jvm/temurin-17-jdk-amd64/bin/java /usr/lib/jvm/temurin-17-jdk-amd64/bin/java -classpath /tmp/semmleTempDir13823907451473464824/apache-maven-3.8.8/boot/plexus-classworlds-2.6.0.jar -Dclassworlds.conf=/tmp/semmleTempDir13823907451473464824/apache-maven-3.8.8/bin/m2.conf -Dmaven.home=/tmp/semmleTempDir13823907451473464824/apache-maven-3.8.8 -Dlibrary.jansi.path=/tmp/semmleTempDir13823907451473464824/apache-maven-3.8.8/lib/jansi-native -Dmaven.multiModuleProjectDirectory=/home/REDACTED/work/OpenConext-oidcng/OpenConext-oidcng org.codehaus.plexus.classworlds.launcher.Launcher -f pom.xml -B -V -e -Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Dspotbugs.skip -Denforcer.skip -Dmaven.javadoc.skip -DskipTests (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

…rge parameters

Co-authored-by: oharsta <530657+oharsta@users.noreply.github.com>
@oharsta oharsta merged commit 3b86d2b into main Feb 17, 2026
1 check passed
@github-project-automation github-project-automation bot moved this from New to Delivered in OpenConext-oidcng Feb 17, 2026
@codecov
Copy link

codecov bot commented Feb 17, 2026

Codecov Report

❌ Patch coverage is 96.42857% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.00%. Comparing base (df8feb4) to head (d4a83fe).
⚠️ Report is 13 commits behind head on main.

Files with missing lines Patch % Lines
...ain/java/oidc/endpoints/AuthorizationEndpoint.java 96.15% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main     #322      +/-   ##
============================================
+ Coverage     89.93%   90.00%   +0.07%     
- Complexity      665      673       +8     
============================================
  Files            86       87       +1     
  Lines          2424     2442      +18     
  Branches        276      277       +1     
============================================
+ Hits           2180     2198      +18     
  Misses          129      129              
  Partials        115      115              

☔ View full report in Codecov by Sentry.
📢 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Delivered

Development

Successfully merging this pull request may close these issues.

2 participants