{
  "variant_id": "CVE-2026-14198-middie-variant-analysis-001",
  "created_at": "2026-07-02T17:45:00Z",
  "variant_summary": "Negative variant result. Bounded, exhaustive search for a distinct variant or bypass of the @fastify/middie 9.3.3 fix for CVE-2026-14198 (encoded-slash authz bypass on parameterized middleware paths). Tested ~60 materially distinct candidate probes across encodings (%2F/%2f/mixed/multiple/%2F-only, double %252F, triple %25252F, quad %2525252F, bare %25, %252), structural guard shapes (multi-param, prefix end:false), all router-option combinations (ignoreTrailingSlash/ignoreDuplicateSlashes/useSemicolonDelimiter alone and combined), all HTTP methods (GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS), and an alternate registration entry point (encapsulated prefixed plugin use()). Result: 24 bypasses on vulnerable 9.3.2, 0 bypasses on fixed 9.3.3 (= latest published). The 9.3.3 fix (commit 61d90cd) is complete for the encoded-slash-in-parameter bypass class; no distinct variant or bypass was confirmed.",
  "relation": "newer_version_sibling",
  "origin_kind": "pruva_variant",
  "repository": "fastify/middie",
  "submitted_target": {
    "target_kind": "version",
    "commit_sha": "792d2f46ae68516d3122c9a4468a5748a34efb47",
    "version": "9.3.2",
    "ref": "v9.3.2",
    "display": "@fastify/middie@9.3.2 (vulnerable, commit 792d2f46)"
  },
  "variant_target": {
    "target_kind": "commit",
    "commit_sha": "e038188b33b9436e1be9f9d1c1920416ec6c18f1",
    "version": "9.3.3",
    "ref": "v9.3.3",
    "display": "@fastify/middie@9.3.3 (fixed = latest published, commit e038188b, includes fix 61d90cd + malformed-URL hardening 01acaed)"
  },
  "same_root_cause_confidence": "high",
  "same_surface_confidence": "high",
  "claimed_surface": "library_api",
  "validated_surface": "library_api",
  "required_entrypoint_kind": "function_call",
  "required_entrypoint_detail": "Fastify app.inject (canonical library entrypoint) and encapsulated plugin instance.use() exercising a parameterized @fastify/middie guard on /user/:id/comments (and multi-param /api/:org/:repo, prefix /files/:dir) with encoded-slash variants in the parameter position, sent without credentials",
  "attacker_controlled_input": "URL path containing an encoded slash (%2F, or nested/variant encodings %2f/%252F/%25252F/...) in a path-parameter position, sent to a Fastify server that guards a parameterized route with middie middleware, without the required credential header",
  "trigger_path": "Request URL -> index.js hook handler -> lib/engine.js run() -> normalizePathForMatching() (the sole path-decode sink) -> Holder.done() regexp.exec(normalizedUrl) decides whether the parameterized guard runs. On 9.3.2 sanitizeUrlPath decodes %2F -> '/', adding a phantom segment so the /user/:id/comments guard regexp fails to match while find-my-way's router still dispatches the route (200, guard skipped). On 9.3.3 safeDecodeURI preserves %2F so the guard regexp matches and blocks (401); decodeNestedPercentEncodedBytes (%25XX->%XX) cannot recreate a literal slash; malformed input is rejected with 400.",
  "observed_impact_class": "authz_bypass",
  "exploitability_confidence": "none",
  "evidence_scope": "production_path",
  "runtime_manifest_present": true,
  "end_to_end_target_reached": true,
  "inferred": false,
  "claim_block_reason": null,
  "blocking_mitigation": "9.3.3 fix commit 61d90cd replaces the sole decode sink normalizePathForMatching in lib/engine.js: FindMyWay.sanitizeUrlPath (decoded %2F -> '/') is replaced with find-my-way's safeDecodeURI (preserves %2F/%2f and all reserved chars), aligning middie's matching path with the router's lookup path. Added decodeNestedPercentEncodedBytes (%25XX -> %XX, one level) cannot emit a literal '/'. Companion commit 01acaed rejects malformed percent-encoding (/%zz, /%) with FST_ERR_MIDDIE_MALFORMED_URL 400. Single decode sink (verified by search_code) means all entry points (onRequest/preValidation/preHandler hooks, top-level use, encapsulated prefixed use, all HTTP methods) are covered. 0 fixed-build bypasses across ~60 candidate probes.",
  "file_path": "lib/engine.js",
  "line_start": 174,
  "line_end": 201,
  "secondary_anchors": [
    {
      "file_path": "lib/engine.js",
      "line_start": 60,
      "line_end": 75
    },
    {
      "file_path": "index.js",
      "line_start": 26,
      "line_end": 40
    },
    {
      "file_path": "node_modules/find-my-way/lib/url-sanitizer.js",
      "line_start": 1,
      "line_end": 60
    }
  ],
  "review_scope_paths": [
    "lib/engine.js",
    "index.js",
    "lib/errors.js",
    "node_modules/find-my-way/lib/url-sanitizer.js",
    "test/security-encoded-slash-param-bypass.test.js",
    "test/security-normalization-bypass.test.js",
    "test/security-router-options-combinations.test.js"
  ],
  "artifact_refs": {
    "variant_manifest": "bundle/vuln_variant/variant_manifest.json",
    "validation_verdict": "bundle/vuln_variant/validation_verdict.json",
    "runtime_manifest": "bundle/vuln_variant/runtime_manifest.json",
    "repro_log": "bundle/logs/vuln_variant/reproduction_steps.log",
    "root_cause_equivalence": "bundle/vuln_variant/root_cause_equivalence.json",
    "reproducer": [
      "bundle/vuln_variant/reproduction_steps.sh",
      "bundle/vuln_variant/harness_gen/consolidated_probe.js"
    ]
  }
}
