Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
rewrite-cve-2026-22732 — OpenRewrite recipe that detects and fixes Spring Security header suppression (CVE-2026-22732) by identifying Content-Length header misuse and generating eager header-writing configuration. | Kitploit
Tools/GitHubGitHub/moderneinc/rewrite-cve-2026-22732
Static AnalysisVulnerability AnalysisCode AnalysisWeb SecurityDevSecOps
GitHubmoderneinc/rewrite-cve-2026-22732

rewrite-cve-2026-22732

OpenRewrite recipe that detects and fixes Spring Security header suppression (CVE-2026-22732) by identifying Content-Length header misuse and generating eager header-writing configuration.

View Repository

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
9h 21m agoNot yet reviewed
Share

rewrite-cve-2026-22732

OpenRewrite recipe that detects code susceptible to CVE-2026-22732, a Spring Security defect where setting Content-Length through one of three response methods bypasses Spring Security's OnCommittedResponseWrapper. Because the wrapper never sees the header, onResponseCommitted() never fires, and the lazy-added security headers (X-Frame-Options, X-Content-Type-Options, Cache-Control, etc.) are silently dropped.

What it finds

The actual triggers, confirmed against vulnerable Spring Security 6.4.12 with Spring Boot 3.4.3 / embedded Tomcat:

  1. Servlet Content-Length via the wrapper-bypassing overloads

root@kitploit:~
response.setHeader("Content-Length", "42");
response.setIntHeader("Content-Length", 42);
response.addIntHeader("Content-Length", 42);

