Skip to content

review remediation: middleware lifecycle contract — cache route-scoped string middleware (stateful reset) + cache preflight-capability boolean #2954

Description

@bpamiri

Problem

Route-scoped string middleware is re-instantiated on every request, and the preflight-capability scan re-runs per OPTIONS request even though the pipeline is fixed at $init. Line numbers verified against origin/develop @ 8971094.

1. Route-scoped string middleware re-instantiated per request — stateful middleware silently reset (R15 / MA12, High)
vendor/wheels/Dispatch.cfc:9, :61, :298-313, :377-401 — global middleware is built once at $init ($buildMiddlewarePipeline), but route-scoped string middleware (the documented form) is re-instantiated via CreateObject("component", mw).init() per request, a new Pipeline is built per request, and regex routes also re-Duplicate instance middleware. Consequence: a route-scoped in-memory RateLimiter gets a fresh empty store each request and never enforces — any stateful middleware is silently reset.

2. $hasPreflightCapableMiddleware re-runs IsInstanceOf over the pipeline on every OPTIONS request (R19 / MA15, Low)
vendor/wheels/Dispatch.cfc:298, :393-401 — the IsInstanceOf scan over the middleware pipeline runs on every OPTIONS request, but the pipeline is fixed at $init, so the boolean is invariant.

Impact

  • Route-scoped stateful middleware (the documented registration form) is non-functional: a per-route in-memory RateLimiter never accumulates counts and never enforces. This is a correctness bug masquerading as a perf cost and interacts with the feat(testing): add browserLoginAsHandler override for /_browser/login-as fixture #2832 init() gotcha and SecurityHeaders HSTS defaults.
  • Per-request instantiation + Pipeline rebuild + regex-route Duplicate are avoidable allocations on the dispatch hot path.

Suggested approach

This needs a deliberate singleton-vs-per-request contract for middleware, not an autonomous hot-path edit:

  • Decide and document whether route-scoped string middleware is cached (singleton) or per-request, including the stateful-middleware implications and migration notes (interacts with feat(testing): add browserLoginAsHandler override for /_browser/login-as fixture #2832 init() requirement and HSTS defaults).
  • Cache resolved route-scoped middleware instances in application scope (cleared on reload); exclude middleware from the per-request route Duplicate.
  • Compute the preflight-capable boolean once in $buildMiddlewarePipeline and cache it at $init.
  • Benchmark the dispatch hot path before/after.

Acceptance criteria

  • A route-scoped in-memory RateLimiter enforces across requests (state survives).
  • Documented contract for middleware lifecycle (singleton vs per-request) with migration notes.
  • Route-scoped instances cached in application scope, cleared on reload; no per-request Duplicate of middleware.
  • Preflight-capability boolean computed once at $init.
  • Middleware + dispatch specs green cross-engine; benchmark recorded.

Source

Internal multi-agent framework review 2026-06-09, wave 2 (issues phase). Findings: routing R15/R19, middleware-auth MA12/MA15.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions