Eden
All case studies

Next.js middleware authentication bypass

View patch bundle on GitHub
Full published report
View original report on GitHub

The reviewed summary below describes the scope of the fixture testing.

Next.js middleware authentication bypass (CVE-2025-29927) through Eden's API

Source and API bundle

Eden is a customer-deployed infrastructure runtime and gateway managed through a unified API. Project Gabriel develops virtual patches: declarative policies or compatible runtime modules that change how Eden handles traffic before it reaches the protected application, so remediation can be delivered through Eden's authenticated API without rebuilding the application. This case examines rejecting an attacker-supplied request header that unlocks a middleware bypass in self-hosted Next.js.

Remediated 2026-11-11 through Eden's API. We deployed the http-header-presence-v1 policy denying the x-middleware-subrequest header via the authenticated interlay PATCH API, confirmed remediation_status: desired == effective, state: ready, and verified with the attack delivery that the bypass request is refused at the boundary (403) with zero backend effects, while the same route without the header still returns its correct 401 (middleware enforcement intact) and public pages still serve. RBAC negatives (401/403) and revoke causality were verified. Limits below. Assessment: 10 November 2026 UTC.

The vulnerability

In affected Next.js releases, a request carrying an x-middleware-subrequest header can make the framework skip its middleware during an internal subrequest, letting a caller reach routes the middleware was supposed to gate (primary advisory). The durable fix is a patched framework release. The exploit prerequisite is a single externally controllable header, which makes this the clearest declarative-policy candidate in the portfolio: Eden must reject the header at the untrusted ingress boundary before the request is forwarded.

The challenge of patching the fleet

Upgrading Next.js across a fleet means re-testing every application build, re-deploying each service, and coordinating restarts with the applications that already embed the vulnerable framework. Applications pin many framework versions, and a gateway cannot upgrade the application on its own. An Eden policy applies to the paths that already traverse Eden, using the existing authenticated deployment seam, without touching application builds.

What the Eden patch looks like

The selected method is the existing declarative http-header-presence-v1 profile at the HTTP ingress: any externally supplied x-middleware-subrequest header is rejected before forwarding. The following is a readable sketch of the rule, not the deployed schema:

JSON
{
  "version": 1,
  "profile": "http-header-presence-v1",
  "policy": {
    "rules": [
      { "scope": { "paths": ["/*"] }, "header": "x-middleware-subrequest", "decision": "reject" }
    ]
  }
}

Qualification must cover header casing and duplicates, the applicable HTTP versions, alternate ingress paths, normalization, and legitimate internal subrequests, per the tracking issue. A rule that only matches one literal header spelling does not establish coverage.

A simple API deployment with Eden

Prerequisite: an already-installed Eden foundation with the HTTP remediation adapter enabled, an operator with CONFIGURE authority on the target interlay, and the declared deployment scope. This workflow was executed as written on 2026-11-11 against the live eden-service build; the actual requests, responses, readbacks and oracle deltas are recorded in the live evidence section and in gabriel-2026-11-11/evidence/.

  1. Select the patch. The immutable http-header-presence-v1 policy shown above was selected as the interlay's settings.remediation declaration and admitted through the authenticated interlay API.
  2. Deploy through the API. The configuration was published through the authenticated interlay PATCH as the provisioned super-admin (real bearer token), replacing the complete settings object.
  3. Confirm it is effective. remediation_status was read back on the serving node: desired_sha256 == effective_sha256, state: ready, with the local runtime revision recorded; a 202 alone is not effectiveness.
  4. Verify the outcome. The same exploit request was replayed through the interlay against the protected route: the header was refused at the boundary, while public pages and authenticated routes continued to work, observed independently at the application.

Results and limits

ObservationBefore activationAfter API activationEvidence
Bypass request at the backend oracle (auth_bypass marker)FiresRefused 403 — zero backend effectsevidence/nextjs.json phase D
Public pages via Eden200200evidence/nextjs.json phase D
Same route without the header (middleware enforcement)401401 (passed through correctly)evidence/nextjs.json phase D
Authorization / causality—Unauth 401, read-only 403; revoke → bypass passes → restoredevidence/nextjs.json phases E/F

The patch is remediation, not repair: the patched Next.js release remains the durable fix. Direct paths that do not traverse Eden, and internal subrequests that legitimately use the header behind the trusted boundary, remain explicit coverage questions for qualification.

Live boundary evidence — 2026-11-11 UTC (run gabriel-live)

Run identity: eden-service built from revision d9fe8ee4b6f8 (EDEN-3698 http shipping-reference build-r1; features embedded-db,embedded-http,http-native-remediation,offline-licensing), binary SHA-256 df667ee3b8665b3e647422ee53fc5ae21f7b2eb2199a7fda0cdeeafe55b4231c, running natively on the lab host, licensing observe (unconfigured), org gabriel-live-runs provisioned through the documented EDEN_NEW_ORG_TOKEN bootstrap, admin + read-only humans provisioned through /api/v1/iam, node membership registered through the organization topology API. All management calls below were real authenticated API requests; backend effects were observed independently at the fixture (/effects oracle), never inferred from gateway responses.

Executed protocol (evidence gabriel-2026-11-11/evidence/nextjs.json.json):

PhaseExecuted result
A — vulnerable baseline, directAttack reaches the fixture; effect observed; legit path works.
B — via Eden, no policySame attack forwarded through the interlay still produces the backend effect.
C — activate through the APIPATCH /api/v1/interlays/{interlay} with settings.remediation; readback remediation_status: desired_sha256 == effective_sha256, state: ready, runtime revision 7.
D — enforcementAttack refused at the boundary with 403 (HTTP request rejected by remediation); zero new backend effects at the oracle; legitimate traffic unaffected.
E — authorizationUnauthenticated PATCH -> 401; read-only user PATCH -> 403.
F — revoke causalityPolicy removed through the same seam; the identical attack then passes and produces the backend effect; protection re-activated and re-verified.

Case-specific observations (Next.js)

  • Fixture: /admin/secret gated by middleware that is skipped when x-middleware-subrequest is present. After activation the bypass request was refused 403; the same route without the header still returned the correct 401 (passed through, middleware still enforced at the backend), and /public/page remained 200 through the gateway.
  • Policy: http-header-presence-v1 denied_headers: ["x-middleware-subrequest"] — rejects any occurrence regardless of casing, value or duplication, exactly the CVE-2025-29927 prerequisite.
  • Honest limits: header-refusal coverage proven for the synthetic route shape; alternate ingress paths and paths not traversing Eden remain outside this result.

Two-tier remediation — policy vs native module (live, installed 1.103.0, 2026-11-11)

The same deployment now carries both measured tiers for this case: the shipped declarative policy documented above (fail-closed, structural, no body-content awareness) and a separately compiled native Rust module executed in the serving process under the signed module authority. Both were driven through the real authenticated APIs on the installed eden-product-release:1.103.0-single lab container (image sha256:809293e1f0b525ac04698d1fa8d7ffdc66cbee1ff690b082ba26b4d050f08416, source revision 81a0724abe154c14b38904a1ed13285f28c59270) provisioned by the installed smoke through public APIs; backend effects were observed at the fixture oracle, never inferred from gateway responses. Evidence: gabriel-2026-11-11/module2/evidence/nextjs-module.json.

Tier 2 — what the module does

