pixellint

Pixellint 0.44.0 checks Mixpanel SDK plain-text captures.

The official SDK exposes a missing body representation.

The pinned Mixpanel JavaScript SDK revision d6bdc7f80230a54f8ec470d344058d0a36a81aa2 serializes a payload, optionally Base64-encodes it, and places the result in data. Its POST sender constructs a data= string using percent encoding. When sendBeacon is selected, that string becomes the supplied body. The same sender serves event tracking, people updates and group updates.

Both encoded modes matter. The source retains a configurable Base64 mode, while initialization for standard Mixpanel hosts chooses raw JSON when the caller has not supplied a payload format. Treating every modern SDK beacon as Base64 would overlook a source-supported branch. Pixellint admits both modes through the already documented data contracts after inspecting the outer captured representation.

The concrete gap was a missing project token that remained invisible in a plain-text capture. Changing only that capture's media type to application/x-www-form-urlencoded let the previous engine find the existing required-token error. The token rule already existed. The missing step was connecting the SDK's actual body representation to that rule without inventing a different HTTP request.

The captured text/plain header remains visible.

The browser standards explain the observed header. Fetch assigns text/plain;charset=UTF-8 when extracting a JavaScript string body. Beacon uses that extracted body and media type. A string that resembles a form does not become a URLSearchParams object merely because its contents start with data=. Those are different body inputs with different resulting transport evidence.

Pixellint retains the observed media type and original request body. The owning Mixpanel pack receives a private interpretation of the recognized SDK entity, while the generic normalized request continues to describe text. This lets payload rules inspect an event without implying that the capture contained a form media type. Original wire and entity byte accounting also remains available through the existing capture adapter.

Payload inspection does not establish delivery. sendBeacon can queue a request without reporting whether the destination ultimately accepted it. A local capture cannot recover browser in-flight quota state, a missing response, project permissions or successful attribution. The release checks the available representation and its destination fields; those operational questions still require additional evidence.

Only the selected built-in endpoint receives this interpretation.

The new interpretation belongs to the built-in Mixpanel Track, Engage and Groups entries. Their existing host matchers must accept the capture, and the new scope narrows to POST on the owning /track, /engage or /groups route, with its optional trailing slash. A path that merely contains a familiar word does not acquire the SDK body decoder. Import and unrelated products retain their existing contracts.

Rulepack selection remains part of that authority. Excluding the owner does not leave a hidden payload decoder active. A custom pack, an empty custom engine or a replacement plugin that reuses the same ID does not inherit the built-in capability. When another selected pack receives the same request, it keeps the ordinary HTTP representation rather than receiving a rewritten Mixpanel body by accident.

The change preserves the existing public request and manifest types, plugin trait, exported enums and ordinary report shapes. Consumers can keep constructing their current Rust values and using the established CLI, npm and WASM interfaces. The deeper check is reached through the same request capture API, with an implementation boundary that does not impose a new public manifest field on custom rulepack authors.

The outer data member is decoded once.

A recognizable SDK form body has exactly one decoded data key and no additional or repeated members. The local parser applies one strict outer form decode, including the form meaning of plus. Encoded separators remain inside that member, and a percent sign produced by decoding is not decoded again. The resulting value then reaches the existing raw-JSON or Base64 payload contract.

Ambiguity stays explicit. Repeated data fields, extra form members, malformed outer escapes and a competing data value in the URL prevent an established interpretation of the body. The new core.request.sdk_body_unvalidated information finding explains that limit. Existing query data checks can still run when the body is deferred, while the validator avoids inventing an undocumented query-versus-body precedence rule for this producer form.

Arbitrary non-form text and literal JSON text retain their prior behavior. This release does not classify every text/plain body as Mixpanel form data. Once a supported data value has been established, a demonstrably malformed ordinary Base64 or JSON representation still receives its existing vendor diagnostic. The distinction is whether the local capture establishes the claimed representation, rather than whether an input happens to look plausible.

The SDK Unicode boundary keeps an explicit information finding.

The pinned utility encoder processes UTF-16 code units before Base64 encoding. An astral character can therefore produce paired CESU-8 byte sequences rather than standard UTF-8. A source-produced fixture demonstrates this boundary. Applying a general strict UTF-8 decoder to that representation and claiming a new vendor rejection would overstate what the reviewed source establishes.

The new body interpretation keeps ordinary strict UTF-8 payloads within the existing rules. ASCII and supported BMP text can reach their field checks, and the raw-JSON mode preserves supported Unicode directly. A recognized paired-CESU-8 producer value remains explicitly unvalidated through an information finding after its complete byte validity, JSON shape and local resource checks have been established.

No lossy replacement is used to make those bytes look valid, and the exception does not relax Base64 semantics for every vendor. A malformed byte sequence outside the recognized producer representation retains the ordinary encoded-data diagnostic. Deferring the documented boundary preserves the original evidence and makes the remaining decoding depth visible to someone reviewing a capture.

Local inspection bounds are separate from destination limits.

The newly admitted plain-text form path has local bounds before it reaches existing payload scopes. Those bounds cover source and decoded text, parsed values, nesting, concrete paths, generated finding text and projected rule work. A compact encoded string can still expand into expensive nested evaluation or many findings, so raw input length alone is not sufficient to bound the work.

An over-bound capture receives core.request.sdk_body_limit as an information finding. Body-dependent checks remain unvalidated, while available endpoint, method and query evidence continues to run. This is local evaluator policy. It does not claim that Mixpanel rejects the request, and it does not turn the validator's memory or work budget into a destination batch quota.

The bounds must also leave established vendor boundaries reachable. A reasonable Track batch at the published count limit, and its one-over counterpart, can reach the existing count rule when the local limits are satisfied. Supported explicit binary captures reuse the shared identity or gzip decoding path and its integrity checks. Raw text marked gzip retains its established deferral, and existing endpoint compression contracts remain unchanged.

Source-authored controls distinguish deeper checks from regressions.

The independent controls begin with the pinned producer's actual encoding and sending functions, using synthetic payloads and captured outputs. Raw-JSON and Base64 modes both receive valid and invalid destination-field cases. Track token and event omissions, Engage identity requirements and Groups identity fields demonstrate that existing contracts become reachable through the newly admitted representation. Browser checks also exposed a documentation link gap: existing JSON root findings had no matching root anchor. The generated pack pages now display those root contracts and use the engine's diagnostic names, so a batch-limit finding reaches its supporting rule.

The suite also covers exact routes, wrong methods, host lookalikes, custom replacement and selection boundaries. Percent escapes, separators, duplicated form members and query conflicts exercise representation ownership. Unicode controls distinguish supported raw JSON from the verified paired-CESU-8 boundary. Capture fixtures keep known absence, unavailable evidence and explicitly redacted fields separate, with no guessed HAR request-body extension.

All 144 independent static controls match the expected outcome in 0.44.0, compared with 67 in 0.43.0. Seventy-seven finding outcomes change, and 55 previously non-error cases now produce errors. Error findings across those controls rise from 8 to 69. Complete native, Node and browser-target WASM reports agree in 432 comparisons. The retained workspace passes 610 Rust tests, alongside 20 fullsize resource checks, 50 compatibility controls and 486 inherited AppsFlyer report comparisons. These are synthetic contract checks, reported separately from the stored corpus. Actual Chromium checks pass 52 SDK controls and pack-page probes across desktop and mobile, plus eight retained capture and documentation checks. Request captures remain unchanged in the textarea, with no input sharing or sample requests.

Corpus replay measures retained behavior alongside the new scope.

A complete old and new replay remains necessary even when the representation gap is established by official source rather than a matching historical failure. The stored corpus contains other destinations, bare URLs, JSON payloads and extracted VAST trackers. Those inputs provide a broad regression check for the unchanged ordinary paths and for the custom and endpoint boundaries around the new interpretation.

The frozen snapshot contains 8,836 artifacts through sample watermark 9,964. Both versions produce 324 errors, 769 warnings and 250 error-bearing artifacts, with zero changed complete reports or engine exceptions. Its stored kinds are 8,673 VAST tags, 113 URLs and 50 JSON pastes; there are no captured HTTP request bodies in this snapshot. That explains the zero observed beacon detection delta. Another 708 complete native and WASM comparisons cover all URL and JSON pastes plus representative VAST findings, while the full native replay covers every stored artifact.

Published examples remain synthetic, while exported customer payloads and credentials stay in private local replay files. The website request mode keeps submitted captures out of query sharing and sample collection. A clean locally inspected event still cannot prove token ownership, delivery, receiver acceptance, deduplication history or attribution. Those external requirements remain in the coverage audit beside the body contracts that can now be evaluated.

Sources

Contract pages

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

Inspect a Mixpanel request capture Docs