pixellint

Pixellint 0.39.0 checks PHP parsing and sanitization in Matomo bulk requests.

Bulk tracking needs the receiver’s parsing rules.

A Matomo bulk request wraps tracking entries in a JSON requests collection. The receiver can process a query-string entry or a native map, then read the resulting fields through Matomo’s own helpers. Looking only for a nonempty idsite and rec in the original text misses cases where the PHP parser creates an array or a reader substitutes its documented default. The value used by the tracker can differ from the apparent value in the pasted request.

Pixellint already reused its event and ecommerce contracts for observable bulk entries. This release extends the bounded matomo_php8 profile that prepares those entries. It adds bracket trees, the pinned Common sanitization steps and native readers for core plugin flags. It also corrects a false positive: optional browser capability flags can report zero, as Matomo’s official JavaScript tracker does. These changes deepen an existing pack without adding a vendor.

The destination scope remains the documented matomo.php and piwik.php routes, including self-hosted collectors. A complete HTTP capture gives the validator the body and the availability context needed to inspect its bulk entries. Each inspected entry keeps its own collection location, so a malformed second item is reported against that item rather than against the first event or the surrounding request.

Bracket fields can change which defaults apply.

PHP bracket syntax creates parameter trees. Empty brackets append members, numeric brackets address indexes, and named brackets create keyed members. Pixellint 0.39.0 follows the supported forms before running the mapped Matomo readers. This matters for ordinary required fields as well as nested data: idsite[]=1 creates a container, while idsite=1 creates a scalar website ID.

In the pinned implementation, the integer reader for idsite falls back to zero for the array form. The pack can therefore report that the resulting site ID does not satisfy its existing positive-ID contract. Similarly, rec[]=1 does not become the recording flag merely because the digit appears inside the original string. The recording reader’s default reaches the existing rec=1 check.

Nested JSON-oriented fields receive their own handling. Supported bracket trees for ec_items and uadata preserve the distinction between indexed members and named members as they reach the mapped reader. The local implementation keeps bounds around these structures. Recognizing a supported nested form does not mean that arbitrary PHP syntax or every custom tracking field has acquired a destination contract.

Later assignments determine the surviving value.

The order of duplicate assignments matters to the receiver. A scalar can replace an earlier tree, and a later bracket assignment can replace a scalar with a container. The parser models that shadowing for supported bulk query forms instead of validating every discarded value as though the tracker used them all. Required-field checks run on the value that survives parsing and the mapped reader.

Consider idsite=0&idsite[]=1&idsite=2&rec=1. The final scalar assignment leaves the website ID at 2, so the earlier zero and array do not create an extra site-ID error. Reverse the final assignments and the surviving type changes. Those pairs belong in regression controls because a parser that always keeps the first value or always flattens containers can produce a plausible but incorrect result.

Top-level name normalization also participates in this process. PHP can normalize a space or dot in a field name to an underscore, allowing apparently different spellings to target the same key. Percent decoding happens at the appropriate input layer. The implementation does not repeatedly decode escaped content until it resembles a familiar field or a valid number.

Sanitization belongs before the mapped field contract.

The pinned Matomo Common helper sanitizes input as part of reading a request variable. Pixellint now models the supported HTML4 entity decoding, quote escaping and null-byte removal for that profile. This closes the previous blanket deferral for sanitizer-sensitive values inside otherwise inspectable bulk entries. The result is evaluated by the destination field reader rather than by a generic guess about its appearance.

An encoded numeric entity illustrates why the order matters. The bulk query value idsite=%26%2349%3B decodes to the entity spelling for the digit 1. The pinned sanitization and integer reader can resolve that input to the same site ID as a scalar 1. A validator that applies its integer format check to the entity spelling would reject the value too early.

The controls also cover named entities, encoded quotes, null bytes and nested entity spellings. They distinguish a single supported decoding pass from recursive decoding. Invalid UTF-8 remains outside this profile, and a sanitized value is not an authentication or account-configuration verdict. The behavior here describes how the reviewed local reader prepares an input for existing tracking contracts.

Matomo’s tracker reports unavailable browser features as zero.

Matomo’s pinned JavaScript producer sends 1 when a supported browser feature is present and 0 when it is absent. The previous pack accepted only 1 for cookie and the plugin flags pdf, fla, java, qt, realp, wma and ag. That could turn an ordinary tracker request reporting an unsupported capability into a field error. Pixellint 0.39.0 accepts both published flag values on direct query hits and inspected bulk entries.