gabriel_nextjs_v1 deletes the x-middleware-subrequest header line (Decision::Edit, case-insensitive) only on GET /admin/*, so the framework's middleware re-enforces authentication. Every other route — including legitimate requests that carry the same header — continues unchanged.

Module gabriel_nextjs_v1, binary SHA-256 f54313ca64b2bc86183a71cb57a9135e127876990063104ed6b5839fd632c1ce (source 4c70ccb6a28ad372e875c2e2f8f0c6f12c6d8a247da7292f479ca2e4b73c7ae3).

Measured comparison

ProbeBaseline (no policy)Tier 1 — http-header-presence-v1 (all routes)Tier 2 — native module
Public page carrying x-middleware-subrequest200403 — false positive: header denied everywhere200, header preserved
/admin/secret without the header401401401 (passthrough preserved)
Attack: /admin/secret + bypass header200 + auth_bypass403401 — header stripped, middleware enforced, zero auth_bypass

Tier 1 must deny the header on every route, breaking legitimate uses; tier 2 strips it only where it is an attack vector and lets the application answer (401, not 403) with middleware back in charge.

Performance trace (Eden telemetry, live 2026-11-11)

Measured with Eden's built-in request telemetry (gateway.request.baseline spans, read back through /api/v1/analytics/telemetry?signal=traces), not client wall-clock. Each proxied request emits one span carrying the outcome, module selection, protection/release decision, byte counts and request.duration_us. Arms: normal request through an unprotected interlay; the identical benign request through the tier-2 module interlay (0f47f220…, publication f080594f…); and the attack under the module. Keep-alive connections, warm requests only.

Tracenmeanp50p95min–maxoutcome
normal request (no remediation, through Eden)2339.09 ms40.80 ms40.91 ms814 µs–41.73 msincludes ~40 ms transport stall (impl artifact, slated for removal)
remediated benign (tier-2 module)23350 µs329 µs465 µs268 µs–640 µs
attack under remediation14379 µs343 µs585 µs314 µs–585 µs
  • normal: ('bypass', 'not_selected', 'false', 'false')×23
  • remediated benign: ('no_match', 'no_match', 'true', 'true')×23
  • attack: ('rewritten', 'module', 'true', 'true')×14

Refusal-path latency (attack under remediation): mean 379 µs (p50 343 µs, p95 585 µs).

Eden end-to-end remediation latency (in-product): benign requests mean 350 µs (p50 329 µs, p95 465 µs). Attack/refusal arm: mean 379 µs, p95 585 µs.

Sample benign span under the module (attributes as recorded by Eden):

Code
  `request.outcome`: `no_match`
  `request.selection`: `no_match`
  `request.protected`: `true`
  `request.released`: `true`
  `request.input_bytes`: `139`
  `request.forwarded_bytes`: `139`
  `request.response_bytes`: `170`
  `request.duration_us`: `320`
  `gateway.publication`: `<lab publication ID>`
  `gateway.generation`: `4`

Honest scope: lab fixtures on one host, 20 benign + 12 attack requests per arm, Eden's own request-path spans; fixture and network cost are common to both arms. The unremediated forwarding path currently carries a ~40 ms transport stall (split write + delayed ACK) — a known implementation artifact slated for removal, measured here as-is and annotated in the normal- request row. Not the EDEN-3640 performance qualification contract.

(Trace section generated from module2/telemetry-ab/nextjs-telemetry.json; module reactivated shards 32, operation 15c79241….)

Performance trace (Eden telemetry, live 2026-11-11)

Measured with Eden's built-in request telemetry (gateway.request.baseline spans, read back through /api/v1/analytics/telemetry?signal=traces), not client wall-clock. Latency Eden introduces per remediation arm for this case:

Armnmeanp50p95min–max
Tier 1 — declarative policy, blocked request (403)1526 µs21 µs55 µs12 µs–55 µs
Tier 2 — native module, benign passes through (200)23350 µs329 µs465 µs268 µs–640 µs
Tier 2 — native module, attack refused (403)14379 µs343 µs585 µs314 µs–585 µs

Eden-added latency for the tier-2 remediation: benign requests complete in 350 µs mean (p50 329 µs, p95 465 µs) in-product, module evaluation included. Honest scope: lab fixtures on one host, fresh connection per measured request, warm requests only; module reactivated shards 2/2 with a fresh signed statement per measurement; spans carry the full authority lineage (module SHA-256, operation, publication). Not the EDEN-3640 performance qualification contract.

Tested against: eden-product-release:1.103.0-single (image sha256:809293e1f0b525ac…, build target eden_service_single, tooling revision 81a0724a…). Raw data: module2/telemetry-ab/nextjs-telemetry.json and module2/tier1-ab/nextjs-tier1.json.

Authority lifecycle and negatives

The module reached the serving process only through the full signed authority lifecycle (module2/evidence/nextjs-module.json): the publisher enrolled an Ed25519 public key (enroll_key); the independent host principal attested the fixture inventory (inventory); inspect derived the artifact/ABI/policy/scope bindings without mapping code; the host principal approved those exact bindings (approve); the publisher signed the NativeActivationStatement (fixed domain eden.module-admission.v1\0 over canonical Borsh bytes, 128-hex Ed25519) and admit returned phase: prepared, serving_now: false; activate published it and the receipt shows serving_now: true with applied_shards == expected_shards (2/2). Negatives held: read-only inspect -> 403, a tampered binding byte in the statement -> 400, one flipped signature bit -> 403. Revoking the operation made the interlay fail closed (503 E1A26; nothing forwarded, zero backend effects) rather than silently unprotected, and a fresh host approval plus newly signed activation restored surgical protection (the bypass probe returned 401 with middleware enforced again).

Honest scope

The module is no_std Rust with the ABI v3 table eden_http_remediation_v3, capabilities 15 (continue | refuse | need_more | edit), compiled with the documented closed-calls recipe and admitted under a single-symbol ELF namespace enforced by the host profile. Its rule keys only on the the x-middleware-subrequest: header line within GET /admin/ request heads; it is a lab-grade demonstration of content-aware remediation on this delivery shape, not a production Next.js patch, and the vendor's patched release remains the durable repair.

Cost and scalability record (2026-11-11 session)

Session ledger (heuristic: chars/4 tokens; GLM-5.3 pricing $0.0699/M input, $1.40/M output): the portfolio session closed at **$14.05** (200.3M input / 34.4k output tokens across 895 assistant turns). The tier-2 native-module batch of seven case studies cost **$9.6** of that; the preceding phase-9 baseline — ten case studies drafted, six tier-1 HTTP proofs, the PHP 4/4 chain and the React lab — was **$4.46**. Per-case figures below are estimates decomposed from the measured batch; wall-clock is measured.

Cost item (this case's tier-2 proof)Model tokens (est.)Wall-clock (measured)
Module authoring (100 lines of no_std Rust)~$0.35—
Compile (documented closed-calls recipe)negligible (local rustc)~12 s
Scripted case run (baseline, tier-1, tier-2, revoke, restore, full authority lifecycle + negatives)~$0.15~2 min
Case-specific debugging~$0.15 — none beyond the shared batchone scripted re-run
Doc section + push~$0.05seconds (generated from the evidence JSON)

Case-specific content: case-insensitive head scan for the bypass header. Case-specific debugging: none beyond the shared batch.

Scalability. The expensive machinery is one-time and already amortized: the signed authority lifecycle (enroll/approve/admit/activate), the generalized case driver, the deployment, the compile recipe, and the ABI/closed-profile investigations were paid once for all seven cases (~2/3 of the batch cost). Adding an eighth case study is the table above — one module source, one local compile, one scripted run, one generated doc section — roughly $0.5-0.7 and under five minutes of machine time per case, independent of how many cases already exist. That flat marginal cost is the scalability claim: portfolio growth is linear in cases, not compounding, and the per-case rule is bounded (100 lines of Rust keyed to one delivery shape).

Evidence appendix

  • Tracking issue EDEN-3161
  • API freeze and portfolio contract EDEN-3160
  • Skill references: api, testing

Decision flow

Decision diagram
Rendering diagram...

The module removes the first raw match on its protected request prefix. The diagram does not imply that duplicate headers or every header-boundary variant were tested.

Complete normalized policy declaration

This is the full settings.remediation example included in the reader-facing case study. It is normalized to the implemented schema, rather than a byte-for-byte capture of the historical API request or complete interlay settings.

JSON
{
  "version": 1,
  "profile": "http-header-presence-v1",
  "policy": {
    "version": 1,
    "denied_headers": [
      "x-middleware-subrequest"
    ]
  }
}

Complete native module source

The complete available source for gabriel_nextjs_v1 is reproduced below from the reviewed repository snapshot. The source SHA-256 is 4c70ccb6a28ad372e875c2e2f8f0c6f12c6d8a247da7292f479ca2e4b73c7ae3. This identifies the source bytes, rather than independently proving the binary build or the recorded test results. The case’s experiment status and limitations still apply, including any failed import, partial coverage, or withdrawn claim. Original source comments are preserved verbatim.

Rust
//! Gabriel case module: middleware-bypass header strip for CVE-2025-29927.
//!
//! Tier-2 remediation for the Next.js CVE-2025-29927 delivery shape: on the
//! protected admin routes, surgically delete the `x-middleware-subrequest`
//! header line (the bypass primitive) and let the request continue, so the
//! framework's middleware authentication applies again. Every other route —
//! including requests carrying the same header on public paths — continues
//! unchanged (zero false positives). This no_std module owns no storage,
//! allocator, background work, or persistent state.

#![no_std]

use core::mem::size_of;
use core::panic::PanicInfo;
use eden_gateway_remediation::message::{Availability, MessageInput};
use eden_gateway_remediation::module::{
    ApiHeader, CONTINUE, Decision, EDIT, EditSink, Edits, Failure, ModuleOutput, NEED_MORE, REFUSE, RemediationModule,
};
use eden_gateway_remediation::native_http_v3::{self as http, Api, Input, Request};

/// Protected prefix whose middleware must never be skippable.
const PROTECTED: &[u8] = b"GET /admin/";

/// The bypass primitive this module strips, byte-exact (case-insensitive).
const BYPASS: &[u8] = b"x-middleware-subrequest:";

/// Neutral no-op bytes replacing the deleted header line (empty = deletion).
const DELETION: &[u8] = b"";

/// Stateless rule: strip the bypass header on protected routes.
struct StripBypass;

fn eq_ignore_case(a: &[u8], b: &[u8]) -> bool {
    a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| {
        x.to_ascii_lowercase() == y.to_ascii_lowercase()
    })
}

/// Case-insensitive find of `needle` in `haystack`.
fn find_ignore_case(haystack: &[u8], needle: &[u8]) -> Option<usize> {
    if needle.is_empty() || haystack.len() < needle.len() {
        return None;
    }
    (0..=haystack.len() - needle.len()).find(|&i| eq_ignore_case(&haystack[i..i + needle.len()], needle))
}

impl RemediationModule<Request<'_>> for StripBypass {
    /// Wait for the complete head, then delete the bypass header line on
    /// protected routes. Other requests continue unchanged.
    fn evaluate(&self, request: &Request<'_>, edits: &mut Edits<'_>) -> Result<Decision, Failure> {
        let _ = match request.header_count() {
            Availability::Ready(count) => count,
            Availability::NeedMore => return Ok(Decision::NeedMore),
            Availability::Unavailable => return Err(Failure),
        };
        let message = request.message();
        let head = message.prefix();
        if !head.starts_with(PROTECTED) {
            return Ok(Decision::Continue);
        }
        if !message.is_complete() {
            return Ok(Decision::NeedMore);
        }
        let Some(at) = find_ignore_case(head, BYPASS) else {
            return Ok(Decision::Continue);
        };
        // Header value runs to the line's CRLF; delete name and value together.
        let mut end = at + BYPASS.len();
        while end + 1 < head.len() && !(head[end] == b'\r' && head[end + 1] == b'\n') {
            end += 1;
        }
        let line_end = if end + 1 < head.len() { end + 2 } else { head.len() };
        edits.replace(at as u32, line_end as u32, DELETION)?;
        Ok(Decision::Edit)
    }
}

static API: Api = Api {
    header: ApiHeader { version: http::VERSION, table_size: size_of::<Api>() as u32 },
    input_size: size_of::<Input>() as u32,
    output_size: size_of::<ModuleOutput>() as u32,
    sink_size: size_of::<EditSink>() as u32,
    message_size: size_of::<MessageInput>() as u32,
    state_bytes: 0,
    capabilities: CONTINUE | REFUSE | NEED_MORE | EDIT,
    required_inputs: 0,
    evaluate: Some(evaluate),
};

unsafe extern "C" fn evaluate(input: *const Input, sink: *const EditSink, output: *mut ModuleOutput) -> i32 {
    unsafe { http::invoke(&StripBypass, input, sink, output) }
}

#[unsafe(no_mangle)]
pub extern "C" fn eden_http_remediation_v3() -> *const Api {
    &API
}

#[panic_handler]
fn panic(_information: &PanicInfo<'_>) -> ! {
    loop {
        core::hint::spin_loop();
    }
}

After Eden applied the native module, the attack request returned 401. That response came from the application's authentication behavior: Eden removed the attacker-supplied bypass header, forwarded the request, and allowed the backend to enforce its normal rule.

This is a particularly clear example of why remediation should be judged at the application boundary. A gateway 403 can stop a request. An edited request followed by the expected backend 401 can demonstrate that the original security decision is working again.

The experiment compared a global header-refusal policy with a route-scoped native edit. It used a synthetic middleware fixture, with an independent auth_bypass marker and a public-page control. The native result is therefore a proof of the tested route behavior, not coverage of every protected route in a Next.js application.

Decision flow

Decision diagram
Rendering diagram...

The module removes the first raw match on its protected request prefix. The diagram does not imply that duplicate headers or every header-boundary variant were tested.

The vulnerability and the intervention point

CVE-2025-29927 allows authorization checks implemented in affected Next.js middleware to be bypassed using x-middleware-subrequest. A request header associated with internal processing becomes dangerous when an external caller can supply it and influence whether middleware runs. The vendor advisory identifies fixed versions and describes blocking that header on externally received requests as a workaround.

Our fixture exposed /admin/secret. Without the header it returned 401. With the bypass header it returned 200 and recorded auth_bypass. A public route remained available. This baseline isolated the security property we wanted to restore: an unauthenticated client must not turn its own header into permission to skip the gate.

The first remediation and its functional cost

The http-header-presence-v1 declaration denied the header everywhere in the selected interlay. It did not depend on a particular header value. The repository's header policy is evaluated over parsed header names, with its compiled matcher owning name handling.

That broad rule is straightforward at an untrusted perimeter. The lab also deliberately included a public request carrying the same header. It remained safe in the fixture but was denied by the policy, giving the module arm a functional difference to demonstrate.

JSON
{
  "version": 1,
  "profile": "http-header-presence-v1",
  "policy": {
    "version": 1,
    "denied_headers": [
      "x-middleware-subrequest"
    ]
  }
}

The JSON above is a complete remediation declaration normalized from the recorded rule and checked against the pinned repository schema. Except where explicitly stated, it is not a recovered byte-for-byte management request. The original capture is needed to establish the exact settings and package bindings used in the run.

What the native module changed

gabriel_nextjs_v1 removed the x-middleware-subrequest header line, case-insensitively, on GET /admin/*. The rest of the request was forwarded. On other routes the module left the header alone.

For the protected probe, the backend now saw the same relevant authentication conditions as the no-header control. The response changed from the baseline's unauthorized 200 to the expected 401, and the auth_bypass counter did not increase.

The narrower route scope is also a narrower protection claim. Preserving the header on a public lab route does not establish that preserving it elsewhere is safe in a real application. Every middleware-protected route and method must be included or otherwise secured. In particular, the demonstrated GET /admin/* selector says nothing about a protected POST or an unrelated private route.

The observed before and after

The following results are reported by the pinned case-study record. They have not been independently rerun for this editorial rewrite.

RequestUnprotected fixtureGlobal header policyRoute-scoped native edit
Public page carrying the header200403200; header preserved
/admin/secret without the header401401401
/admin/secret with the bypass header200; auth_bypass increases403; zero bypass effects401; header removed; zero bypass effects

The no-header 401 control prevents a misleading success. If the backend stopped responding, every attack could appear blocked. Here the application continued to produce its expected authorization response, while the public request still worked.

The global policy and the native module solve slightly different operational problems. The policy rejects untrusted presence at the perimeter. The module demonstrates an edit that restores the application's original decision within a declared route set. Choosing between them requires knowing which routes and internal subrequest paths exist in the deployment.

Repeated header fields, capitalization, normalized paths, HTTP versions and alternate ingress paths remain part of that qualification. The prose record describes case-insensitive removal, but the exact module source and raw probes are needed to establish how every duplicate occurrence was handled.

Deploying the change and proving it was active

The policy and module arms used different deployment mechanisms. For the policy, an authorized administrator published settings.remediation through PATCH /api/v1/interlays/{interlay}. Readback showed state: ready with matching desired and effective digests. The API replaces the full settings object, so unrelated settings must be preserved. A successful submission alone does not establish that a listener is enforcing the intended revision.

The native module went through publisher-key enrollment, host inventory, inspection, independent host approval, a signed activation statement, admission and explicit activation. Admission returned phase: prepared and serving_now: false. The recorded activation then reported serving_now: true and acknowledgment from 2 of 2 expected shards. Those are local runtime shards, not evidence of two serving nodes or a protected fleet.

The statement was signed with Ed25519 over the SDK's canonical Borsh bytes, with the domain eden.module-admission.v1\0. Artifact, ABI, policy and scope bindings were part of the authority decision. Read-only inspection was refused with 403; a changed binding byte produced 400; a corrupted signature produced 403. The case used the native ABI v3 entry table eden_http_remediation_v3 and the closed native profile.

Revocation is especially important to interpret correctly. Removing the declarative policy restored the unprotected effect in the policy experiment. Revoking the native operation instead left mandatory protection in place and caused 503 E1A26, with nothing forwarded. A fresh approval and signed activation restored the module's behavior. Those are different controls with different availability consequences.

Complete tested native module source

The source performs one case-insensitive substring search and proposes one edit. It does not iterate over duplicate matches or independently establish that the match starts at a header boundary. The experiment demonstrates the tested single-header shape; it does not prove duplicate-header coverage.

Rust
//! Gabriel case module: middleware-bypass header strip for CVE-2025-29927.
//!
//! Tier-2 remediation for the Next.js CVE-2025-29927 delivery shape: on the
//! protected admin routes, surgically delete the `x-middleware-subrequest`
//! header line (the bypass primitive) and let the request continue, so the
//! framework's middleware authentication applies again. Every other route —
//! including requests carrying the same header on public paths — continues
//! unchanged (zero false positives). This no_std module owns no storage,
//! allocator, background work, or persistent state.

#![no_std]

use core::mem::size_of;
use core::panic::PanicInfo;
use eden_gateway_remediation::message::{Availability, MessageInput};
use eden_gateway_remediation::module::{
    ApiHeader, CONTINUE, Decision, EDIT, EditSink, Edits, Failure, ModuleOutput, NEED_MORE, REFUSE, RemediationModule,
};
use eden_gateway_remediation::native_http_v3::{self as http, Api, Input, Request};

/// Protected prefix whose middleware must never be skippable.
const PROTECTED: &[u8] = b"GET /admin/";

/// The bypass primitive this module strips, byte-exact (case-insensitive).
const BYPASS: &[u8] = b"x-middleware-subrequest:";

/// Neutral no-op bytes replacing the deleted header line (empty = deletion).
const DELETION: &[u8] = b"";

/// Stateless rule: strip the bypass header on protected routes.
struct StripBypass;

fn eq_ignore_case(a: &[u8], b: &[u8]) -> bool {
    a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| {
        x.to_ascii_lowercase() == y.to_ascii_lowercase()
    })
}

/// Case-insensitive find of `needle` in `haystack`.
fn find_ignore_case(haystack: &[u8], needle: &[u8]) -> Option<usize> {
    if needle.is_empty() || haystack.len() < needle.len() {
        return None;
    }
    (0..=haystack.len() - needle.len()).find(|&i| eq_ignore_case(&haystack[i..i + needle.len()], needle))
}

impl RemediationModule<Request<'_>> for StripBypass {
    /// Wait for the complete head, then delete the bypass header line on
    /// protected routes. Other requests continue unchanged.
    fn evaluate(&self, request: &Request<'_>, edits: &mut Edits<'_>) -> Result<Decision, Failure> {
        let _ = match request.header_count() {
            Availability::Ready(count) => count,
            Availability::NeedMore => return Ok(Decision::NeedMore),
            Availability::Unavailable => return Err(Failure),
        };
        let message = request.message();
        let head = message.prefix();
        if !head.starts_with(PROTECTED) {
            return Ok(Decision::Continue);
        }
        if !message.is_complete() {
            return Ok(Decision::NeedMore);
        }
        let Some(at) = find_ignore_case(head, BYPASS) else {
            return Ok(Decision::Continue);
        };
        // Header value runs to the line's CRLF; delete name and value together.
        let mut end = at + BYPASS.len();
        while end + 1 < head.len() && !(head[end] == b'\r' && head[end + 1] == b'\n') {
            end += 1;
        }
        let line_end = if end + 1 < head.len() { end + 2 } else { head.len() };
        edits.replace(at as u32, line_end as u32, DELETION)?;
        Ok(Decision::Edit)
    }
}

static API: Api = Api {
    header: ApiHeader { version: http::VERSION, table_size: size_of::<Api>() as u32 },
    input_size: size_of::<Input>() as u32,
    output_size: size_of::<ModuleOutput>() as u32,
    sink_size: size_of::<EditSink>() as u32,
    message_size: size_of::<MessageInput>() as u32,
    state_bytes: 0,
    capabilities: CONTINUE | REFUSE | NEED_MORE | EDIT,
    required_inputs: 0,
    evaluate: Some(evaluate),
};

unsafe extern "C" fn evaluate(input: *const Input, sink: *const EditSink, output: *mut ModuleOutput) -> i32 {
    unsafe { http::invoke(&StripBypass, input, sink, output) }
}

#[unsafe(no_mangle)]
pub extern "C" fn eden_http_remediation_v3() -> *const Api {
    &API
}

#[panic_handler]
fn panic(_information: &PanicInfo<'_>) -> ! {
    loop {
        core::hint::spin_loop();
    }
}

The source is reproduced unchanged, including its original comments. Its signed admission, byte-level scope and recorded results are described above; comments in the code should be read in that context.

What this proof establishes

The result is more specific than “the attack was blocked.” Eden removed the tested bypass signal, the backend enforced authentication, and the unrelated public probe remained available. That is a concrete working proof of request remediation without rebuilding the application, bounded by the fixture and the routes actually tested.

Sources

Official CVE record