These three overloads are not overridden in OnCommittedResponseWrapper. Subsequent body writes complete the declared length and the container commits without firing the lazy header writer.

  • WebFlux Content-Length via HttpHeaders

    root@kitploit:~
    serverHttpResponse.getHeaders().set("Content-Length", "42");
    serverHttpResponse.getHeaders().add("Content-Length", "42");
    serverHttpResponse.getHeaders().setContentLength(42L);
    
  • WebFlux unconditional response commits

    root@kitploit:~
    serverHttpResponse.writeWith(Mono.just(dataBuffer));
    serverHttpResponse.writeAndFlushWith(publisher);
    serverHttpResponse.setComplete();
    
  • The recipe is gated on Spring Security presence — it emits nothing in files that don't reference any org.springframework.security.* type — and on affected Spring Security version ranges. Per the Spring advisory published 2026-03-19, the affected ranges and fix versions are:

    SeriesAffectedFixed
    5.7.x5.7.0 – 5.7.215.7.22 (Enterprise)
    5.8.x5.8.0 – 5.8.235.8.24 (Enterprise)
    6.3.x6.3.0 – 6.3.146.3.15 (Enterprise)
    6.4.x6.4.0 – 6.4.146.4.15 (Enterprise)
    6.5.x6.5.0 – 6.5.86.5.9 (OSS)
    7.0.x7.0.0 – 7.0.37.0.4 (OSS)

    Projects resolving a Spring Security version at or above the fix in its series (or on any future series past 7.0 / 6.5) are treated as unaffected and receive no per-sink or per-file markers. Projects where the version can't be resolved fall through to the usual pattern-based detection so a scanner errs toward reporting a finding it can't disprove. The SpringSecurityVersionByProject data table still records the resolved version and flags each project as affected or not, so you can audit what was filtered.

    What is intentionally NOT flagged

    These look dangerous but are wrapper-tracked, so security headers are written before the response commits:

    CodeWhy it's safe
    response.setContentLength(int) / setContentLengthLong(long)Overridden — wrapper records the declared length and fires onResponseCommitted() when the body completes.
    response.flushBuffer()Overridden — calls doOnResponseCommitted() before super.flushBuffer().
    response.getOutputStream().write(..) / flush() / close()Returns SaveContextServletOutputStream; every write/flush/close fires doOnResponseCommitted() before delegating.
    response.getWriter().write(..) / print(..) / println(..) / flush() / close()Returns SaveContextPrintWriter; same pattern.
    response.addHeader("Content-Length", v)Special-cased in the wrapper — routed through setContentLength(long).

    The Semgrep demo's /vuln/flush endpoint claims flushBuffer() is the trigger, but on a vulnerable Spring Security 6.4.12 the response actually returns all six security headers. The real triggers in the demo are the setIntHeader("Content-Length", ...) calls in /vuln/stream and /vuln/content-length.

    Finding

    Run this one:

    RecipePurpose
    io.moderne.recipe.cve202622732.FindSpringSecurityHeaderSuppressionRuns every detection and emits the version report table

    Building blocks (advanced)

    The aggregator above is composed of two smaller recipes. You can invoke them individually if you want only one detection.

    RecipePurpose
    io.moderne.recipe.cve202622732.FindHttpResponseContentLengthHeaderTaint-flow for "Content-Length" literal reaching setHeader / setIntHeader / addIntHeader (servlet) or HttpHeaders.set / add (WebFlux)
    io.moderne.recipe.cve202622732.FindHttpResponseContentLengthOrFlushBufferWebFlux unconditional commits: writeWith, writeAndFlushWith, setComplete, HttpHeaders.setContentLength

    Fixing

    Run this one:

    RecipePurpose
    io.moderne.recipe.cve202622732.FixSpringSecurityHeaderSuppressionPicks the cheapest remediation each project can actually take

    It runs two steps in order.

    1. Bump to the fix on the project's own series. Spring Security published the fix as 6.5.9 and 7.0.4 on Maven Central. Each bump is gated on a FindAffectedSpringSecuritySeries precondition, because UpgradeDependencyVersion only checks that its target is newer — told to go to 7.0.4 it would happily drag a 5.8 project across two major versions.

    2. Add an eager header-writing configuration to whatever step 1 could not fix. This generates one @Configuration class per project:

    root@kitploit:~
    @Bean
    public static BeanPostProcessor eagerHeaderWriterFilterBeanPostProcessor() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof HeaderWriterFilter) {
                    ((HeaderWriterFilter) bean).setShouldWriteHeadersEagerly(true);
                }
                return bean;
            }
        };
    }
    

    Writing the headers up front makes it irrelevant whether the wrapper ever observes the commit, so this closes every sink in the project at once — including the ones taint analysis can't reach, like the Map flow under "Known limitations". The BeanPostProcessor sees filters built by the HttpSecurity DSL because AutowireBeanFactoryObjectPostProcessor initialises them through the bean factory. Two independently published fixes for this CVE use exactly this shape (hmcts/idam-web-public, and armory-io's Spinnaker forks via the equivalent ObjectPostProcessor).

    Step 2 is what covers the projects step 1 cannot help:

    SituationWhy the bump doesn't work
    5.7, 5.8, 6.3, 6.4Fix ships to Spring Enterprise subscribers only — not on Maven Central
    6.0 - 6.2No fix was ever released on those series
    Version managed by an imported BOMNothing is declared locally for the bump to edit

    That last row is not a corner case. nla/bamboo resolves 7.0.3 — a series with an open-source fix — entirely from the Spring Boot BOM, so a version check alone would skip it under both steps and leave it vulnerable. AddEagerHeaderWriterConfiguration therefore only defers to the upgrade when the project both has an open-source fix available and declares a version of its own.

    The generated class is placed next to an @EnableWebSecurity class where one exists, falling back to @SpringBootApplication and then any @Configuration, so it always lands somewhere component scanning reaches. Projects that already write headers eagerly, or that vendor a patched OnCommittedResponseWrapper (as jogetworkflow/jw-community does), are left alone.

    Building blocks (advanced)

    RecipePurpose
    io.moderne.recipe.cve202622732.UpgradeSpringSecurityToPatchedVersionSeries-gated bump to 6.5.9 / 7.0.4
    io.moderne.recipe.cve202622732.AddEagerHeaderWriterConfigurationThe generated configuration, on its own
    io.moderne.recipe.cve202622732.FindAffectedSpringSecuritySeriesMarks projects on one affected series; the upgrade precondition

    Verified against a running server

    Applied to semgrep/cve-2026-22732-demo on vulnerable Spring Security 6.4.12, against embedded Tomcat. Its HeaderVerificationTest asserts that the security headers are missing, so a working fix makes it fail:

    EndpointBeforeAfter
    /vuln/streamX-Content-Type-Options: nullnosniff
    /vuln/content-lengthX-Content-Type-Options: nullnosniff
    /safenosniffnosniff

    X-Frame-Options and Cache-Control follow the same pattern.

    Across the 16 repositories of the corpus that build (of 29 identified), the fix generated a configuration for three and correctly left the rest alone:

    RepositoryOutcome
    semgrep/cve-2026-22732-demoGenerated into com/example/vuln, beside @EnableWebSecurity; compiles, headers restored
    nla/bambooGenerated into ui/src/bamboo, beside @SpringBootApplication; compiles. The BOM-managed 7.0.3 case the upgrade cannot reach
    star-whale/starwhaleGenerated into ai/starwhale/mlops/configuration/security, beside @EnableWebSecurity; compiles (JDK 11, its declared target)
    hmcts/idam-web-publicLeft alone — already calls setShouldWriteHeadersEagerly
    okta/okta-idx-java (6.5.9), psi-probe (6.5.11), Ant-Media-Server (6.5.11)Left alone — past the fix on their series
    apache/shenyu (6.3.1)Left alone — reactive only; HeaderWriterFilter has no servlet API behind it, and the CVE is servlet-only
    brutusin/Brutusin-RPC (4.0.4)Left alone — predates setShouldWriteHeadersEagerly (5.2)
    bootplus, template-app, front50, igor, rosco, spring-security, reportserverLeft alone — no affected Spring Security version resolved

    Re-running the fix over the three patched repositories generates nothing further, so the remediation is idempotent against its own output on real projects.

    A second, wider corpus targets the population the fix actually addresses — any affected servlet Spring Security application, since no sink is required. Of 64 such projects found by code search, 52 built, 48 resolved an affected version, and 38 were patched; 35 of those compile (the other three fail identically without the generated file). All 8 projects on Spring Security below 5.2 were correctly skipped. See SUSCEPTIBLE-REPOSITORIES.md section 8.

    Limitations

    • The WebFlux detections are a different hazard, not this CVE. OnCommittedResponseWrapper extends jakarta.servlet.http.HttpServletResponseWrapper, so CVE-2026-22732 is servlet-only and a reactive application is not exposed to it. Findings from FindHttpResponseContentLengthOrFlushBuffer flag the analogous reactive pattern and still need manual review, but the fix deliberately does not act on them. AddEagerHeaderWriterConfiguration skips any module that can see HeaderWriterFilter without the servlet API behind it — the filter extends OncePerRequestFilter, and spring-security-web carries the servlet API as a non-transitive provided dependency, so generating there fails with cannot access jakarta.servlet.Filter (observed on apache/shenyu).
    • Versions below Spring Security 5.2 are detected but not fixed. HeaderWriterFilter.setShouldWriteHeadersEagerly arrives in 5.2; on 4.0.4 the filter has only a constructor and doFilterInternal. Detection still reports EOL versions, but the remediation is withheld rather than emitting a call that cannot compile.
    • Eager headers are written for every request, including ones later replaced by an error dispatch. That is the trade-off Spring Security's lazy default avoids, and it is why the upgrade runs first.

    Data tables

    TableRows
    TaintFlowTable (from rewrite-program-analysis)One row per Content-Length-header taint hit
    HttpResponseDirectCommitTableOne row per WebFlux structural hit
    SpringSecurityVersionByProjectOne row per project with detected Spring Security version

    Running

    Via the Moderne CLI:

    root@kitploit:~
    mod run . --recipe io.moderne.recipe.cve202622732.FindSpringSecurityHeaderSuppression
    

    Via rewrite.yml:

    root@kitploit:~
    ---
    type: specs.openrewrite.org/v1beta/recipe
    name: com.example.DetectSpringSecurityHeaderSuppression
    displayName: Detect CVE-2026-22732
    recipeList:
      - io.moderne.recipe.cve202622732.FindSpringSecurityHeaderSuppression
    

    Reproducing the evaluation corpus

    repos.csv lists the 75 public repositories these recipes were developed and measured against, pinned to the exact commit each was evaluated at. Several are actively maintained and will be patched upstream, so the changeset column is what makes the numbers below reproducible rather than merely plausible.

    root@kitploit:~
    mod git sync csv ./corpus repos.csv --with-sources
    mod build ./corpus
    mod run ./corpus --recipe=io.moderne.recipe.cve202622732.CveDevCenter
    mod devcenter ./corpus --last-recipe-run
    

    The sync takes about 20 seconds and 1.4 GB; the build takes roughly 15 minutes and is the only slow step. mod devcenter writes devcenter.html into corpus/.moderne/run/<runId>/, plus one per organisation subdirectory.

    The org1 column splits the corpus into groups that reflect why each repository is present — Servlet Sinks and WebFlux Sinks for the two vulnerable call shapes, Patched for repositories already remediated upstream, Reference for Spring Security itself and other non-consumers, Verified for the case checked against a running server, Gradle for build-tool coverage, and Wide for the bulk sample.

    Expect, on the pinned commits:

    Result
    Upgrade card39 Major, 21 Minor, 6 Patch, 4 Completed (70 repositories)
    Security card65 exposed repositories
    Not applicable5 repositories resolve no Spring Security dependency

    Four of those five genuinely do not use Spring Security — spring-projects/spring-security is the library itself, JoeyBling/bootplus uses Apache Shiro, jenkinsci/stapler targets the servlet API directly, and infofabrik/reportserver has no Maven or Gradle build to resolve. The fifth, xtuer/template-app, declares spring-security-web:5.0.0.RELEASE but its Gradle build resolves no dependencies at all during mod build, so no recipe can see the version. Treat it as unmeasured rather than unaffected.

    To apply the fix and check the result:

    root@kitploit:~
    mod run ./corpus --recipe=io.moderne.recipe.cve202622732.FixSpringSecurityHeaderSuppression
    mod git apply ./corpus --last-recipe-run
    mod exec ./corpus --last-recipe-run MODERNE_BUILD_TOOL_CHECK
    

    mod git apply writes to the checkouts in place. A full check across the corpus is slow and will surface failures unrelated to this change — missing JDK toolchains, unreachable dependency repositories, tests that were already red — so the meaningful signal is the delta against the same command run before applying.

    What's covered beyond the literal demo

    The taint analysis from rewrite-program-analysis handles local dataflow and per-method summaries, so these patterns are detected automatically:

    • Constant-propagated Content-Length header name. String h = "Content-Length"; response.setIntHeader(h, 42); is flagged — the framework tracks the literal taint through the local assignment.
    • Helper that wraps the call — taint flows through return values via method summaries.

    Known limitations

    • Flow through generic container types (Map, List, custom collections). stash.put("k", "Content-Length") followed by response.setIntHeader(stash.get("k"), 42) is not detected — the put/get identity is opaque to the analysis.

    License

    Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.

    Download Tool