These fields remain optional. A capture that omits them does not need to add a capability list to pass validation. A supplied value of 2 still produces a format finding against the reviewed producer profile. The PHP server’s integer reader is broader than the producer’s binary output, so that finding does not establish that a collector rejected the request. The source link identifies which published behavior the pack checks.

Native JSON maps introduce another type distinction: booleans, numbers and containers are not simply their apparent URL spellings. Request::getPlugins selects integer readers with a zero default for these eight flags. The expanded mapping follows those readers for supported native inputs. Independent controls now include false, zero and out-of-profile values so that a native absence does not become the same false positive through a different transport.

Other native readers retain narrower boundaries. Nonintegral numeric stringification can depend on PHP precision settings, and float containers can encounter behavior that depends on the runtime. Premium and plugin fields require their own reader evidence. An unsupported native value keeps an explicit information finding when its conversion is unestablished, rather than becoming a fabricated event-field error.

Runtime-dependent forms retain explicit limits.

The expanded profile has a defined PHP input context. It uses ampersand separators, a 1,000-variable limit and a 64-level bracket bound. Literal semicolon separator candidates remain unvalidated because a deployment can configure separator handling differently. Inputs beyond the supported variable or nesting bounds also retain an explicit deferral. These are local evaluation limits, not a statement that every Matomo collector rejects those requests.

Malformed bracket syntax and numeric indexes whose meaning is not portable across the supported integer context remain outside the checked subset. Append behavior after negative-only indexes also has PHP-version dependencies, so the implementation does not claim a universal result there. Ordinary named and portable numeric trees can be inspected without extending that claim to every possible key.

The apiv tracking field does not identify the deployed PHP version or Matomo plugin set. The source pin makes the reviewed behavior reproducible; it does not discover those deployment facts from a request. The depth audit records remaining gaps alongside the implemented mapping so that a later expansion can target an exact reader or runtime condition instead of treating a clean local report as complete server compatibility.

The expectations come from an independent PHP execution.

The new parser and reader controls use an executed PHP 8.3.35 runtime with the pinned Matomo source at commit 1e9169ddd9eba7c982dc67b8bd3f9310a7a312c6. The recorded evidence includes runtime settings and source hashes. PHP produces the parsed trees and the Common reader outputs. Pixellint’s candidate results are compared with those independent observations.

The fixture set includes successful requests, deliberate field errors, surviving duplicate assignments and excluded runtime boundaries. Entity controls check both the parsed input and its reader result. Native-map controls exercise typed values. These expectations are independent of Pixellint’s own output, which prevents a parser implementation from becoming its own acceptance oracle.

Executing that source locally provides stronger evidence for the modeled conversions than a checklist of fixture names. It still does not execute a customer’s collector, verify a configured site ID, or establish attribution. The PHP runtime is a validation oracle used for the release review. Pixellint continues to ship its local engine through the native and WASM distributions.

The release replay separates corpus evidence from authored controls.

The frozen comparison contains 8,836 stored samples: 8,673 VAST tracking artifacts, 113 URL artifacts and 50 JSON artifacts. All 8,800 rows from the previous snapshot retain their original artifacts and reference clocks; 36 newly stored rows extend the corpus. Both engine versions receive the same frozen inputs, kinds, selection policy and clocks. Errors stay at 324 in both versions, warnings stay at 769, and the same 250 samples contain errors. All 8,836 complete reports are unchanged, with zero engine exceptions. The corpus contains one selected Matomo artifact and supplies no broad sample of the newly supported bulk forms. Their behavior is measured separately with independent authored controls.

Stored tracking URLs alone cannot exercise every bulk-body conversion. The authored PHP controls supply evidence for the new bracket and sanitizer paths, while the complete corpus replay checks that unrelated destination findings retain their intended behavior. The published aggregate comparison distinguishes those populations and excludes customer URLs, identifiers and payload values. All 84 new and retained authored controls meet their static expectations in 0.39.0; 12 already matched in 0.38.0, so 72 outcomes improve. The new fixture family supplies 75 source cases, and another 256 independent PHP vectors check the parser directly. Native, Node and browser WASM comparisons use complete reports, including fields, sources and targets.

Pixellint 0.39.0 ships as a minor release through the existing package and executable channels. The pixellint.org playground and Vastlint’s embedded pixel engine receive the reviewed browser build. The Matomo pack page describes the expanded bulk scope and the remaining PHP context limits. When a bulk entry produces a finding, use its field and collection location to inspect the value the mapped reader actually receives.

Sources

Contract pages

The dated argument is above. These pages are the field lists.

Inspect a Matomo capture Docs