fix(middleware): fail closed for unsafe matchers - #2078
Conversation
|
/bigbonk review for issues |
commit: |
|
There was a problem hiding this comment.
Review: fail closed for unsafe middleware matchers
Reviewed the change end-to-end (source, tests, behavior parity, dev/prod paths). This is a solid, well-targeted security fix. Verified locally: vp check clean on both source files, and all 21 middleware matcher tests plus the new middleware runner test pass.
What works well
- Correct fix for the real bug. Previously a rejected unsafe matcher fell through
safeRegExp→null→ literalpathname === patterncomparison. For regex-style sources that literal comparison can never match the request path, so middleware was silently skipped — bypassing request guards. Routing the rejection through the newUNSAFE_MATCHER_PATTERNsentinel somatchPatternreturnstrue(run middleware for all paths) is the right fail-closed behavior. - Good separation of the two failure modes. Distinguishing unsafe regex (
isSafeRegexfalse → fail closed) from malformed syntax (safeRegExpreturnsnull→ literal fallback) is the key insight, and it's covered directly by thedoes not run globally for malformed regex syntaxtest (matchPattern("/public", "/admin(") === false). This avoids an unintended global middleware expansion for typo'd patterns. - Scope is correctly limited to middleware. Config-level
safeRegExpconsumers (headers/redirects/rewrites inconfig-matchers.ts) intentionally keep returningnull(fail-open / no-match). The onlyconfig-matchers.tschange is the cosmetic warning wording ("Ignoring" → "Rejecting"), which no test depends on. Good call not to change config fail-open semantics in this PR. - Test coverage matches the PR description. Direct (
matchPattern), cached (warn-once assertion), array, object-source, and full runtime (runMiddlewarereturning the 403) paths are all exercised. The cache-rejection path correctly verifieswarnSpyis called exactly once. - Array/object matcher semantics are sound. An unsafe string matcher short-circuits to
true; an unsafe objectsourcematches the path but still honorshas/missinggating. That's a reasonable fail-closed-but-still-conditional behavior.
Minor (non-blocking) observations
- Redundant
isSafeRegexevaluation.compileMatcherRegExpcallsisSafeRegex(regexPattern)and then, on the safe path, callssafeRegExp(regexPattern)which runsisSafeRegexagain internally. Results are cached per pattern in_mwPatternCache, so this only costs one extra scan on first compile — harmless, but could be tidied if you want to avoid the double pass. - Warning wording divergence. The middleware warning says "Rejecting … middleware matcher" and the config warning says "Rejecting … regex pattern". That's intentional and reads fine; just flagging that the two messages now share the "Rejecting" verb but describe different fallback behavior (run-for-all vs. ignore).
Verification
vp check packages/vinext/src/server/middleware-matcher.ts packages/vinext/src/config/config-matchers.ts→ clean (format + lint + types)vp test run tests/shims.test.ts -t "middleware matcher"→ 21 passedvp test run tests/shims.test.ts -t "unsafe"→ 5 passed
LGTM. No blocking issues.
|
Review posted to PR #2078. SummaryI reviewed PR #2078 ( Verdict: LGTM, no blocking issues. Key findingsThe fix correctly addresses a real security gap:
What I verified:
Only two non-blocking notes: a redundant |
Summary
Why
A matcher rejected as potentially unsafe previously fell back to a literal comparison. For regex-style matcher sources, that comparison could never match the request pathname, so middleware could be skipped entirely.
Rejected unsafe matchers now conservatively execute middleware for every path. Malformed regex syntax retains the existing literal-match fallback, avoiding an unrelated global middleware expansion.
Testing
vp test run tests/shims.test.tsvp check packages/vinext/src/server/middleware-matcher.ts packages/vinext/src/config/config-matchers.ts tests/shims.test.tsgit diff --check