Skip to content

Minor dev-branch findings (tracker for L1-L9 from review of last 3 months) #2246

@adrianbj

Description

@adrianbj

Short description of the issue

Single tracker for nine low-severity findings from a scan of dev over the last 3 months (commits between 2026-02-12 and 2026-05-12). Each is a small bug or behavior change — none are showstoppers, but logging them here so they don't get lost. HEAD 15c749ed.


L1 — sessionCacheLimiter admin-URL detection is prefix-only

Commit: ae20ea03 · File: wire/core/Session/Session.php:574-581
strpos($_SERVER['REQUEST_URI'], $url) === 0 matches URLs like /administrators as admin when the admin URL is /admin. Wrong cache-context header gets selected (perf only, not a security issue).
Fix: boundary check via rtrim($url, '/') . '/'.

L2 — URL sanitizer authority no longer normalized

Commit: 1f8b2726 · File: wire/core/Sanitizer/Sanitizer.php:2362-2375
The encode/decode dance is skipped for the authority portion. URLs with extended characters in userinfo/host that previously round-tripped through FILTER_VALIDATE_URL may now be rejected. Narrow functional regression; no security implication.

L3 — Session::getIP() unsanitized REMOTE_ADDR fallback

Commit: 36e11a60 · File: wire/core/Session/Session.php:1085
When all X-Forwarded-For entries are invalid, falls back to raw $_SERVER['REMOTE_ADDR'] without re-detecting $ipv6 — an IPv6 REMOTE_ADDR can then be sliced with IPv4 logic, producing a malformed partial-IP string in the fingerprint. False session invalidation, not exploitation.

L4 — Paths::short() blindly slices non-matching paths

Commit: bf3d7119 · File: wire/core/Paths.php
substr($value, strlen($root)-1) — when the caller passes a path not under $root, returns a wrong substring silently. Should verify strpos($value, $root) === 0 first and return $value unchanged otherwise.

L5 — WireDataDB::getCache() can clobber existing non-cache meta

Commit: 008ffb5a · File: wire/core/WireDataDB.php
If a meta key contains a non-cache array (no _cre/_exp/_val), decodeCacheValue() returns null and getCache() interprets that as "expired," overwriting the stored value with a cache envelope. Users converting an existing meta key to getCache() silently lose their data.

L6 — FieldtypeComments::getCommentsFields($one=true) index-type change

Commit: 7375d28b (incidental) · File: wire/modules/Fieldtype/FieldtypeComments/FieldtypeComments.module
Internal switch from numerically-indexed to name-indexed array. reset() fixes the $one=true internal path, but any external caller doing $fields[0] on the returned array now gets null.

L7 — PageFinder v2: self::$level not decremented on exception

Commit: 01d6cc00 · Files: wire/core/PageFinder/PageFinder.php:817, wire/core/PageFinder/PageFinder2.php:962
self::$level++ at start of ___find() matched by self::$level-- only on the normal-return paths. Exceptions leave the counter incremented for the rest of the request, suppressing testMode timing.
Fix: wrap body in try { ... } finally { self::$level--; ... }.

L8 — PageFinder v2: getInstance::$settings cache not keyed by Wire instance

Commit: 01d6cc00 · File: wire/core/PageFinder/PageFinder2.php:4084-4087
Static $settings cached on first call; multi-instance ProcessWire setups silently use the first instance's config->PageFinder for all instances. Multi-instance is uncommon but supported.

L9 — status=<published operator translations are counter-intuitive

Commit: abd4bd4b · Files: wire/core/PageFinder/PageFinder.php:701, wire/core/PageFinder/PageFinder2.php:822
The lenient status=published translation also rewrites <, <=, >, >=. status<published becomes status>=unpublished (matches hidden + unpublished + trash), which is the opposite of any reasonable reading. The original ask was leniency on =published typos; the comparison-operator translations weren't part of that. Suggest restricting the table to = and != only.


Setup/Environment

  • ProcessWire version: dev @ 15c749ed
  • Scan range: 2026-02-12 → 2026-05-12 (~80 PHP-touching commits)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions